Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ dist/
*.js.map
*config*.json
acp-cli-signer/acp-cli-signer
.DS_Store
.DS_Store

# Superpowers/Hermes Agent artifacts
.hermes/
docs/plans/
docs/superpowers/
3 changes: 2 additions & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Pay for the agent's own LLM-inference workloads from a USDC-funded compute accou
|---|---|---|
| `acp compute status --json` | Show the compute account balance, usage, and limit | `{limit, limitRemaining, usage, ...}` |
| `acp compute top-up --amount <usdc 1–1000> [--chain-id <id>] --json` | Transfer USDC (+ a processing fee) from the agent's wallet to the ACP fee wallet to credit the compute account | `{amount, totalAmount, chainId, feeWallet, txnHash}` |
| `acp compute apply` | Opens the agent's web compute page (`app.virtuals.io/acp/agents/<id>?tab=compute&action=link-github`) where the developer connects GitHub (Privy OAuth) and claims the credit — the CLI can't auth to the campaign backend | `--json` returns `{status:"redirect", url, agentId}` |

The credited balance updates shortly after the transfer confirms — re-probe with `compute status`.

Expand Down Expand Up @@ -523,7 +524,7 @@ src/
chain.ts Chain info
email.ts Agent email
card.ts Agent virtual cards
compute.ts Agent compute account (status, top-up)
compute.ts Agent compute account (status, top-up, apply)
skill.ts Inspect/verify the bundled SKILL.md (path, print, check)
lib/
config.ts Load/save config.json at ~/.config/acp/ (override with ACP_CONFIG_DIR)
Expand Down
57 changes: 57 additions & 0 deletions src/commands/compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getActiveAgentId } from "../lib/activeAgent";
import { createProviderAdapter, getWalletAddress } from "../lib/agentFactory";
import { formatChainId, formatChainIds } from "../lib/chains";
import { CliError } from "../lib/errors";
import { openBrowser } from "../lib/browser";

// ── Registration ────────────────────────────────────────────────────

Expand Down Expand Up @@ -160,4 +161,60 @@ export function registerComputeCommands(program: Command): void {
outputError(json, err instanceof Error ? err : String(err));
}
});

compute
.command("apply")
.description("Apply for Venice developer compute credits ($200 approved)")
.action(async (_opts, cmd) => {
const json = isJson(cmd);
try {
const { agentApi } = await getClient();
const agentId = getActiveAgentId(json);
if (!agentId) return;

// GitHub linking + enrollment for this campaign run on the web app's
// Privy-authenticated session, which the CLI can't reproduce. So the CLI
// just sends the user to the agent's compute page, where the GitHub-linking
// UI lives, and they complete the connect + claim there.
const webUrl = agentApi.getDeveloperCampaignWebUrl(agentId);

if (json) {
outputResult(json, { status: "redirect", url: webUrl, agentId });
return;
}

console.log(
`\n🚀 ${c.cyan(
"Venice-Virtuals Developer Inference Credit Campaign ($200 approved)"
)}`
);
console.log(
`${c.dim(
"----------------------------------------------------------------------"
)}`
);
console.log(`\nApply by connecting your GitHub on this agent's compute page:`);
console.log(`\n ${c.underline(webUrl)}\n`);
console.log(` ${c.dim("1.")} Log in if prompted`);
console.log(` ${c.dim("2.")} Connect GitHub — verifies your eligible repositories`);
console.log(` ${c.dim("3.")} Claim your $200 weekly compute credit`);

if (isTTY()) {
openBrowser(webUrl);
console.log(
`\nℹ️ ${c.dim("Opened in your browser. Run")} ${c.bold(
"acp compute status"
)} ${c.dim("afterward to see your credit.")}`
);
} else {
console.log(
`\nℹ️ ${c.dim("Open the link above, then run")} ${c.bold(
"acp compute status"
)} ${c.dim("to see your credit.")}`
);
}
} catch (err) {
outputError(json, err instanceof Error ? err : String(err));
}
});
}
17 changes: 17 additions & 0 deletions src/lib/api/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,23 @@ export class AgentApi {
);
}

// ── Developer campaign methods ───────────────────────────────────

private getWebBaseUrl(): string {
const base = this.client["baseUrl"];
const isTestnet = base.includes("api-dev") || base.includes("testnet") || base.includes("dev") || base.includes("acp-dev");
return isTestnet ? "https://app-dev.virtuals.io" : "https://app.virtuals.io";
}

/**
* Web URL of an agent's compute / developer-campaign page, where the user can
* connect GitHub via the app's Privy OAuth flow (used as the headless-CLI
* fallback when no local GitHub token is available).
*/
getDeveloperCampaignWebUrl(agentId: string): string {
return `${this.getWebBaseUrl()}/acp/agents/${agentId}?tab=compute&action=link-github`;
}

async getAgentAssets(
agentId: string,
networks: string[]
Expand Down
1 change: 1 addition & 0 deletions src/lib/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const c = {
red: pc.red,
cyan: pc.cyan,
magenta: pc.magenta,
underline: pc.underline,
status: (status: string) => {
switch (status) {
case "completed":
Expand Down
7 changes: 6 additions & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ function isKeychainUnavailable(err: unknown): boolean {

async function withKeyringFallback<T>(op: () => Promise<T>): Promise<T> {
try {
return await op();
const res = await op();
if (res === null) {
await useBackend("file");
return await op();
}
return res;
} catch (err) {
if (!isKeychainUnavailable(err)) throw err;
await useBackend("file");
Expand Down