Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
83fc398
chore: open the v0.1.3 cycle — declare 0.1.3
Kevin-Chen0 Oct 5, 2026
a830a09
ci: keep Dependabot pull requests on the newest release branch
Kevin-Chen0 Oct 6, 2026
af557f4
ci: note that a moved pull request needs its checks re-run, and toler…
Kevin-Chen0 Oct 6, 2026
7c8291e
earn: Ethereum and Monad as two more chains, with Cash Plus USDC (Tes…
Kevin-Chen0 Oct 11, 2026
655c074
earn and connect on Solana, with Cash Plus USDC (Test 2G), a Kamino v…
Kevin-Chen0 Oct 11, 2026
6e378e3
solana: review fixes — CI flag set, Kamino's charges and powers discl…
Kevin-Chen0 Oct 11, 2026
a5da8e9
docs: the Solana withdrawal charge is per instruction in tools.md; th…
Kevin-Chen0 Oct 11, 2026
55c1c39
Merge pull request #123 from TemporaLabs/ci/retarget-dependabot
Kevin-Chen0 Oct 11, 2026
8d4cbdb
Merge pull request #126 from TemporaLabs/feat/ethereum-monad
Kevin-Chen0 Oct 11, 2026
3142a81
tests: fork round trip on every depositable EVM vault
Kevin-Chen0 Oct 11, 2026
bfc460f
tests: Solana fork round trip on a local mainnet fork
Kevin-Chen0 Oct 11, 2026
35b7fa3
build: rebuild the CLI bundle for the new fork fixtures
Kevin-Chen0 Oct 11, 2026
24e1194
Merge pull request #128 from TemporaLabs/test/fork-every-evm-vault
Kevin-Chen0 Oct 11, 2026
9e57f9e
Merge release/v0.1.3 into feat/solana-kamino
Kevin-Chen0 Oct 11, 2026
606aa4f
tests: the Solana fork deposit may take a few units less than asked
Kevin-Chen0 Oct 11, 2026
8701128
Merge pull request #127 from TemporaLabs/feat/solana-kamino
Kevin-Chen0 Oct 11, 2026
5ecc316
embed the registry and the version in the bundle
Kevin-Chen0 Oct 11, 2026
59ef97f
embed only the version from package.json, not the whole manifest
Kevin-Chen0 Oct 11, 2026
75b2087
build: embed only the version from package.json in the CLI bundle
Kevin-Chen0 Oct 11, 2026
61d3684
Merge pull request #129 from TemporaLabs/feat/embed-registry-107
Kevin-Chen0 Oct 11, 2026
1059514
deps: viem 2.57.3, with the CLI bundle rebuilt
Kevin-Chen0 Oct 11, 2026
0e6af6f
add llms.txt, generated, and point to it from --help
Kevin-Chen0 Oct 11, 2026
935fc8f
leave the plugin's CLI without the guide pointer
Kevin-Chen0 Oct 11, 2026
4490517
Merge pull request #131 from TemporaLabs/feat/llms-txt-106
Kevin-Chen0 Oct 11, 2026
d1f1df1
docs: name the two ways to use the vaults, the app and an agent
Kevin-Chen0 Oct 11, 2026
bb2ee9f
Merge pull request #132 from TemporaLabs/docs/app-and-agent
Kevin-Chen0 Oct 12, 2026
59bc5e0
Merge remote-tracking branch 'origin/release/v0.1.3' into deps/viem-2…
Kevin-Chen0 Oct 12, 2026
6fcfe81
Merge pull request #130 from TemporaLabs/deps/viem-2.57.3
Kevin-Chen0 Oct 12, 2026
ba892a1
connect withdraw on Solana: acknowledge the amount, not a share count
Kevin-Chen0 Oct 12, 2026
7cbc4a7
test: an acknowledgement for shares does not confirm the same number …
Kevin-Chen0 Oct 12, 2026
3976fe9
release review fixes: Solana gate tests, terms, help text and small c…
Kevin-Chen0 Oct 12, 2026
a50467d
Merge branch 'fix/solana-withdraw-ack' into fix/release-0.1.3-minor
Kevin-Chen0 Oct 12, 2026
719ebdd
test: the Solana gate's data length and the connect receiver check
Kevin-Chen0 Oct 12, 2026
e5e710e
docs: v0.1.3 install pins, and corrections from the release review
Kevin-Chen0 Oct 12, 2026
fdf17d4
docs: Test 2A's withdrawals are not gated, and still subject to liqui…
Kevin-Chen0 Oct 12, 2026
66af3b1
docs: AGENTS.md names where the Solana relay builds its transaction
Kevin-Chen0 Oct 12, 2026
51e7e34
Merge pull request #133 from TemporaLabs/fix/solana-withdraw-ack
Kevin-Chen0 Oct 12, 2026
a8d95b5
Merge pull request #134 from TemporaLabs/fix/release-0.1.3-minor
Kevin-Chen0 Oct 12, 2026
24492ae
Merge pull request #137 from TemporaLabs/docs/release-0.1.3
Kevin-Chen0 Oct 12, 2026
a80df65
docs: README lists the six supported chains, with their logos
Kevin-Chen0 Oct 12, 2026
d7af866
Merge pull request #138 from TemporaLabs/docs/readme-supported-chains
Kevin-Chen0 Oct 12, 2026
a9f7dbe
docs: README shows the chain logos in the Vaults table, not a separat…
Kevin-Chen0 Oct 12, 2026
d428eeb
Merge pull request #139 from TemporaLabs/docs/readme-chains-in-vaults
Kevin-Chen0 Oct 12, 2026
5616ce3
earn skill: several chains in one request
Kevin-Chen0 Oct 12, 2026
7f0a5fb
earn skill: frame the multi-deposit section by vault, not chain
Kevin-Chen0 Oct 12, 2026
7abe305
earn skill: several vaults in one request (#140)
Kevin-Chen0 Oct 12, 2026
f070b96
Solana withdrawal: refuse a simulation that burns fewer shares than a…
Kevin-Chen0 Oct 12, 2026
11b39cc
ci: run the built CLI on Windows, macOS and Linux and check how it exits
Kevin-Chen0 Oct 12, 2026
0dff26b
ci: widen the exit check to installed bins, v0.1.2, Solana, history s…
Kevin-Chen0 Oct 12, 2026
8fdd60d
Solana withdrawal: allow one share unit of rounding per withdraw inst…
Kevin-Chen0 Oct 12, 2026
a7d21dd
fix: Windows with Node 24, exit without crashing after a correct answer
Kevin-Chen0 Oct 12, 2026
e36ea1a
Solana withdrawal: refuse a simulation that burns fewer shares than a…
Kevin-Chen0 Oct 12, 2026
2efdfcd
ci: keep the published v0.1.2 in the exit check as a reference, not a…
Kevin-Chen0 Oct 12, 2026
9ba04bf
build: llms.txt generation reads \r\n line ends, so the build runs on…
Kevin-Chen0 Oct 12, 2026
cf68bfa
earn skill: say what Treasury can do, and handle positions and withdr…
Kevin-Chen0 Oct 12, 2026
f54d11c
earn skill: reach the overview from "what can Treasury do", and keep …
Kevin-Chen0 Oct 12, 2026
aaf900d
earn skill: what Treasury can do, and positions and withdrawals acros…
Kevin-Chen0 Oct 12, 2026
ff0cad2
test: pin the check mode's line-end normalisation, drop a vacuous ass…
Kevin-Chen0 Oct 12, 2026
7aac5fe
ci: the slow-endpoint case pins the Windows exit backstop; review fol…
Kevin-Chen0 Oct 12, 2026
d573aea
Merge pull request #144 from TemporaLabs/fix/llms-txt-crlf
Kevin-Chen0 Oct 12, 2026
6005d13
Merge pull request #142 from TemporaLabs/fix/windows-exit-122
Kevin-Chen0 Oct 12, 2026
cdf7529
fix: a Solana command no longer exits 0 with no output when the RPC a…
Kevin-Chen0 Oct 12, 2026
a7a5277
docs: say where Solana differs, and point the guide at this version
Kevin-Chen0 Oct 12, 2026
206d846
fix: the Solana landing check binds to the transaction the page built…
Kevin-Chen0 Oct 12, 2026
97f7a67
test: pin that the Solana retry waits on the ref'd timer by default
Kevin-Chen0 Oct 12, 2026
fcf7a5f
test: drive the Solana confirm flow end to end through the built-bloc…
Kevin-Chen0 Oct 12, 2026
3d4f765
solana: pre-flight verdicts, the withdraw split, the 429 hint, and te…
Kevin-Chen0 Oct 12, 2026
0d6a21f
Merge pull request #148 from TemporaLabs/docs/v013-solana-wording
Kevin-Chen0 Oct 12, 2026
931ba4f
Merge pull request #149 from TemporaLabs/fix/v013-minor-code
Kevin-Chen0 Oct 12, 2026
a6755e1
Merge pull request #146 from TemporaLabs/fix/solana-429-silent-exit
Kevin-Chen0 Oct 12, 2026
88e8daa
Merge pull request #147 from TemporaLabs/fix/solana-landing-binding
Kevin-Chen0 Oct 12, 2026
3eff504
connect: serve the page on the first free port of 53682 to 53686
Kevin-Chen0 Oct 12, 2026
163a80e
changelog: the port fallback ships in v0.1.3
Kevin-Chen0 Oct 12, 2026
f880f0a
Merge pull request #151 from TemporaLabs/connect/port-fallback
Kevin-Chen0 Oct 12, 2026
2349018
solana: the landing check allows the rounding the program does
Kevin-Chen0 Oct 13, 2026
128ca02
docs: correct the skill's withdraw-everything step, and fill the Sola…
Kevin-Chen0 Oct 13, 2026
746b41d
Merge pull request #152 from TemporaLabs/fix/solana-landing-rounding
Kevin-Chen0 Oct 13, 2026
b873184
Merge pull request #153 from TemporaLabs/docs/v013-review-fixes
Kevin-Chen0 Oct 13, 2026
0e1158b
docs: the amount is the operator's decision — ask, do not pick
Kevin-Chen0 Oct 13, 2026
6d72dfd
Merge pull request #156 from TemporaLabs/docs/ask-the-amount
Kevin-Chen0 Oct 13, 2026
9b7f816
docs: on a partial withdrawal, ask the operator instead of shrinking it
Kevin-Chen0 Oct 13, 2026
9c14652
Merge pull request #158 from TemporaLabs/docs/withdraw-ask-the-amount
Kevin-Chen0 Oct 13, 2026
5d2e0bb
docs(skill): rewrap the earn skill to fit the 500-line limit
Kevin-Chen0 Oct 13, 2026
d2e6ff0
Merge pull request #159 from TemporaLabs/docs/skill-under-500-lines
Kevin-Chen0 Oct 13, 2026
70476f9
fix(skill): keep the earn skill out of vault analysis requests
Kevin-Chen0 Oct 13, 2026
a89bfc1
Merge pull request #160 from TemporaLabs/fix/skill-trigger-113
Kevin-Chen0 Oct 13, 2026
6bbfa56
fix: withdrawal refusals ask the operator about a smaller amount
Kevin-Chen0 Oct 13, 2026
04506f6
Merge pull request #161 from TemporaLabs/fix/withdraw-refusal-wording
Kevin-Chen0 Oct 13, 2026
6e8fdc4
docs: date the v0.1.3 changelog entries
Kevin-Chen0 Oct 13, 2026
1a2ad93
Merge pull request #162 from TemporaLabs/docs/date-v0.1.3
Kevin-Chen0 Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"name": "Tempora Labs"
},
"metadata": {
"description": "Agent Treasury by Tempora Labs — treasury management plugin for AI agents. Open source, starting with yield on idle USDC and USDG: measured vault registry on Base, Arbitrum One and Robinhood Chain, simulation-based pre-flight, unsigned deposit and withdraw calls for your own signer or your own browser wallet. Never holds a key."
"description": "Agent Treasury by Tempora Labs — treasury management plugin for AI agents. Open source, starting with yield on idle USDC and USDG: measured vault registry on Base, Arbitrum One, Robinhood Chain, Ethereum, Monad and Solana, simulation-based pre-flight, unsigned deposit and withdraw calls for your own signer or your own browser wallet. Never holds a key."
},
"plugins": [
{
"name": "treasury",
"description": "Agent Treasury by Tempora Labs — treasury management plugin for AI agents; your own wallet holds the position. Ships the earn skill: measured vault registry, simulation-based pre-flight, unsigned deposit/withdraw builders, and a local confirm page for your own wallet. Holds no keys.",
"source": "./plugin",
"version": "0.1.2",
"version": "0.1.3",
"category": "finance",
"homepage": "https://temporalabs.com"
}
Expand Down
183 changes: 183 additions & 0 deletions .github/scripts/cli-exit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Runs the BUILT CLI the way callers do and checks how it exits: the exit code, how long the call took,
// and that the answer came from the chain. Issue #122: on Windows with Node 24 the health check printed
// a correct answer and still exited non-zero.
//
// Subjects: this tree's bundle, and optionally the published v0.1.2 package (where the failure was
// first seen), given as `--subject name=<install prefix>`. Each is run directly (`node dist/treasury.mjs`)
// and through the `treasury` bin npm installs: from cmd and PowerShell on Windows, from sh elsewhere.
// Every call is reported before the verdict, so a red run shows each one's real exit code (with its hex
// form, which names a Windows crash), duration and output instead of stopping at the first mismatch.
import { spawn } from "node:child_process";
import { createServer } from "node:http";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

const WIN = process.platform === "win32";
const ROOT = fileURLToPath(new URL("../..", import.meta.url));
const MAX_MS = Number(process.env.CLI_EXIT_MAX_MS ?? 20000);
const BASE_RPC = "https://mainnet.base.org";
const EVM_ACCOUNT = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // a Base address that holds USDC; read only
const SOLANA_ACCOUNT = "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T"; // an unfunded Solana address; read only
const VAULT_2B = "0x91BcEbA5feCB9E92d80F1845B55cC56621E9352F";
const SLOW_MS = 15000;
// The slow case's own ceiling. A CLI that waits for the held request instead of ending gives up on it
// only when viem's 10 s request timeout fires; with Windows's 2 s exit backstop it ends in about 5 s.
// Below 10 s, this ceiling fails a fix that waits for open handles, which MAX_MS alone does not.
const SLOW_CASE_MAX_MS = 9000;
// "0.1.10" sorts before "0.1.3" as a string; compare the numbers.
const versionAtLeast = (v, min) => {
const [a, b] = [v, min].map((x) => x.split(".").map(Number));
for (let i = 0; i < Math.max(a.length, b.length); i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
return true;
};

async function rpc(method, params) {
const r = await fetch(BASE_RPC, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }) });
return (await r.json()).result;
}

// An account with real history on the default vault: whoever the chain says owns it (it deposited).
const owner = `0x${(await rpc("eth_call", [{ to: VAULT_2B, data: "0x8da5cb5b" }, "latest"])).slice(-40)}`;

// A Base endpoint that forwards every request but holds `eth_chainId` back for SLOW_MS: the health
// check answers without it, so the CLI exits while a request to this endpoint is still open. That is
// the case its immediate exit exists for, and the one most likely to trip an exit-time defect.
const slow = createServer((req, res) => {
let body = "";
req.on("data", (c) => (body += c));
req.on("end", async () => {
try {
if (/"eth_chainId"/.test(body)) await new Promise((r) => setTimeout(r, SLOW_MS));
const up = await fetch(BASE_RPC, { method: "POST", headers: { "content-type": "application/json" }, body });
res.writeHead(up.status, { "content-type": "application/json" }).end(await up.text());
} catch {
res.destroy();
}
});
});
await new Promise((r) => slow.listen(0, "127.0.0.1", r));
const SLOW_URL = `http://127.0.0.1:${slow.address().port}`;

const cases = [
{ name: "health check", args: ["earn", "status"], code: 0, proof: (o) => o.rpc === "ok" && /^\d+$/.test(o.latestBlock ?? "") },
{
name: "deposit quote",
args: ["earn", "quote", "--direction", "deposit", "--account", EVM_ACCOUNT, "--amount_usdc", "1"],
code: 0,
proof: (o) => /USDC per share$/.test(o.sharePriceInAssets ?? "") && o.preflight !== undefined,
},
{
name: "Solana quote",
args: ["earn", "quote", "--chain", "solana", "--direction", "deposit", "--account", SOLANA_ACCOUNT, "--amount_usdc", "1"],
code: 0,
since: "0.1.3",
proof: (o) => o.chain === "solana" && JSON.stringify(o).includes("slot"),
},
{
name: "balance with history",
args: ["earn", "balance", "--account", owner],
code: 0,
// The event-log scan: many requests, and it may stop at its time budget on a public endpoint.
// A failed or partial scan on the public endpoint is fine: the point is that it ran and the CLI exited.
proof: (o) => o.scan !== undefined && /^\d+(\.\d+)?$/.test(o.sharesExact ?? "") && Number.isInteger(o.measuredAtBlock),
},
{
name: "health, slow endpoint",
args: ["earn", "status"],
env: { TREASURY_RPC_BASE: SLOW_URL },
code: 0,
maxMs: SLOW_CASE_MAX_MS,
proof: (o) => o.rpc === "ok" && /^\d+$/.test(o.latestBlock ?? ""),
},
{ name: "refused argument", args: ["earn", "quote", "--direction", "sideways"], code: 1, proof: (_o, err) => typeof JSON.parse(err).error === "string" },
];

// Subjects: this tree, plus any `--subject name=<prefix>` (an npm install prefix holding the package).
// A `--reference name=<prefix>` is run and reported the same way but does not decide the verdict: it is
// a published release kept beside this tree for comparison, and a release that shipped a defect this
// tree fixes would otherwise keep the job red for good. (A reference that is missing still fails.)
const subjects = [{ name: "this tree", version: JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version, file: join(ROOT, "dist", "treasury.mjs"), prefix: process.env.CLI_EXIT_PREFIX }];
for (const a of process.argv.slice(2)) {
const m = /^--(subject|reference)=([^=]+)=(.+)$/.exec(a);
if (m) subjects.push({ name: m[2], version: m[2].replace(/^v/, ""), file: join(m[3], "node_modules", "@temporalabs", "treasury", "dist", "treasury.mjs"), prefix: m[3], reference: m[1] === "reference" });
}

function launches(s) {
const out = [{ how: "node", cmd: process.execPath, pre: [s.file], shell: false }];
if (!s.prefix) return out;
const bin = join(s.prefix, "node_modules", ".bin");
if (WIN) {
out.push({ how: "cmd + npm bin", cmd: join(bin, "treasury.cmd"), pre: [], shell: true });
out.push({ how: "PowerShell + npm bin", cmd: "pwsh", pre: ["-NoProfile", "-Command"], shell: false, ps1: join(bin, "treasury.ps1") });
} else {
out.push({ how: "sh + npm bin", cmd: join(bin, "treasury"), pre: [], shell: true });
}
return out;
}

function call(l, args, env) {
return new Promise((resolve) => {
const started = Date.now();
// PowerShell takes one command string: the npm shim, each argument single-quoted, then its exit code.
const argv = l.ps1 ? [...l.pre, `& '${l.ps1}' ${args.map((a) => `'${a}'`).join(" ")}; exit $LASTEXITCODE`] : [...l.pre, ...args];
const child = spawn(l.cmd, l.shell ? argv.map((a) => (/[\s"]/.test(a) ? `"${a}"` : a)) : argv, {
env: { ...process.env, ...env },
shell: l.shell,
windowsHide: true,
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (d) => (stdout += d));
child.stderr.on("data", (d) => (stderr += d));
const kill = setTimeout(() => child.kill(), 120000);
child.on("error", (e) => (stderr += `\nspawn error: ${e.message}`));
child.on("close", (status, signal) => {
clearTimeout(kill);
resolve({ status, signal, ms: Date.now() - started, stdout, stderr });
});
});
}

const rows = [];
for (const s of subjects) {
if (!existsSync(s.file)) {
console.log(`subject ${s.name}: ${s.file} is missing`);
// A subject that did not install fails the job even when it is a reference: a comparison that
// silently has nothing to compare against is a broken check, not a passing one.
rows.push({ subject: s.name, launch: "-", case: "installed", ok: false, gates: true });
continue;
}
for (const l of launches(s)) {
for (const c of cases) {
if (c.since && !versionAtLeast(s.version, c.since)) continue;
const r = await call(l, c.args, c.env);
let proved = false;
try {
proved = Boolean(c.proof(r.stdout.trim() ? JSON.parse(r.stdout) : {}, r.stderr));
} catch {
proved = false;
}
const hex = typeof r.status === "number" && r.status > 255 ? ` (0x${(r.status >>> 0).toString(16).toUpperCase()})` : "";
const ok = r.status === c.code && proved && r.ms <= (c.maxMs ?? MAX_MS);
rows.push({ subject: s.reference ? `${s.name} (reference)` : s.name, launch: l.how, case: c.name, expected: c.code, exit: `${r.status}${hex}`, signal: r.signal, ms: r.ms, proved, ok, gates: !s.reference });
console.log(`--- [${s.name} | ${l.how}] ${c.name}: exit ${r.status}${hex}${r.signal ? ` signal ${r.signal}` : ""}, ${r.ms} ms, expected ${c.code}, answer proved: ${proved}`);
if (!ok) {
if (r.stdout.trim()) console.log(`stdout:\n${r.stdout.trim().slice(0, 1500)}`);
if (r.stderr.trim()) console.log(`stderr:\n${r.stderr.trim().slice(0, 3000)}`);
}
}
}
}
slow.close();
slow.closeAllConnections?.();
console.log(`\n${process.platform} ${process.arch} ${process.version}`);
console.table(rows);
const differs = rows.filter((r) => !r.ok && !r.gates);
if (differs.length) console.log(`reference, not gating: ${differs.map((r) => `${r.subject} | ${r.launch} | ${r.case}`).join("; ")}`);
const bad = rows.filter((r) => !r.ok && r.gates);
if (bad.length) {
console.log(`FAILED: ${bad.map((r) => `${r.subject} | ${r.launch} | ${r.case}`).join("; ")}`);
process.exit(1);
}
console.log("every call exited as expected, in time, with an answer read from the chain");
49 changes: 44 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ jobs:
|| { echo "::error::dist/treasury.mjs is stale — run npm run build and commit it"; exit 1; }
git diff --exit-code --stat -- THIRD_PARTY_NOTICES.md \
|| { echo "::error::THIRD_PARTY_NOTICES.md is stale — run npm run build and commit it"; exit 1; }
# llms.txt is built from the earn skill and the command descriptions; a drifted copy fails here.
git diff --exit-code --stat -- llms.txt \
|| { echo "::error::llms.txt differs from what the skill and the commands produce — run npm run build and commit it"; exit 1; }
# Only the files the build WRITES into plugin/; the manifests and the skill there are edited by hand.
# `git status`, not `git diff`: a copy the commit deleted comes back UNTRACKED, which diff ignores.
stale=$(git status --porcelain -- plugin/dist plugin/registry plugin/LICENSE plugin/NOTICE plugin/THIRD_PARTY_NOTICES.md plugin/THIRD_PARTY_NOTICES.connect-page.md plugin/package.json)
Expand All @@ -184,13 +187,13 @@ jobs:
# ── Licensing (Apache-2.0). An npm tarball takes what `files` publishes, and a plugin install
# takes plugin/, so the licence, its NOTICE and the dependency notices must travel INSIDE both.
# ───────────────────────────────────────────────────────────────────────────────────────────
- name: the packed artifact ships LICENSE, NOTICE and the third-party notices
- name: the packed artifact ships LICENSE, NOTICE, the third-party notices and llms.txt
if: steps.pk.outputs.present == 'yes'
run: |
npm pack --dry-run --json --ignore-scripts > /tmp/pack.json
node -e '
const files = new Set(require("/tmp/pack.json")[0].files.map((f) => f.path));
const need = ["LICENSE", "NOTICE", "THIRD_PARTY_NOTICES.md", "THIRD_PARTY_NOTICES.connect-page.md", "dist/treasury.mjs", "dist/connect-page.js"];
const need = ["LICENSE", "NOTICE", "THIRD_PARTY_NOTICES.md", "THIRD_PARTY_NOTICES.connect-page.md", "llms.txt", "dist/treasury.mjs", "dist/connect-page.js"];
const missing = need.filter((f) => !files.has(f));
if (missing.length) { console.log("::error file=package.json::the packed artifact is missing " + missing.join(", ")); process.exit(1); }
console.log("packed artifact carries " + need.join(", "));
Expand Down Expand Up @@ -227,7 +230,7 @@ jobs:
cprops = {c["tool"]: {f["flag"].removeprefix("--") for f in c["flags"]} for c in json.loads(con.stdout)["commands"]}
assert cprops == {
"connect_status": set(),
"connect_wallet": set(),
"connect_wallet": {"chain"},
"connect_disconnect": set(),
"connect_deposit": {"vault", "chain", "amount_usdc", "receiver", "ack"},
"connect_withdraw": {"vault", "chain", "receiver", "amount_usdc", "all", "shares_exact", "ack"},
Expand Down Expand Up @@ -289,8 +292,8 @@ jobs:
if phantom: print("::error::SKILL.md names commands the CLI does not have:", sorted(phantom))
sys.exit(1 if (missing or phantom) else 0)
PY
# A package.json version is a RUNTIME value: the bundle reports the one beside it as
# `treasury --version` and `earn status`, and the manifests are what the marketplaces advertise.
# The bundle reports the package.json version it was built from as `treasury --version` and
# `earn status`, and the manifests are what the marketplaces advertise: this ties them together.
- name: every declaration of this repository's own version agrees
if: steps.pk.outputs.present == 'yes'
run: bash scripts/check-versions.sh
Expand Down Expand Up @@ -343,6 +346,42 @@ jobs:
console.log("LICENSE is the canonical Apache-2.0 text; NOTICE and the third-party notices say what they must");
JS

# The BUILT CLI, run as a caller runs it, on each OS the CLI ships to: the exit code, the time the
# call takes, and proof the answer came from the chain (.github/scripts/cli-exit.mjs). Issue #122:
# on Windows with Node 24 a correct answer came with a non-zero exit. Node 22 is the control, and
# 24.0.0 the earliest Node 24.
cli-exit:
strategy:
fail-fast: false
matrix:
include:
- { os: windows-latest, node: 24 }
- { os: windows-latest, node: 24.0.0 }
- { os: windows-latest, node: 22 }
- { os: macos-latest, node: 24 }
- { os: ubuntu-latest, node: 24 }
- { os: ubuntu-latest, node: 22 }
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
# Installed the way users install it, so the npm `treasury` bin is exercised too: this tree's
# package (packed without the build lifecycle; CI's `check` job already holds dist/ to the
# source) and the published v0.1.2, the release the failure was first seen with. v0.1.2 is a
# reference: reported beside this tree, but it does not decide the verdict, since it ships the defect.
- name: install this tree's package and the published v0.1.2
shell: bash
run: |
npm pack --ignore-scripts --pack-destination "$RUNNER_TEMP"
npm install --prefix "$RUNNER_TEMP/cur" --ignore-scripts --no-audit --no-fund "$RUNNER_TEMP"/temporalabs-treasury-*.tgz
npm install --prefix "$RUNNER_TEMP/v012" --ignore-scripts --no-audit --no-fund @temporalabs/treasury@0.1.2
- name: the built CLI exits 0 with a chain-read answer, and 1 on a refused argument
shell: bash
run: CLI_EXIT_PREFIX="$RUNNER_TEMP/cur" node .github/scripts/cli-exit.mjs "--reference=v0.1.2=$RUNNER_TEMP/v012"

# ── Bundle provenance ──────────────────────────────────────────────────────────────────────────
# This job attests the COMMITTED root dist/treasury.mjs. A marketplace install runs
# plugin/dist/treasury.mjs, which the plugin-folder step holds byte-identical to it in the
Expand Down
Loading
Loading