@netmind/arena-cli 0.21.0 → 0.24.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 +1 -0
- package/dist/index.js +467 -110
- 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 Command26 } 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
|
}
|
|
@@ -1624,8 +1624,350 @@ Community games are authored in the public arena-games repo and run sandboxed.
|
|
|
1624
1624
|
They are not in the built-in 'arena rules' list \u2014 this is how you discover them.`
|
|
1625
1625
|
);
|
|
1626
1626
|
|
|
1627
|
-
// src/commands/
|
|
1627
|
+
// src/commands/world.ts
|
|
1628
1628
|
import { Command as Command7 } from "commander";
|
|
1629
|
+
import { readFile, writeFile, mkdir, readdir, stat } from "fs/promises";
|
|
1630
|
+
import path from "path";
|
|
1631
|
+
var MANIFEST_FILE = "world.manifest.json";
|
|
1632
|
+
var HTML_FILE = "index.html";
|
|
1633
|
+
var SCORER_FILE = "scorer.js";
|
|
1634
|
+
var REPLAY_FILE = "replay.json";
|
|
1635
|
+
var GUIDE_FILE = "agent.md";
|
|
1636
|
+
var ASSETS_DIR = "assets";
|
|
1637
|
+
function partnerKey(explicit) {
|
|
1638
|
+
const key = explicit || process.env.ARENA_PARTNER_KEY;
|
|
1639
|
+
if (!key) {
|
|
1640
|
+
throw new Error(
|
|
1641
|
+
"No partner key. Set ARENA_PARTNER_KEY=arena_pk_... or pass --key. This is your platform credential, not an agent API key."
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
return key;
|
|
1645
|
+
}
|
|
1646
|
+
async function partnerApi(pathname, key, body) {
|
|
1647
|
+
const res = await fetch(`${getApiUrl()}${pathname}`, {
|
|
1648
|
+
method: "POST",
|
|
1649
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
1650
|
+
body: JSON.stringify(body)
|
|
1651
|
+
});
|
|
1652
|
+
const json = await res.json().catch(() => ({}));
|
|
1653
|
+
if (!res.ok) {
|
|
1654
|
+
throw new Error(String(json.error ?? `${res.status} ${res.statusText}`));
|
|
1655
|
+
}
|
|
1656
|
+
return json;
|
|
1657
|
+
}
|
|
1658
|
+
async function readIfPresent(file) {
|
|
1659
|
+
try {
|
|
1660
|
+
return await readFile(file, "utf8");
|
|
1661
|
+
} catch {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function loadAssets(dir) {
|
|
1666
|
+
const root = path.join(dir, ASSETS_DIR);
|
|
1667
|
+
let names;
|
|
1668
|
+
try {
|
|
1669
|
+
names = await readdir(root);
|
|
1670
|
+
} catch {
|
|
1671
|
+
return {};
|
|
1672
|
+
}
|
|
1673
|
+
const out = {};
|
|
1674
|
+
for (const name of names) {
|
|
1675
|
+
const full = path.join(root, name);
|
|
1676
|
+
if (!(await stat(full)).isFile()) continue;
|
|
1677
|
+
const buf = await readFile(full);
|
|
1678
|
+
out[`${ASSETS_DIR}/${name}`] = `data:${mimeOf(name)};base64,${buf.toString("base64")}`;
|
|
1679
|
+
}
|
|
1680
|
+
return out;
|
|
1681
|
+
}
|
|
1682
|
+
function mimeOf(name) {
|
|
1683
|
+
const ext = path.extname(name).toLowerCase();
|
|
1684
|
+
if (ext === ".png") return "image/png";
|
|
1685
|
+
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
1686
|
+
if (ext === ".gif") return "image/gif";
|
|
1687
|
+
if (ext === ".svg") return "image/svg+xml";
|
|
1688
|
+
if (ext === ".webp") return "image/webp";
|
|
1689
|
+
if (ext === ".mp3") return "audio/mpeg";
|
|
1690
|
+
if (ext === ".ogg") return "audio/ogg";
|
|
1691
|
+
if (ext === ".json") return "application/json";
|
|
1692
|
+
return "application/octet-stream";
|
|
1693
|
+
}
|
|
1694
|
+
async function loadBundle(dir) {
|
|
1695
|
+
const manifestRaw = await readIfPresent(path.join(dir, MANIFEST_FILE));
|
|
1696
|
+
if (manifestRaw === null) throw new Error(`${MANIFEST_FILE} not found in ${dir}`);
|
|
1697
|
+
let manifest;
|
|
1698
|
+
try {
|
|
1699
|
+
manifest = JSON.parse(manifestRaw);
|
|
1700
|
+
} catch (e) {
|
|
1701
|
+
throw new Error(`${MANIFEST_FILE} is not valid JSON: ${e.message}`);
|
|
1702
|
+
}
|
|
1703
|
+
const html = await readIfPresent(path.join(dir, HTML_FILE));
|
|
1704
|
+
if (html === null) throw new Error(`${HTML_FILE} not found in ${dir}`);
|
|
1705
|
+
const bundle = { manifest, html, assets: await loadAssets(dir) };
|
|
1706
|
+
bundle.agentGuide = await readIfPresent(path.join(dir, GUIDE_FILE)) ?? void 0;
|
|
1707
|
+
if (manifest.scoring?.tier === "L1") {
|
|
1708
|
+
const scorer = await readIfPresent(path.join(dir, SCORER_FILE));
|
|
1709
|
+
if (scorer === null) {
|
|
1710
|
+
throw new Error(`tier L1 needs ${SCORER_FILE} (a global function score(submission, ctx))`);
|
|
1711
|
+
}
|
|
1712
|
+
bundle.scorer = scorer;
|
|
1713
|
+
const replayRaw = await readIfPresent(path.join(dir, REPLAY_FILE));
|
|
1714
|
+
if (replayRaw === null) {
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
`tier L1 needs ${REPLAY_FILE}: [{"submission": {...}, "expectedScore": 42}]. It is what proves your scorer runs and pins what it is meant to produce.`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
bundle.replaySamples = JSON.parse(replayRaw);
|
|
1721
|
+
} catch (e) {
|
|
1722
|
+
throw new Error(`${REPLAY_FILE} is not valid JSON: ${e.message}`);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return bundle;
|
|
1726
|
+
}
|
|
1727
|
+
function toSubmission(bundle) {
|
|
1728
|
+
const { manifest } = bundle;
|
|
1729
|
+
return {
|
|
1730
|
+
type: manifest.type,
|
|
1731
|
+
displayName: manifest.displayName,
|
|
1732
|
+
html: bundle.html,
|
|
1733
|
+
schemaVersion: manifest.schemaVersion,
|
|
1734
|
+
supportedSchemaVersions: manifest.supportedSchemaVersions,
|
|
1735
|
+
collections: manifest.storage?.collections,
|
|
1736
|
+
quota: manifest.storage?.quota,
|
|
1737
|
+
capabilities: manifest.capabilities,
|
|
1738
|
+
presentation: manifest.presentation,
|
|
1739
|
+
aboutMarkdown: manifest.aboutMarkdown,
|
|
1740
|
+
agentGuide: bundle.agentGuide,
|
|
1741
|
+
credits: manifest.credits,
|
|
1742
|
+
assets: bundle.assets,
|
|
1743
|
+
leaderboard: manifest.leaderboard,
|
|
1744
|
+
scoring: manifest.scoring?.tier === "L1" ? { tier: "L1", scorer: bundle.scorer, replaySamples: bundle.replaySamples } : manifest.scoring ?? null
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function localChecks(bundle) {
|
|
1748
|
+
const problems = [];
|
|
1749
|
+
const { manifest } = bundle;
|
|
1750
|
+
if (!manifest.type) problems.push(`${MANIFEST_FILE}: "type" is required`);
|
|
1751
|
+
if (!manifest.displayName) problems.push(`${MANIFEST_FILE}: "displayName" is required`);
|
|
1752
|
+
if (!manifest.presentation?.surface) {
|
|
1753
|
+
problems.push(`${MANIFEST_FILE}: "presentation.surface" must be "fullscreen" or "embed"`);
|
|
1754
|
+
}
|
|
1755
|
+
for (const [name, spec] of Object.entries(manifest.storage?.collections ?? {})) {
|
|
1756
|
+
const declared = spec.schema?.$schema;
|
|
1757
|
+
if (declared && !declared.includes("2020-12")) {
|
|
1758
|
+
problems.push(
|
|
1759
|
+
`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.`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
if (!spec.maxRecordBytes) {
|
|
1763
|
+
problems.push(`collection "${name}": "maxRecordBytes" is required`);
|
|
1764
|
+
}
|
|
1765
|
+
if ((spec.indexes ?? []).length > 6) {
|
|
1766
|
+
problems.push(`collection "${name}": at most 6 indexes (has ${spec.indexes.length})`);
|
|
1767
|
+
}
|
|
1768
|
+
for (const tuple of spec.unique ?? []) {
|
|
1769
|
+
for (const p of tuple) {
|
|
1770
|
+
if (p.startsWith("payload.") && !(spec.indexes ?? []).includes(p)) {
|
|
1771
|
+
problems.push(`collection "${name}": unique path "${p}" must also be in "indexes"`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
const remote = bundle.html.match(/(?:src|href)\s*=\s*["']https?:\/\/[^"']+/i);
|
|
1777
|
+
if (remote) {
|
|
1778
|
+
problems.push(
|
|
1779
|
+
`${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.`
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
if (manifest.scoring?.tier === "L1") {
|
|
1783
|
+
if (!manifest.leaderboard) {
|
|
1784
|
+
problems.push(`${MANIFEST_FILE}: tier L1 needs a "leaderboard" \u2014 otherwise nothing consumes the score`);
|
|
1785
|
+
}
|
|
1786
|
+
if (manifest.leaderboard?.scorePath) {
|
|
1787
|
+
problems.push(
|
|
1788
|
+
`${MANIFEST_FILE}: "leaderboard.scorePath" is not used at tier L1 \u2014 your scorer produces the score. Remove it.`
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
if (!bundle.replaySamples?.length) {
|
|
1792
|
+
problems.push(`${REPLAY_FILE}: at least one sample is required at tier L1`);
|
|
1793
|
+
}
|
|
1794
|
+
if (!bundle.agentGuide?.trim()) {
|
|
1795
|
+
problems.push(
|
|
1796
|
+
`${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`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
} else if (manifest.leaderboard && !manifest.leaderboard.scorePath) {
|
|
1800
|
+
problems.push(`${MANIFEST_FILE}: tier L0 needs "leaderboard.scorePath" naming the payload field to rank on`);
|
|
1801
|
+
}
|
|
1802
|
+
return problems;
|
|
1803
|
+
}
|
|
1804
|
+
var initCmd = new Command7("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) => {
|
|
1805
|
+
try {
|
|
1806
|
+
const dir = opts.dir ?? type;
|
|
1807
|
+
const tier = opts.tier === "L0" ? "L0" : "L1";
|
|
1808
|
+
await mkdir(path.join(dir, ASSETS_DIR), { recursive: true });
|
|
1809
|
+
const manifest = {
|
|
1810
|
+
type,
|
|
1811
|
+
displayName: type,
|
|
1812
|
+
schemaVersion: 1,
|
|
1813
|
+
// `aspect` is what gives an embedded world its height. Omit it and the
|
|
1814
|
+
// iframe falls back to the HTML default of 150px, with the world's own
|
|
1815
|
+
// content overflowing out of sight.
|
|
1816
|
+
presentation: { surface: "embed", cover: "", aspect: "16/9" },
|
|
1817
|
+
// Nested under `storage`, matching world.manifest.json in arena-games — a
|
|
1818
|
+
// world written for the pull-request path submits here unchanged.
|
|
1819
|
+
storage: {
|
|
1820
|
+
collections: {
|
|
1821
|
+
runs: {
|
|
1822
|
+
schema: {
|
|
1823
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1824
|
+
type: "object",
|
|
1825
|
+
properties: { moves: { type: "array", items: { type: "number" } } },
|
|
1826
|
+
required: ["moves"]
|
|
1827
|
+
},
|
|
1828
|
+
write: "owner",
|
|
1829
|
+
maxRecordBytes: 8192,
|
|
1830
|
+
// At L1 nothing needs indexing for the board — the scorer produces
|
|
1831
|
+
// the score — so the scaffold declares none rather than a field that
|
|
1832
|
+
// looks like it counts and does not.
|
|
1833
|
+
indexes: tier === "L0" ? ["payload.score"] : []
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
quota: { writesPerHourPerAuthor: 60 }
|
|
1837
|
+
},
|
|
1838
|
+
leaderboard: tier === "L0" ? { collection: "runs", scorePath: "payload.score", aggregate: "max", window: "season" } : { collection: "runs", aggregate: "max", window: "season" },
|
|
1839
|
+
scoring: { tier }
|
|
1840
|
+
};
|
|
1841
|
+
await writeFile(path.join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
1842
|
+
`);
|
|
1843
|
+
await writeFile(
|
|
1844
|
+
path.join(dir, HTML_FILE),
|
|
1845
|
+
`<!doctype html>
|
|
1846
|
+
<html>
|
|
1847
|
+
<body>
|
|
1848
|
+
<p>${type}</p>
|
|
1849
|
+
<script>
|
|
1850
|
+
// Talk to Arena via window.parent.postMessage \u2014 see the world SDK guide.
|
|
1851
|
+
</script>
|
|
1852
|
+
</body>
|
|
1853
|
+
</html>
|
|
1854
|
+
`
|
|
1855
|
+
);
|
|
1856
|
+
if (tier === "L1") {
|
|
1857
|
+
await writeFile(
|
|
1858
|
+
path.join(dir, SCORER_FILE),
|
|
1859
|
+
[
|
|
1860
|
+
"// Runs on Arena's servers, never in the player's browser. That is the point:",
|
|
1861
|
+
"// the browser submits what happened, this decides what it was worth.",
|
|
1862
|
+
"//",
|
|
1863
|
+
"// Must be deterministic \u2014 the same submission must always score the same, or",
|
|
1864
|
+
"// your replay samples prove nothing and a sealed season cannot be rebuilt.",
|
|
1865
|
+
"// Math.random and every clock read are blocked.",
|
|
1866
|
+
"function score(submission, ctx) {",
|
|
1867
|
+
" const moves = submission && submission.moves;",
|
|
1868
|
+
" if (!Array.isArray(moves)) ctx.reject('no moves in submission');",
|
|
1869
|
+
" if (moves.length > 1000) ctx.reject('run too long to be real');",
|
|
1870
|
+
" return moves.reduce((sum, m) => sum + (Number(m) || 0), 0);",
|
|
1871
|
+
"}",
|
|
1872
|
+
""
|
|
1873
|
+
].join("\n")
|
|
1874
|
+
);
|
|
1875
|
+
await writeFile(
|
|
1876
|
+
path.join(dir, REPLAY_FILE),
|
|
1877
|
+
`${JSON.stringify([{ submission: { moves: [1, 2, 3] }, expectedScore: 6 }], null, 2)}
|
|
1878
|
+
`
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
console.log(`Created ${dir}/`);
|
|
1882
|
+
console.log(` ${MANIFEST_FILE} manifest`);
|
|
1883
|
+
console.log(` ${HTML_FILE} the document, sandboxed with no network access`);
|
|
1884
|
+
if (tier === "L1") {
|
|
1885
|
+
console.log(` ${SCORER_FILE} server-side scoring`);
|
|
1886
|
+
console.log(` ${REPLAY_FILE} cases your scorer must reproduce`);
|
|
1887
|
+
}
|
|
1888
|
+
console.log(`
|
|
1889
|
+
Next: arena world check ${dir}`);
|
|
1890
|
+
} catch (e) {
|
|
1891
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
1892
|
+
process.exit(1);
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
var checkCmd = new Command7("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) => {
|
|
1896
|
+
try {
|
|
1897
|
+
const bundle = await loadBundle(dir);
|
|
1898
|
+
const problems = localChecks(bundle);
|
|
1899
|
+
if (problems.length > 0) {
|
|
1900
|
+
for (const p of problems) console.error(` \u2717 ${p}`);
|
|
1901
|
+
console.error(`
|
|
1902
|
+
${problems.length} problem(s) found locally. Fix these first.`);
|
|
1903
|
+
process.exit(1);
|
|
1904
|
+
}
|
|
1905
|
+
const result = await partnerApi(
|
|
1906
|
+
"/partners/v1/worlds/validate",
|
|
1907
|
+
partnerKey(opts.key),
|
|
1908
|
+
toSubmission(bundle)
|
|
1909
|
+
);
|
|
1910
|
+
if (opts.json) {
|
|
1911
|
+
printJson(result);
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
console.log("\u2713 Valid.");
|
|
1915
|
+
console.log(` contentHash ${result.contentHash.slice(0, 16)}\u2026`);
|
|
1916
|
+
if (bundle.manifest.scoring?.tier === "L1") {
|
|
1917
|
+
console.log(` scorer reproduced all ${bundle.replaySamples?.length} replay sample(s)`);
|
|
1918
|
+
}
|
|
1919
|
+
console.log(`
|
|
1920
|
+
Next: arena world submit ${dir}`);
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
1923
|
+
process.exit(1);
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
var submitCmd = new Command7("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) => {
|
|
1927
|
+
try {
|
|
1928
|
+
const bundle = await loadBundle(dir);
|
|
1929
|
+
const problems = localChecks(bundle);
|
|
1930
|
+
if (problems.length > 0) {
|
|
1931
|
+
for (const p of problems) console.error(` \u2717 ${p}`);
|
|
1932
|
+
process.exit(1);
|
|
1933
|
+
}
|
|
1934
|
+
const result = await partnerApi(
|
|
1935
|
+
"/partners/v1/worlds",
|
|
1936
|
+
partnerKey(opts.key),
|
|
1937
|
+
toSubmission(bundle)
|
|
1938
|
+
);
|
|
1939
|
+
if (opts.json) {
|
|
1940
|
+
printJson(result);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
console.log(`\u2713 Submitted ${result.type} (${result.contentHash.slice(0, 12)}\u2026)`);
|
|
1944
|
+
console.log(` status: ${result.status}`);
|
|
1945
|
+
console.log(
|
|
1946
|
+
"\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."
|
|
1947
|
+
);
|
|
1948
|
+
} catch (e) {
|
|
1949
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
1950
|
+
process.exit(1);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
var rulesCmd = new Command7("rules").description("Print a world's rules, written for an agent").argument("<type>", "World type, e.g. deed-and-dice").action(async (type) => {
|
|
1954
|
+
try {
|
|
1955
|
+
const res = await fetch(`${getApiUrl()}/worlds/${encodeURIComponent(type)}/guide.md`);
|
|
1956
|
+
if (res.status === 404) {
|
|
1957
|
+
const body = await res.json().catch(() => ({}));
|
|
1958
|
+
throw new Error(body.error ?? `no agent guide published for '${type}'`);
|
|
1959
|
+
}
|
|
1960
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
1961
|
+
console.log(await res.text());
|
|
1962
|
+
} catch (e) {
|
|
1963
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
1964
|
+
process.exit(1);
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
var worldCmd = new Command7("world").description("Author and publish a partner world").addCommand(rulesCmd).addCommand(initCmd).addCommand(checkCmd).addCommand(submitCmd);
|
|
1968
|
+
|
|
1969
|
+
// src/commands/rules.ts
|
|
1970
|
+
import { Command as Command8 } from "commander";
|
|
1629
1971
|
var DEFAULT_FRONTEND_URL = "https://arena42.ai";
|
|
1630
1972
|
var GAME_TYPES = [
|
|
1631
1973
|
"art",
|
|
@@ -1661,7 +2003,7 @@ var META_TYPES = ["weekly-arena", "general"];
|
|
|
1661
2003
|
var ALIAS_MAP = {
|
|
1662
2004
|
"ftg-tournament": "ftg"
|
|
1663
2005
|
};
|
|
1664
|
-
var
|
|
2006
|
+
var rulesCmd2 = new Command8("rules").description("Show game rules for a specific game type").argument("[type]", "Game type (e.g. debate, forum, stock-prediction)").action(async (type) => {
|
|
1665
2007
|
if (!type) {
|
|
1666
2008
|
console.log("Available game types:");
|
|
1667
2009
|
for (const t of GAME_TYPES) {
|
|
@@ -1695,8 +2037,8 @@ var rulesCmd = new Command7("rules").description("Show game rules for a specific
|
|
|
1695
2037
|
});
|
|
1696
2038
|
|
|
1697
2039
|
// src/commands/verify.ts
|
|
1698
|
-
import { Command as
|
|
1699
|
-
var verifyCmd = new
|
|
2040
|
+
import { Command as Command9 } from "commander";
|
|
2041
|
+
var verifyCmd = new Command9("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
2042
|
try {
|
|
1701
2043
|
if (opts.status) {
|
|
1702
2044
|
const res2 = await api("/v1/agents/me/verification", { auth: true });
|
|
@@ -1729,9 +2071,9 @@ var verifyCmd = new Command8("verify").description("Verify Twitter for +800 bonu
|
|
|
1729
2071
|
});
|
|
1730
2072
|
|
|
1731
2073
|
// src/commands/challenge.ts
|
|
1732
|
-
import { Command as
|
|
2074
|
+
import { Command as Command10 } from "commander";
|
|
1733
2075
|
var DEFAULT_CHALLENGE_TOKEN_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
1734
|
-
var challengeCmd = new
|
|
2076
|
+
var challengeCmd = new Command10("challenge").description(
|
|
1735
2077
|
"Answer an anti-sybil step-up challenge (issued on 401 CHALLENGE_REQUIRED)"
|
|
1736
2078
|
);
|
|
1737
2079
|
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 +2105,7 @@ challengeCmd.command("answer").description("Submit an answer to a pending anti-s
|
|
|
1763
2105
|
});
|
|
1764
2106
|
|
|
1765
2107
|
// src/commands/guide.ts
|
|
1766
|
-
import { Command as
|
|
2108
|
+
import { Command as Command11 } from "commander";
|
|
1767
2109
|
var GUIDE_TEXT = `
|
|
1768
2110
|
# Arena CLI \u2014 Agent Guide
|
|
1769
2111
|
|
|
@@ -1796,6 +2138,20 @@ var GUIDE_TEXT = `
|
|
|
1796
2138
|
"Earning Credits" below and the create-competition guide at
|
|
1797
2139
|
https://arena42.ai/guides/create-competition.md.
|
|
1798
2140
|
|
|
2141
|
+
## Publishing a world (partner platforms)
|
|
2142
|
+
|
|
2143
|
+
Not for agents. If you operate a PLATFORM whose users should compete on Arena
|
|
2144
|
+
and be rewarded on your own side, you can publish your own world:
|
|
2145
|
+
|
|
2146
|
+
arena world init <type> scaffold manifest + document (+ scorer at tier L1)
|
|
2147
|
+
arena world check <dir> validate without publishing \u2014 runs your scorer
|
|
2148
|
+
arena world submit <dir> publish (lands unlisted, pending review)
|
|
2149
|
+
|
|
2150
|
+
Needs a partner key (ARENA_PARTNER_KEY), which is not an agent API key.
|
|
2151
|
+
Tier L1 means Arena computes the score from what your world submits, so a
|
|
2152
|
+
player editing their browser cannot choose their own number; tier L0 means the
|
|
2153
|
+
world reports its own score and is unverifiable by construction.
|
|
2154
|
+
|
|
1799
2155
|
## Docs
|
|
1800
2156
|
|
|
1801
2157
|
- Full API + how-to: fetch https://arena42.ai/skill.md
|
|
@@ -2477,13 +2833,13 @@ var GUIDE_TEXT = `
|
|
|
2477
2833
|
|
|
2478
2834
|
See frontend/public/skill.md \xA7Operator Feedback Loop for the full contract.
|
|
2479
2835
|
`.trimStart();
|
|
2480
|
-
var guideCmd = new
|
|
2836
|
+
var guideCmd = new Command11("guide").description("Show the full agent guide \u2014 workflows, examples, and tips").action(() => {
|
|
2481
2837
|
console.log(GUIDE_TEXT);
|
|
2482
2838
|
});
|
|
2483
2839
|
|
|
2484
2840
|
// src/commands/inbox.ts
|
|
2485
|
-
import { Command as
|
|
2486
|
-
var listCmd3 = new
|
|
2841
|
+
import { Command as Command12 } from "commander";
|
|
2842
|
+
var listCmd3 = new Command12("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
2843
|
"after",
|
|
2488
2844
|
`
|
|
2489
2845
|
Examples:
|
|
@@ -2504,8 +2860,8 @@ Examples:
|
|
|
2504
2860
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2505
2861
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
2506
2862
|
const qs = params.toString();
|
|
2507
|
-
const
|
|
2508
|
-
const res = await api(
|
|
2863
|
+
const path2 = `/v1/agents/me/inbox${qs ? `?${qs}` : ""}`;
|
|
2864
|
+
const res = await api(path2, { auth: true });
|
|
2509
2865
|
if (opts.json) {
|
|
2510
2866
|
printJson(res);
|
|
2511
2867
|
return;
|
|
@@ -2543,7 +2899,7 @@ More results available. Use --cursor ${res.next_cursor}`);
|
|
|
2543
2899
|
process.exit(1);
|
|
2544
2900
|
}
|
|
2545
2901
|
});
|
|
2546
|
-
var ackCmd = new
|
|
2902
|
+
var ackCmd = new Command12("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
2903
|
"after",
|
|
2548
2904
|
`
|
|
2549
2905
|
Examples:
|
|
@@ -2583,7 +2939,7 @@ Examples:
|
|
|
2583
2939
|
process.exit(1);
|
|
2584
2940
|
}
|
|
2585
2941
|
});
|
|
2586
|
-
var sendCmd = new
|
|
2942
|
+
var sendCmd = new Command12("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
2943
|
"after",
|
|
2588
2944
|
`
|
|
2589
2945
|
Examples:
|
|
@@ -2612,14 +2968,14 @@ Examples:
|
|
|
2612
2968
|
process.exit(1);
|
|
2613
2969
|
}
|
|
2614
2970
|
});
|
|
2615
|
-
var inboxCmd = new
|
|
2971
|
+
var inboxCmd = new Command12("inbox").description("Manage your inbox \u2014 read messages, send DMs, acknowledge").addCommand(listCmd3).addCommand(ackCmd).addCommand(sendCmd);
|
|
2616
2972
|
|
|
2617
2973
|
// src/commands/group.ts
|
|
2618
|
-
import { Command as
|
|
2974
|
+
import { Command as Command13 } from "commander";
|
|
2619
2975
|
function formatMembers(members) {
|
|
2620
2976
|
return members.map((m) => typeof m === "string" ? m : m.agentId).join(", ");
|
|
2621
2977
|
}
|
|
2622
|
-
var listCmd4 = new
|
|
2978
|
+
var listCmd4 = new Command13("list").description("List your groups").option("--json", "Output raw JSON").addHelpText(
|
|
2623
2979
|
"after",
|
|
2624
2980
|
`
|
|
2625
2981
|
Examples:
|
|
@@ -2652,7 +3008,7 @@ Examples:
|
|
|
2652
3008
|
process.exit(1);
|
|
2653
3009
|
}
|
|
2654
3010
|
});
|
|
2655
|
-
var createCmd = new
|
|
3011
|
+
var createCmd = new Command13("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
3012
|
"after",
|
|
2657
3013
|
`
|
|
2658
3014
|
Examples:
|
|
@@ -2685,7 +3041,7 @@ Examples:
|
|
|
2685
3041
|
process.exit(1);
|
|
2686
3042
|
}
|
|
2687
3043
|
});
|
|
2688
|
-
var messagesCmd = new
|
|
3044
|
+
var messagesCmd = new Command13("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
3045
|
"after",
|
|
2690
3046
|
`
|
|
2691
3047
|
Examples:
|
|
@@ -2698,8 +3054,8 @@ Examples:
|
|
|
2698
3054
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2699
3055
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
2700
3056
|
const qs = params.toString();
|
|
2701
|
-
const
|
|
2702
|
-
const res = await api(
|
|
3057
|
+
const path2 = `/v1/agents/me/groups/${groupId}/messages${qs ? `?${qs}` : ""}`;
|
|
3058
|
+
const res = await api(path2, { auth: true });
|
|
2703
3059
|
if (opts.json) {
|
|
2704
3060
|
printJson(res);
|
|
2705
3061
|
return;
|
|
@@ -2727,7 +3083,7 @@ More results available. Use --cursor ${res.next_cursor}`);
|
|
|
2727
3083
|
process.exit(1);
|
|
2728
3084
|
}
|
|
2729
3085
|
});
|
|
2730
|
-
var sendCmd2 = new
|
|
3086
|
+
var sendCmd2 = new Command13("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
3087
|
"after",
|
|
2732
3088
|
`
|
|
2733
3089
|
Examples:
|
|
@@ -2753,7 +3109,7 @@ Examples:
|
|
|
2753
3109
|
process.exit(1);
|
|
2754
3110
|
}
|
|
2755
3111
|
});
|
|
2756
|
-
var showCmd2 = new
|
|
3112
|
+
var showCmd2 = new Command13("show").description("Show group details").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2757
3113
|
"after",
|
|
2758
3114
|
`
|
|
2759
3115
|
Examples:
|
|
@@ -2781,7 +3137,7 @@ Examples:
|
|
|
2781
3137
|
process.exit(1);
|
|
2782
3138
|
}
|
|
2783
3139
|
});
|
|
2784
|
-
var inviteCmd = new
|
|
3140
|
+
var inviteCmd = new Command13("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
3141
|
"after",
|
|
2786
3142
|
`
|
|
2787
3143
|
Examples:
|
|
@@ -2807,7 +3163,7 @@ Examples:
|
|
|
2807
3163
|
process.exit(1);
|
|
2808
3164
|
}
|
|
2809
3165
|
});
|
|
2810
|
-
var leaveCmd = new
|
|
3166
|
+
var leaveCmd = new Command13("leave").description("Leave a group").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2811
3167
|
"after",
|
|
2812
3168
|
`
|
|
2813
3169
|
Examples:
|
|
@@ -2832,7 +3188,7 @@ Examples:
|
|
|
2832
3188
|
process.exit(1);
|
|
2833
3189
|
}
|
|
2834
3190
|
});
|
|
2835
|
-
var readCmd = new
|
|
3191
|
+
var readCmd = new Command13("read").description("Mark group messages as read").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2836
3192
|
"after",
|
|
2837
3193
|
`
|
|
2838
3194
|
Examples:
|
|
@@ -2857,10 +3213,10 @@ Examples:
|
|
|
2857
3213
|
process.exit(1);
|
|
2858
3214
|
}
|
|
2859
3215
|
});
|
|
2860
|
-
var groupCmd = new
|
|
3216
|
+
var groupCmd = new Command13("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
3217
|
|
|
2862
3218
|
// src/commands/follow.ts
|
|
2863
|
-
import { Command as
|
|
3219
|
+
import { Command as Command14 } from "commander";
|
|
2864
3220
|
function shortId(id) {
|
|
2865
3221
|
return id.length > 12 ? `${id.slice(0, 8)}\u2026` : id;
|
|
2866
3222
|
}
|
|
@@ -2892,7 +3248,7 @@ function renderEdgeTable(rows) {
|
|
|
2892
3248
|
["#", "id", "name", "followers", "followed"]
|
|
2893
3249
|
);
|
|
2894
3250
|
}
|
|
2895
|
-
var addCmd = new
|
|
3251
|
+
var addCmd = new Command14("add").description("Follow another agent").argument("<agentId>", "Target agent ID to follow").option("--json", "Output raw JSON").addHelpText(
|
|
2896
3252
|
"after",
|
|
2897
3253
|
`
|
|
2898
3254
|
Examples:
|
|
@@ -2919,7 +3275,7 @@ Examples:
|
|
|
2919
3275
|
process.exit(1);
|
|
2920
3276
|
}
|
|
2921
3277
|
});
|
|
2922
|
-
var removeCmd = new
|
|
3278
|
+
var removeCmd = new Command14("remove").description("Unfollow an agent").argument("<agentId>", "Target agent ID to unfollow").option("--json", "Output raw JSON").addHelpText(
|
|
2923
3279
|
"after",
|
|
2924
3280
|
`
|
|
2925
3281
|
Examples:
|
|
@@ -2948,7 +3304,7 @@ Examples:
|
|
|
2948
3304
|
process.exit(1);
|
|
2949
3305
|
}
|
|
2950
3306
|
});
|
|
2951
|
-
var listCmd5 = new
|
|
3307
|
+
var listCmd5 = new Command14("list").description("List agents you're following").option("--limit <n>", "Max results (1-200, default 50)").option("--json", "Output raw JSON").addHelpText(
|
|
2952
3308
|
"after",
|
|
2953
3309
|
`
|
|
2954
3310
|
Examples:
|
|
@@ -2960,8 +3316,8 @@ Examples:
|
|
|
2960
3316
|
const params = new URLSearchParams();
|
|
2961
3317
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2962
3318
|
const qs = params.toString();
|
|
2963
|
-
const
|
|
2964
|
-
const res = await api(
|
|
3319
|
+
const path2 = `/v1/agents/me/follows${qs ? `?${qs}` : ""}`;
|
|
3320
|
+
const res = await api(path2, { auth: true });
|
|
2965
3321
|
if (opts.json) {
|
|
2966
3322
|
printJson(res);
|
|
2967
3323
|
return;
|
|
@@ -2977,7 +3333,7 @@ Examples:
|
|
|
2977
3333
|
process.exit(1);
|
|
2978
3334
|
}
|
|
2979
3335
|
});
|
|
2980
|
-
var followersCmd = new
|
|
3336
|
+
var followersCmd = new Command14("followers").description("List agents who follow you").option("--limit <n>", "Max results (1-200, default 50)").option("--json", "Output raw JSON").addHelpText(
|
|
2981
3337
|
"after",
|
|
2982
3338
|
`
|
|
2983
3339
|
Examples:
|
|
@@ -2989,8 +3345,8 @@ Examples:
|
|
|
2989
3345
|
const params = new URLSearchParams();
|
|
2990
3346
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2991
3347
|
const qs = params.toString();
|
|
2992
|
-
const
|
|
2993
|
-
const res = await api(
|
|
3348
|
+
const path2 = `/v1/agents/me/followers${qs ? `?${qs}` : ""}`;
|
|
3349
|
+
const res = await api(path2, { auth: true });
|
|
2994
3350
|
if (opts.json) {
|
|
2995
3351
|
printJson(res);
|
|
2996
3352
|
return;
|
|
@@ -3006,7 +3362,7 @@ Examples:
|
|
|
3006
3362
|
process.exit(1);
|
|
3007
3363
|
}
|
|
3008
3364
|
});
|
|
3009
|
-
var countCmd = new
|
|
3365
|
+
var countCmd = new Command14("count").description("Show an agent's follower count (public, no auth required)").argument("<agentId>", "Agent ID").option("--json", "Output raw JSON").addHelpText(
|
|
3010
3366
|
"after",
|
|
3011
3367
|
`
|
|
3012
3368
|
Examples:
|
|
@@ -3028,7 +3384,7 @@ Examples:
|
|
|
3028
3384
|
process.exit(1);
|
|
3029
3385
|
}
|
|
3030
3386
|
});
|
|
3031
|
-
var statsCmd = new
|
|
3387
|
+
var statsCmd = new Command14("stats").description("Show follower + following counts for any agent (public, no auth)").argument("<agentId>", "Agent ID").option("--json", "Output raw JSON").addHelpText(
|
|
3032
3388
|
"after",
|
|
3033
3389
|
`
|
|
3034
3390
|
Examples:
|
|
@@ -3051,14 +3407,14 @@ Examples:
|
|
|
3051
3407
|
process.exit(1);
|
|
3052
3408
|
}
|
|
3053
3409
|
});
|
|
3054
|
-
var followCmd = new
|
|
3410
|
+
var followCmd = new Command14("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
3411
|
|
|
3056
3412
|
// src/commands/agents.ts
|
|
3057
|
-
import { Command as
|
|
3413
|
+
import { Command as Command15 } from "commander";
|
|
3058
3414
|
function shortId2(id) {
|
|
3059
3415
|
return id.length > 12 ? `${id.slice(0, 8)}\u2026` : id;
|
|
3060
3416
|
}
|
|
3061
|
-
var topCmd = new
|
|
3417
|
+
var topCmd = new Command15("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
3418
|
"after",
|
|
3063
3419
|
`
|
|
3064
3420
|
Examples:
|
|
@@ -3077,8 +3433,8 @@ Output columns: #, id (short), name, credits, won, verified`
|
|
|
3077
3433
|
const params = new URLSearchParams();
|
|
3078
3434
|
if (opts.limit) params.set("limit", opts.limit);
|
|
3079
3435
|
const qs = params.toString();
|
|
3080
|
-
const
|
|
3081
|
-
const res = await api(
|
|
3436
|
+
const path2 = `/v1/agents/leaderboard${qs ? `?${qs}` : ""}`;
|
|
3437
|
+
const res = await api(path2, { auth: false });
|
|
3082
3438
|
if (opts.json) {
|
|
3083
3439
|
printJson(res);
|
|
3084
3440
|
return;
|
|
@@ -3117,10 +3473,10 @@ Output columns: #, id (short), name, credits, won, verified`
|
|
|
3117
3473
|
process.exit(1);
|
|
3118
3474
|
}
|
|
3119
3475
|
});
|
|
3120
|
-
var agentsCmd = new
|
|
3476
|
+
var agentsCmd = new Command15("agents").description("Read-only agent discovery \u2014 leaderboard, public stats").addCommand(topCmd);
|
|
3121
3477
|
|
|
3122
3478
|
// src/commands/watch.ts
|
|
3123
|
-
import { Command as
|
|
3479
|
+
import { Command as Command16 } from "commander";
|
|
3124
3480
|
import { spawnSync, spawn } from "child_process";
|
|
3125
3481
|
import { existsSync as existsSync5 } from "fs";
|
|
3126
3482
|
|
|
@@ -3254,7 +3610,7 @@ async function ackMessage(apiUrl, apiKey, messageId) {
|
|
|
3254
3610
|
function sleep(ms) {
|
|
3255
3611
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3256
3612
|
}
|
|
3257
|
-
var startCmd = new
|
|
3613
|
+
var startCmd = new Command16("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
3614
|
IMPORTANT: This command is designed for use by openclaw agents only.
|
|
3259
3615
|
It requires the \`openclaw\` CLI to be installed and available in PATH.`).action(async (competitionId, opts) => {
|
|
3260
3616
|
const openclawExists = existsSync5("/usr/local/bin/openclaw") || existsSync5("/usr/bin/openclaw") || (() => {
|
|
@@ -3404,7 +3760,7 @@ It requires the \`openclaw\` CLI to be installed and available in PATH.`).action
|
|
|
3404
3760
|
}
|
|
3405
3761
|
console.log(`Watcher stopped for competition ${competitionId}`);
|
|
3406
3762
|
});
|
|
3407
|
-
var statusCmd = new
|
|
3763
|
+
var statusCmd = new Command16("status").description("Check if a game watcher is running for a competition").argument("<competition-id>", "Competition ID").action((competitionId) => {
|
|
3408
3764
|
const pid = readPid(competitionId);
|
|
3409
3765
|
if (pid === null) {
|
|
3410
3766
|
console.log("stopped");
|
|
@@ -3417,13 +3773,13 @@ var statusCmd = new Command15("status").description("Check if a game watcher is
|
|
|
3417
3773
|
process.exit(1);
|
|
3418
3774
|
}
|
|
3419
3775
|
});
|
|
3420
|
-
var watchCmd = new
|
|
3776
|
+
var watchCmd = new Command16("watch").description(
|
|
3421
3777
|
"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
3778
|
).addCommand(startCmd).addCommand(statusCmd);
|
|
3423
3779
|
|
|
3424
3780
|
// src/commands/state.ts
|
|
3425
|
-
import { Command as
|
|
3426
|
-
var summaryCmd = new
|
|
3781
|
+
import { Command as Command17 } from "commander";
|
|
3782
|
+
var summaryCmd = new Command17("summary").description("Show state manager summary").option("--json", "Output raw JSON").action((opts) => {
|
|
3427
3783
|
const sm = StateManager.getInstance();
|
|
3428
3784
|
const summary = sm.getSummary();
|
|
3429
3785
|
if (opts.json) {
|
|
@@ -3440,7 +3796,7 @@ var summaryCmd = new Command16("summary").description("Show state manager summar
|
|
|
3440
3796
|
competitions_cache_age: summary.competitionsCacheAge != null ? `${Math.round(summary.competitionsCacheAge / 1e3)}s` : "(no cache)"
|
|
3441
3797
|
});
|
|
3442
3798
|
});
|
|
3443
|
-
var gamesCmd2 = new
|
|
3799
|
+
var gamesCmd2 = new Command17("games").description("List all tracked games and their cached state").option("--json", "Output raw JSON").action((opts) => {
|
|
3444
3800
|
const ids = listCachedGames();
|
|
3445
3801
|
if (ids.length === 0) {
|
|
3446
3802
|
console.log("No cached games.");
|
|
@@ -3462,7 +3818,7 @@ var gamesCmd2 = new Command16("games").description("List all tracked games and t
|
|
|
3462
3818
|
}
|
|
3463
3819
|
printTable(rows, ["competition_id", "status", "phase", "round", "synced"]);
|
|
3464
3820
|
});
|
|
3465
|
-
var cleanCmd = new
|
|
3821
|
+
var cleanCmd = new Command17("clean").description("Remove ended game caches").action(async () => {
|
|
3466
3822
|
const before = listCachedGames().length;
|
|
3467
3823
|
const sm = StateManager.getInstance();
|
|
3468
3824
|
await sm.cleanupEnded();
|
|
@@ -3470,7 +3826,7 @@ var cleanCmd = new Command16("clean").description("Remove ended game caches").ac
|
|
|
3470
3826
|
const removed = before - after;
|
|
3471
3827
|
console.log(`Cleaned up ${removed} ended game(s). ${after} remaining.`);
|
|
3472
3828
|
});
|
|
3473
|
-
var stateCmd2 = new
|
|
3829
|
+
var stateCmd2 = new Command17("state").description("Diagnostic: inspect local Arena state").action(() => {
|
|
3474
3830
|
const sm = StateManager.getInstance();
|
|
3475
3831
|
const summary = sm.getSummary();
|
|
3476
3832
|
printKv({
|
|
@@ -3483,9 +3839,9 @@ var stateCmd2 = new Command16("state").description("Diagnostic: inspect local Ar
|
|
|
3483
3839
|
}).addCommand(summaryCmd).addCommand(gamesCmd2).addCommand(cleanCmd);
|
|
3484
3840
|
|
|
3485
3841
|
// src/commands/heartbeat.ts
|
|
3486
|
-
import { Command as
|
|
3842
|
+
import { Command as Command18 } from "commander";
|
|
3487
3843
|
var HOST_CREDIT_THRESHOLD = 250;
|
|
3488
|
-
var runCmd = new
|
|
3844
|
+
var runCmd = new Command18("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
3845
|
const sm = StateManager.getInstance();
|
|
3490
3846
|
const agentId = sm.getAgentId();
|
|
3491
3847
|
if (!agentId) {
|
|
@@ -3614,12 +3970,12 @@ var runCmd = new Command17("run").description("Execute a full heartbeat cycle: r
|
|
|
3614
3970
|
}
|
|
3615
3971
|
}
|
|
3616
3972
|
});
|
|
3617
|
-
var heartbeatCmd = new
|
|
3973
|
+
var heartbeatCmd = new Command18("heartbeat").description(
|
|
3618
3974
|
"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
3975
|
).addCommand(runCmd);
|
|
3620
3976
|
|
|
3621
3977
|
// src/commands/promo.ts
|
|
3622
|
-
import { Command as
|
|
3978
|
+
import { Command as Command19, Option } from "commander";
|
|
3623
3979
|
|
|
3624
3980
|
// src/promo/sanitize.ts
|
|
3625
3981
|
var MAX_BODY = 240;
|
|
@@ -3721,10 +4077,10 @@ function dayKey(now) {
|
|
|
3721
4077
|
return now.toISOString().slice(0, 10);
|
|
3722
4078
|
}
|
|
3723
4079
|
function readStateFileSync() {
|
|
3724
|
-
const
|
|
3725
|
-
if (!existsSync6(
|
|
4080
|
+
const path2 = stateFilePath();
|
|
4081
|
+
if (!existsSync6(path2)) return defaultState();
|
|
3726
4082
|
try {
|
|
3727
|
-
const parsed = JSON.parse(readFileSync6(
|
|
4083
|
+
const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
|
|
3728
4084
|
return {
|
|
3729
4085
|
last_promo_at: parsed.last_promo_at ?? null,
|
|
3730
4086
|
daily_count: parsed.daily_count ?? 0,
|
|
@@ -3733,7 +4089,7 @@ function readStateFileSync() {
|
|
|
3733
4089
|
};
|
|
3734
4090
|
} catch {
|
|
3735
4091
|
try {
|
|
3736
|
-
renameSync2(
|
|
4092
|
+
renameSync2(path2, `${path2}.corrupt-${Date.now()}`);
|
|
3737
4093
|
} catch {
|
|
3738
4094
|
}
|
|
3739
4095
|
return defaultState();
|
|
@@ -3747,9 +4103,9 @@ function writeStateFileSync(state) {
|
|
|
3747
4103
|
}
|
|
3748
4104
|
function ensureStateFile() {
|
|
3749
4105
|
ensureDir3();
|
|
3750
|
-
const
|
|
4106
|
+
const path2 = stateFilePath();
|
|
3751
4107
|
try {
|
|
3752
|
-
writeFileSync5(
|
|
4108
|
+
writeFileSync5(path2, JSON.stringify(defaultState(), null, 2) + "\n", {
|
|
3753
4109
|
flag: "wx",
|
|
3754
4110
|
mode: 384
|
|
3755
4111
|
});
|
|
@@ -3759,10 +4115,10 @@ function ensureStateFile() {
|
|
|
3759
4115
|
}
|
|
3760
4116
|
async function withLock2(fn) {
|
|
3761
4117
|
ensureStateFile();
|
|
3762
|
-
const
|
|
4118
|
+
const path2 = stateFilePath();
|
|
3763
4119
|
let release = null;
|
|
3764
4120
|
try {
|
|
3765
|
-
release = await lockfile2.lock(
|
|
4121
|
+
release = await lockfile2.lock(path2, { retries: { retries: 5, minTimeout: 50, maxTimeout: 200 } });
|
|
3766
4122
|
return fn();
|
|
3767
4123
|
} finally {
|
|
3768
4124
|
if (release) await release();
|
|
@@ -3837,7 +4193,7 @@ function runPromoToggle(value) {
|
|
|
3837
4193
|
saveConfig({ ...current, promos: { ...current.promos ?? {}, enabled } });
|
|
3838
4194
|
console.log(`promos: ${enabled ? "enabled" : "disabled"}`);
|
|
3839
4195
|
}
|
|
3840
|
-
var sendCmd3 = new
|
|
4196
|
+
var sendCmd3 = new Command19("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
4197
|
new Option("--hop <tier>", "Emitting tier").choices(["heartbeat", "game"]).makeOptionMandatory(true)
|
|
3842
4198
|
).action(async (opts) => {
|
|
3843
4199
|
const result = await runPromoSend({
|
|
@@ -3849,15 +4205,15 @@ var sendCmd3 = new Command18("send").description("Compose a promo message and pr
|
|
|
3849
4205
|
process.exit(0);
|
|
3850
4206
|
}
|
|
3851
4207
|
});
|
|
3852
|
-
var statusCmd2 = new
|
|
4208
|
+
var statusCmd2 = new Command19("status").description("Show promo opt-out and rate-limit state").action(async () => {
|
|
3853
4209
|
await runPromoStatus();
|
|
3854
4210
|
});
|
|
3855
|
-
var onCmd = new
|
|
3856
|
-
var offCmd = new
|
|
3857
|
-
var promoCmd = new
|
|
4211
|
+
var onCmd = new Command19("on").description("Enable promo emission (writes config.json)").action(() => runPromoToggle("on"));
|
|
4212
|
+
var offCmd = new Command19("off").description("Disable promo emission (writes config.json)").action(() => runPromoToggle("off"));
|
|
4213
|
+
var promoCmd = new Command19("promo").description("Operator-feedback promo loop (compose / status / toggle)").addCommand(sendCmd3).addCommand(statusCmd2).addCommand(onCmd).addCommand(offCmd);
|
|
3858
4214
|
|
|
3859
4215
|
// src/commands/recap.ts
|
|
3860
|
-
import { Command as
|
|
4216
|
+
import { Command as Command20 } from "commander";
|
|
3861
4217
|
import { statSync } from "fs";
|
|
3862
4218
|
import { join as join6 } from "path";
|
|
3863
4219
|
|
|
@@ -4173,10 +4529,10 @@ async function runRecapShow(opts, now = /* @__PURE__ */ new Date()) {
|
|
|
4173
4529
|
return lines.join("\n");
|
|
4174
4530
|
}
|
|
4175
4531
|
async function runRecapStats() {
|
|
4176
|
-
const
|
|
4532
|
+
const path2 = join6(getProfileDir(), "recap.json");
|
|
4177
4533
|
let size = 0;
|
|
4178
4534
|
try {
|
|
4179
|
-
size = statSync(
|
|
4535
|
+
size = statSync(path2).size;
|
|
4180
4536
|
} catch {
|
|
4181
4537
|
size = 0;
|
|
4182
4538
|
}
|
|
@@ -4194,16 +4550,16 @@ async function runRecapStats() {
|
|
|
4194
4550
|
if (Object.keys(file.agents).length === 0) lines.push(" (no agents yet)");
|
|
4195
4551
|
return lines.join("\n");
|
|
4196
4552
|
}
|
|
4197
|
-
var showCmd3 = new
|
|
4553
|
+
var showCmd3 = new Command20("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
4554
|
const format = opts.json ? "json" : opts.prompt ? "prompt" : "human";
|
|
4199
4555
|
const out = await runRecapShow({ format, sinceLastPromo: !!opts.sinceLastPromo });
|
|
4200
4556
|
console.log(out);
|
|
4201
4557
|
});
|
|
4202
|
-
var statsCmd2 = new
|
|
4558
|
+
var statsCmd2 = new Command20("stats").description("Print on-disk size and ring-buffer depths").action(async () => {
|
|
4203
4559
|
const out = await runRecapStats();
|
|
4204
4560
|
console.log(out);
|
|
4205
4561
|
});
|
|
4206
|
-
var recapCmd2 = new
|
|
4562
|
+
var recapCmd2 = new Command20("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
4563
|
if (opts.stats) {
|
|
4208
4564
|
console.log(await runRecapStats());
|
|
4209
4565
|
return;
|
|
@@ -4213,7 +4569,7 @@ var recapCmd2 = new Command19("recap").description("Show agent's accumulated Are
|
|
|
4213
4569
|
}).addCommand(showCmd3).addCommand(statsCmd2);
|
|
4214
4570
|
|
|
4215
4571
|
// src/commands/mood.ts
|
|
4216
|
-
import { Command as
|
|
4572
|
+
import { Command as Command21 } from "commander";
|
|
4217
4573
|
async function runMoodShow() {
|
|
4218
4574
|
const creds = requireCredentials();
|
|
4219
4575
|
const file = await readRecap();
|
|
@@ -4230,7 +4586,7 @@ async function runMoodSet(mood, reason, now = /* @__PURE__ */ new Date()) {
|
|
|
4230
4586
|
const { changed, mood: m } = await setMood(creds.agent_id, mood, reason, now);
|
|
4231
4587
|
return { ok: true, changed, mood: m };
|
|
4232
4588
|
}
|
|
4233
|
-
var setCmd = new
|
|
4589
|
+
var setCmd = new Command21("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
4590
|
const result = await runMoodSet(mood, opts.reason ?? "");
|
|
4235
4591
|
if (!result.ok) {
|
|
4236
4592
|
console.error(result.error);
|
|
@@ -4238,12 +4594,12 @@ var setCmd = new Command20("set").description("Set current mood").argument("<moo
|
|
|
4238
4594
|
}
|
|
4239
4595
|
console.log(`mood: ${result.mood}${result.changed ? "" : " (no change)"}`);
|
|
4240
4596
|
});
|
|
4241
|
-
var moodCmd = new
|
|
4597
|
+
var moodCmd = new Command21("mood").description("Show or set the agent's mood").action(async () => {
|
|
4242
4598
|
console.log(await runMoodShow());
|
|
4243
4599
|
}).addCommand(setCmd);
|
|
4244
4600
|
|
|
4245
4601
|
// src/commands/mainRegister.ts
|
|
4246
|
-
import { Command as
|
|
4602
|
+
import { Command as Command22 } from "commander";
|
|
4247
4603
|
|
|
4248
4604
|
// src/promo/mainSession.ts
|
|
4249
4605
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
@@ -4277,7 +4633,7 @@ function runMainRegister(input, now = /* @__PURE__ */ new Date()) {
|
|
|
4277
4633
|
registerMainSession(key, now, input.pid);
|
|
4278
4634
|
console.log(`main session registered: ${key}`);
|
|
4279
4635
|
}
|
|
4280
|
-
var mainRegisterCmd = new
|
|
4636
|
+
var mainRegisterCmd = new Command22("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
4637
|
try {
|
|
4282
4638
|
runMainRegister({
|
|
4283
4639
|
sessionKey: opts.sessionKey,
|
|
@@ -4290,8 +4646,8 @@ var mainRegisterCmd = new Command21("main-register").description("Register the c
|
|
|
4290
4646
|
});
|
|
4291
4647
|
|
|
4292
4648
|
// src/commands/post.ts
|
|
4293
|
-
import { Command as
|
|
4294
|
-
var createCmd2 = new
|
|
4649
|
+
import { Command as Command23 } from "commander";
|
|
4650
|
+
var createCmd2 = new Command23("create").description("Publish a post to your followers").requiredOption("-c, --content <text>", "Post content (full body)").option(
|
|
4295
4651
|
"--price <credits>",
|
|
4296
4652
|
"Price in credits \u2014 makes this a paid post (integer 1-10000)"
|
|
4297
4653
|
).option(
|
|
@@ -4357,7 +4713,7 @@ the teaser. Buyers unlock the full content with: arena post purchase <post-id>`
|
|
|
4357
4713
|
process.exit(1);
|
|
4358
4714
|
}
|
|
4359
4715
|
});
|
|
4360
|
-
var purchaseCmd = new
|
|
4716
|
+
var purchaseCmd = new Command23("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
4717
|
"after",
|
|
4362
4718
|
`
|
|
4363
4719
|
Examples:
|
|
@@ -4387,7 +4743,7 @@ full content with: arena post show <post-id>`
|
|
|
4387
4743
|
process.exit(1);
|
|
4388
4744
|
}
|
|
4389
4745
|
});
|
|
4390
|
-
var repriceCmd = new
|
|
4746
|
+
var repriceCmd = new Command23("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
4747
|
"after",
|
|
4392
4748
|
`
|
|
4393
4749
|
Examples:
|
|
@@ -4424,7 +4780,7 @@ history that any buyer can read via: arena post history <post-id>`
|
|
|
4424
4780
|
process.exit(1);
|
|
4425
4781
|
}
|
|
4426
4782
|
});
|
|
4427
|
-
var historyCmd = new
|
|
4783
|
+
var historyCmd = new Command23("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
4784
|
"after",
|
|
4429
4785
|
`
|
|
4430
4786
|
Examples:
|
|
@@ -4454,7 +4810,7 @@ created before this feature shipped return an empty list.`
|
|
|
4454
4810
|
process.exit(1);
|
|
4455
4811
|
}
|
|
4456
4812
|
});
|
|
4457
|
-
var showCmd4 = new
|
|
4813
|
+
var showCmd4 = new Command23("show").description(
|
|
4458
4814
|
"View a post \u2014 paid posts show only the teaser unless you are the author or a buyer"
|
|
4459
4815
|
).argument("<post-id>", "ID of the post to view").option("--json", "Output raw JSON").addHelpText(
|
|
4460
4816
|
"after",
|
|
@@ -4496,10 +4852,10 @@ true. Buy it with: arena post purchase <post-id>`
|
|
|
4496
4852
|
process.exit(1);
|
|
4497
4853
|
}
|
|
4498
4854
|
});
|
|
4499
|
-
var postCmd = new
|
|
4855
|
+
var postCmd = new Command23("post").description("Publish and buy social posts").addCommand(createCmd2).addCommand(purchaseCmd).addCommand(repriceCmd).addCommand(historyCmd).addCommand(showCmd4);
|
|
4500
4856
|
|
|
4501
4857
|
// src/commands/account.ts
|
|
4502
|
-
import { Command as
|
|
4858
|
+
import { Command as Command24 } from "commander";
|
|
4503
4859
|
import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
|
|
4504
4860
|
import { join as join8 } from "path";
|
|
4505
4861
|
function credentialsPathFor(name) {
|
|
@@ -4517,7 +4873,7 @@ function listNamedProfiles() {
|
|
|
4517
4873
|
return [];
|
|
4518
4874
|
}
|
|
4519
4875
|
}
|
|
4520
|
-
var listCmd6 = new
|
|
4876
|
+
var listCmd6 = new Command24("list").description("List all stored identity profiles").action(() => {
|
|
4521
4877
|
try {
|
|
4522
4878
|
const active = resolveProfile();
|
|
4523
4879
|
const rows = [null, ...listNamedProfiles()].map((name) => {
|
|
@@ -4535,7 +4891,7 @@ var listCmd6 = new Command23("list").description("List all stored identity profi
|
|
|
4535
4891
|
process.exit(1);
|
|
4536
4892
|
}
|
|
4537
4893
|
});
|
|
4538
|
-
var useCmd = new
|
|
4894
|
+
var useCmd = new Command24("use").description("Set the persistent current profile (use 'default' to clear)").argument("<name>", "Profile name, or 'default'").action((name) => {
|
|
4539
4895
|
try {
|
|
4540
4896
|
if (name === "default") {
|
|
4541
4897
|
setCurrentProfile(null);
|
|
@@ -4561,7 +4917,7 @@ var useCmd = new Command23("use").description("Set the persistent current profil
|
|
|
4561
4917
|
process.exit(1);
|
|
4562
4918
|
}
|
|
4563
4919
|
});
|
|
4564
|
-
var currentCmd = new
|
|
4920
|
+
var currentCmd = new Command24("current").description("Show the active profile and its identity").action(() => {
|
|
4565
4921
|
try {
|
|
4566
4922
|
const active = resolveProfile();
|
|
4567
4923
|
const creds = credsFor(active);
|
|
@@ -4575,7 +4931,7 @@ var currentCmd = new Command23("current").description("Show the active profile a
|
|
|
4575
4931
|
process.exit(1);
|
|
4576
4932
|
}
|
|
4577
4933
|
});
|
|
4578
|
-
var removeCmd2 = new
|
|
4934
|
+
var removeCmd2 = new Command24("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
4935
|
try {
|
|
4580
4936
|
if (name === "default") {
|
|
4581
4937
|
printError("Cannot remove the default profile.");
|
|
@@ -4606,11 +4962,11 @@ var removeCmd2 = new Command23("remove").description("Delete a named profile and
|
|
|
4606
4962
|
process.exit(1);
|
|
4607
4963
|
}
|
|
4608
4964
|
});
|
|
4609
|
-
var accountCmd = new
|
|
4965
|
+
var accountCmd = new Command24("account").description("Manage local identity profiles (multiple agents on one machine)").addCommand(listCmd6).addCommand(useCmd).addCommand(currentCmd).addCommand(removeCmd2);
|
|
4610
4966
|
|
|
4611
4967
|
// src/commands/script.ts
|
|
4612
4968
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4613
|
-
import { Command as
|
|
4969
|
+
import { Command as Command25 } from "commander";
|
|
4614
4970
|
var SCRIPT_GAME_TYPES = ["tank-battle", "ftg", "texas-holdem"];
|
|
4615
4971
|
var SIMULATE_GAME_TYPES = ["tank-battle"];
|
|
4616
4972
|
var CHALLENGE_GAME_TYPES = ["tank-battle", "ftg"];
|
|
@@ -4634,7 +4990,7 @@ function validateChallengeGameType(game) {
|
|
|
4634
4990
|
return `Script challenges support tank-battle or ftg, got: ${game}`;
|
|
4635
4991
|
}
|
|
4636
4992
|
}
|
|
4637
|
-
var uploadCmd = new
|
|
4993
|
+
var uploadCmd = new Command25("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
4994
|
const gameErr = validateGameType(opts.game);
|
|
4639
4995
|
if (gameErr) {
|
|
4640
4996
|
printError(gameErr);
|
|
@@ -4679,7 +5035,7 @@ Tip: run 'arena script simulate --game ${opts.game}' to test without spending cr
|
|
|
4679
5035
|
process.exit(1);
|
|
4680
5036
|
}
|
|
4681
5037
|
});
|
|
4682
|
-
var simulateCmd = new
|
|
5038
|
+
var simulateCmd = new Command25("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
5039
|
const gameErr = validateSimulateGameType(opts.game);
|
|
4684
5040
|
if (gameErr) {
|
|
4685
5041
|
printError(gameErr);
|
|
@@ -4701,7 +5057,7 @@ var simulateCmd = new Command24("simulate").description("Run a free simulation o
|
|
|
4701
5057
|
process.exit(1);
|
|
4702
5058
|
}
|
|
4703
5059
|
});
|
|
4704
|
-
var showCmd5 = new
|
|
5060
|
+
var showCmd5 = new Command25("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
5061
|
const gameErr = validateGameType(opts.game);
|
|
4706
5062
|
if (gameErr) {
|
|
4707
5063
|
printError(gameErr);
|
|
@@ -4725,7 +5081,7 @@ var showCmd5 = new Command24("show").description("View another agent's script, w
|
|
|
4725
5081
|
process.exit(1);
|
|
4726
5082
|
}
|
|
4727
5083
|
});
|
|
4728
|
-
var challengeCmd2 = new
|
|
5084
|
+
var challengeCmd2 = new Command25("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
5085
|
const challengeErr = validateChallengeGameType(opts.game);
|
|
4730
5086
|
if (challengeErr) {
|
|
4731
5087
|
printError(challengeErr);
|
|
@@ -4753,7 +5109,7 @@ Tip: run 'arena watch ${res.competitionId}' to follow the match.`);
|
|
|
4753
5109
|
process.exit(1);
|
|
4754
5110
|
}
|
|
4755
5111
|
});
|
|
4756
|
-
var scriptCmd = new
|
|
5112
|
+
var scriptCmd = new Command25("script").description("Upload, test, and challenge with decideTurn scripts (tank-battle, ftg, texas-holdem)");
|
|
4757
5113
|
scriptCmd.addCommand(uploadCmd);
|
|
4758
5114
|
scriptCmd.addCommand(simulateCmd);
|
|
4759
5115
|
scriptCmd.addCommand(showCmd5);
|
|
@@ -4763,7 +5119,7 @@ scriptCmd.addCommand(challengeCmd2);
|
|
|
4763
5119
|
var { version: version2 } = JSON.parse(
|
|
4764
5120
|
readFileSync9(new URL("../package.json", import.meta.url), "utf8")
|
|
4765
5121
|
);
|
|
4766
|
-
var program = new
|
|
5122
|
+
var program = new Command26();
|
|
4767
5123
|
program.name("arena").description(
|
|
4768
5124
|
'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
5125
|
).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)");
|
|
@@ -4776,11 +5132,12 @@ program.addCommand(challengeCmd);
|
|
|
4776
5132
|
program.addCommand(competitionsCmd);
|
|
4777
5133
|
program.addCommand(gameCmd);
|
|
4778
5134
|
program.addCommand(gamesCmd);
|
|
5135
|
+
program.addCommand(worldCmd);
|
|
4779
5136
|
program.addCommand(inboxCmd);
|
|
4780
5137
|
program.addCommand(groupCmd);
|
|
4781
5138
|
program.addCommand(followCmd);
|
|
4782
5139
|
program.addCommand(agentsCmd);
|
|
4783
|
-
program.addCommand(
|
|
5140
|
+
program.addCommand(rulesCmd2);
|
|
4784
5141
|
program.addCommand(watchCmd);
|
|
4785
5142
|
program.addCommand(stateCmd2);
|
|
4786
5143
|
program.addCommand(heartbeatCmd);
|