@promus/cli 0.24.17

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.
Files changed (96) hide show
  1. package/README.md +18 -0
  2. package/bin/promus +33 -0
  3. package/package.json +51 -0
  4. package/src/commands/_agents.ts +14 -0
  5. package/src/commands/_inft-ref.ts +43 -0
  6. package/src/commands/_unlock.ts +74 -0
  7. package/src/commands/admin-autotopup-tick.ts +73 -0
  8. package/src/commands/admin.test.ts +34 -0
  9. package/src/commands/admin.ts +32 -0
  10. package/src/commands/balance.test.ts +10 -0
  11. package/src/commands/balance.ts +112 -0
  12. package/src/commands/chat-sandbox.tsx +520 -0
  13. package/src/commands/chat-telegram.ts +398 -0
  14. package/src/commands/chat.tsx +1916 -0
  15. package/src/commands/deploy.ts +204 -0
  16. package/src/commands/drain.ts +90 -0
  17. package/src/commands/gateway-logs.ts +47 -0
  18. package/src/commands/gateway-run.ts +54 -0
  19. package/src/commands/gateway-start.ts +218 -0
  20. package/src/commands/gateway-status.ts +88 -0
  21. package/src/commands/gateway-stop.ts +133 -0
  22. package/src/commands/gateway.ts +101 -0
  23. package/src/commands/init/cost.test.ts +169 -0
  24. package/src/commands/init/cost.ts +154 -0
  25. package/src/commands/init/funding-gate.ts +67 -0
  26. package/src/commands/init/model-picker.ts +81 -0
  27. package/src/commands/init/operator-picker.ts +263 -0
  28. package/src/commands/init/resume.ts +136 -0
  29. package/src/commands/init/sandbox-provision.test.ts +497 -0
  30. package/src/commands/init/sandbox-provision.ts +1177 -0
  31. package/src/commands/init/telegram-step.ts +229 -0
  32. package/src/commands/init/wizard-state.ts +95 -0
  33. package/src/commands/init.ts +612 -0
  34. package/src/commands/inspect.ts +529 -0
  35. package/src/commands/ledger.ts +176 -0
  36. package/src/commands/logs.ts +86 -0
  37. package/src/commands/migrate-keystore.ts +155 -0
  38. package/src/commands/model.ts +48 -0
  39. package/src/commands/pairing-approve.ts +114 -0
  40. package/src/commands/pairing-clear.ts +42 -0
  41. package/src/commands/pairing-list.ts +58 -0
  42. package/src/commands/pairing-revoke.ts +52 -0
  43. package/src/commands/pairing.test.ts +88 -0
  44. package/src/commands/pairing.ts +81 -0
  45. package/src/commands/pause.ts +99 -0
  46. package/src/commands/profile.ts +184 -0
  47. package/src/commands/restore.ts +221 -0
  48. package/src/commands/resume.ts +181 -0
  49. package/src/commands/status.ts +119 -0
  50. package/src/commands/sync.ts +147 -0
  51. package/src/commands/telegram-remove.ts +65 -0
  52. package/src/commands/telegram-setup.ts +74 -0
  53. package/src/commands/telegram-status.ts +89 -0
  54. package/src/commands/telegram.test.ts +50 -0
  55. package/src/commands/telegram.ts +44 -0
  56. package/src/commands/topup.ts +303 -0
  57. package/src/commands/transfer.test.ts +111 -0
  58. package/src/commands/transfer.ts +520 -0
  59. package/src/commands/upgrade.test.ts +137 -0
  60. package/src/commands/upgrade.ts +690 -0
  61. package/src/config/load.ts +35 -0
  62. package/src/config/render.test.ts +96 -0
  63. package/src/config/render.ts +110 -0
  64. package/src/index.ts +378 -0
  65. package/src/sandbox/client.test.ts +251 -0
  66. package/src/sandbox/client.ts +424 -0
  67. package/src/ui/app.tsx +677 -0
  68. package/src/ui/approval-summary.test.ts +154 -0
  69. package/src/ui/approval-summary.ts +34 -0
  70. package/src/ui/markdown-parse.ts +219 -0
  71. package/src/ui/markdown.test.ts +146 -0
  72. package/src/ui/markdown.tsx +37 -0
  73. package/src/ui/state.test.ts +74 -0
  74. package/src/ui/state.ts +198 -0
  75. package/src/util/bootstrap-mode.test.ts +40 -0
  76. package/src/util/bootstrap-mode.ts +25 -0
  77. package/src/util/bootstrap-progress-box.test.ts +190 -0
  78. package/src/util/bootstrap-progress-box.ts +378 -0
  79. package/src/util/brain-secrets.ts +96 -0
  80. package/src/util/cli-version.ts +28 -0
  81. package/src/util/format.test.ts +16 -0
  82. package/src/util/format.ts +11 -0
  83. package/src/util/gateway-spawn.test.ts +86 -0
  84. package/src/util/gateway-spawn.ts +128 -0
  85. package/src/util/gateway-version.test.ts +113 -0
  86. package/src/util/gateway-version.ts +154 -0
  87. package/src/util/github-releases.test.ts +116 -0
  88. package/src/util/github-releases.ts +79 -0
  89. package/src/util/profile-key.test.ts +60 -0
  90. package/src/util/profile-key.ts +25 -0
  91. package/src/util/ref-resolver.test.ts +77 -0
  92. package/src/util/ref-resolver.ts +55 -0
  93. package/src/util/silence-console.test.ts +53 -0
  94. package/src/util/silence-console.ts +40 -0
  95. package/src/util/telegram-secrets.test.ts +227 -0
  96. package/src/util/telegram-secrets.ts +223 -0
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # promus
2
+
3
+ CLI binary for **promus**: the first fully on-chain sovereign agent harness on 0G.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add -g promus
9
+ promus init
10
+ ```
11
+
12
+ Requires [bun](https://bun.sh) ≥ 1.1.
13
+
14
+ ## Commands
15
+
16
+ `promus init` boots the wizard (mints an iNFT, opens a 0G Compute ledger, generates the agent EOA). After that: `promus` for chat, `promus status`, `promus logs`, `promus topup`, `promus ledger`, `promus drain`, `promus sync`, `promus inspect`, `promus deploy`, `promus upgrade`, `promus help` for the full list.
17
+
18
+ See the [root README](https://github.com/JemIIahh/promus#readme) for architecture, concepts, and the full command reference.
package/bin/promus ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bun
2
+ // Register the @opentui/solid JSX transform plugin BEFORE any .tsx file
3
+ // loads. bunfig.toml's `preload` only fires when bun discovers bunfig.toml
4
+ // in the cwd lookup chain — running `promus` from outside the repo (e.g.
5
+ // `cd ~ && promus`, or as an installed npm bin) skips it entirely, leaving
6
+ // JSX compiled as React.createElement and the chat TUI rendering blank.
7
+ // Importing the preload module here registers the plugin regardless of
8
+ // cwd. The plugin is idempotent so this is a no-op if bunfig.toml ALSO
9
+ // loaded it.
10
+ import '@opentui/solid/preload'
11
+
12
+ // Polyfill Error.captureStackTrace for bun + follow-redirects compatibility.
13
+ // follow-redirects@1.16 (transitive via @0glabs/0g-serving-broker → axios) calls
14
+ // Error.captureStackTrace(this, ctor) inside a CustomError constructor whose
15
+ // prototype is set to Error AFTER createErrorType returns. Bun's strict-mode
16
+ // validator rejects the call when bun's workspace resolver loads the module
17
+ // (the failure is cwd-dependent: only triggers when bun's cwd is inside this
18
+ // monorepo). Wrapping the original keeps Node parity without monkey-patching
19
+ // follow-redirects itself.
20
+ const _origCapture = Error.captureStackTrace
21
+ if (typeof _origCapture === 'function') {
22
+ Error.captureStackTrace = (target, ctor) => {
23
+ if (!target || typeof target !== 'object') return
24
+ try {
25
+ _origCapture(target, ctor)
26
+ } catch {
27
+ try {
28
+ target.stack = new Error().stack
29
+ } catch {}
30
+ }
31
+ }
32
+ }
33
+ import '../src/index'
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@promus/cli",
3
+ "version": "0.24.17",
4
+ "type": "module",
5
+ "description": "Promus CLI: sovereign agent gateway on Arbitrum. Runs `promus init` to mint an iNFT and bring up your agent",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/JemIIahh/promus",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/JemIIahh/promus.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/JemIIahh/promus/issues"
15
+ },
16
+ "keywords": ["arbitrum", "promus", "agent", "ai", "cli", "tui", "sovereign", "gateway", "inft"],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "engines": {
21
+ "bun": ">=1.1"
22
+ },
23
+ "files": ["src", "bin", "README.md"],
24
+ "bin": {
25
+ "promus": "./bin/promus"
26
+ },
27
+ "main": "./src/index.ts",
28
+ "types": "./src/index.ts",
29
+ "scripts": {
30
+ "build": "tsc -b",
31
+ "test": "bun test"
32
+ },
33
+ "dependencies": {
34
+ "@clack/prompts": "^0.8.2",
35
+ "@opentui/core": "^0.1.97",
36
+ "@opentui/solid": "^0.1.97",
37
+ "@promus/core": "0.24.17",
38
+ "@promus/gateway": "0.24.17",
39
+ "@promus/plugin-comms": "0.24.17",
40
+ "@promus/plugin-onchain": "0.24.17",
41
+ "@promus/plugin-system": "0.24.17",
42
+ "@promus/plugin-telegram": "0.24.17",
43
+ "picocolors": "^1.1.1",
44
+ "qrcode-terminal": "^0.12.0",
45
+ "solid-js": "^1.9.12",
46
+ "viem": "^2.21.55"
47
+ },
48
+ "devDependencies": {
49
+ "@types/qrcode-terminal": "^0.12.2"
50
+ }
51
+ }
@@ -0,0 +1,14 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readdir } from 'node:fs/promises'
3
+ import { agentPaths } from '@promus/core'
4
+
5
+ export async function listAgentIds(): Promise<string[]> {
6
+ if (!existsSync(agentPaths.agentsDir)) return []
7
+ const entries = await readdir(agentPaths.agentsDir, { withFileTypes: true })
8
+ return entries.filter(e => e.isDirectory()).map(e => e.name)
9
+ }
10
+
11
+ export async function pickDefaultAgent(): Promise<string | null> {
12
+ const ids = await listAgentIds()
13
+ return ids[0] ?? null
14
+ }
@@ -0,0 +1,43 @@
1
+ import { type PromusNetwork, NETWORK_CHAIN_ID, networkFromChainId } from '@promus/core'
2
+ import type { Address } from 'viem'
3
+
4
+ /**
5
+ * Parse a CAIP-style or 0G-flavor iNFT ref string into its parts. Used by
6
+ * `promus restore` and `promus inspect` to take a single positional argument
7
+ * pointing at any iNFT on either 0G network.
8
+ *
9
+ * Accepted forms:
10
+ * `eip155:<chainId>:0xCONTRACT:<tokenId>`
11
+ * `0g-mainnet:0xCONTRACT:<tokenId>`
12
+ * `0g-testnet:0xCONTRACT:<tokenId>`
13
+ */
14
+ export interface ParsedINFTRef {
15
+ network: PromusNetwork
16
+ contract: Address
17
+ tokenId: bigint
18
+ }
19
+
20
+ export function parseINFTRef(ref: string): ParsedINFTRef {
21
+ const parts = ref.split(':')
22
+ if (parts.length === 4 && parts[0] === 'eip155') {
23
+ const chainId = Number(parts[1])
24
+ const contract = parts[2] as Address
25
+ const tokenId = BigInt(parts[3]!)
26
+ const network = networkFromChainId(chainId)
27
+ if (!network) {
28
+ const known = Object.values(NETWORK_CHAIN_ID).join(' or ')
29
+ throw new Error(`Unknown chain id ${chainId} (expected ${known})`)
30
+ }
31
+ return { network, contract, tokenId }
32
+ }
33
+ if (parts.length === 3 && (parts[0] === '0g-mainnet' || parts[0] === '0g-testnet')) {
34
+ return {
35
+ network: parts[0] as PromusNetwork,
36
+ contract: parts[1] as Address,
37
+ tokenId: BigInt(parts[2]!),
38
+ }
39
+ }
40
+ throw new Error(
41
+ `Unrecognized iNFT ref '${ref}'. Expected 'eip155:<chain>:0xCONTRACT:<tokenId>' or '0g-mainnet:0xCONTRACT:<tokenId>'.`,
42
+ )
43
+ }
@@ -0,0 +1,74 @@
1
+ import { spinner } from '@clack/prompts'
2
+ import {
3
+ type PromusConfig,
4
+ type PromusNetwork,
5
+ agentPaths,
6
+ fetchAndDecryptKeystore,
7
+ iNFTAgentId,
8
+ } from '@promus/core'
9
+ import type { Address, Hex } from 'viem'
10
+ import { withSilencedConsole } from '../util/silence-console'
11
+ import { loadOrPickOperatorSigner } from './init/operator-picker'
12
+
13
+ export interface UnlockedAgent {
14
+ agentPrivkey: Hex
15
+ agentAddress: Address
16
+ network: PromusNetwork
17
+ close: () => Promise<void>
18
+ }
19
+
20
+ /**
21
+ * Shared operator-unlock dance for any command that needs the agent privkey:
22
+ * 1. pick the operator signer (keystore / WC / keychain) per config hint
23
+ * 2. fetch the encrypted keystore from 0G Storage
24
+ * 3. decrypt via operator signature
25
+ *
26
+ * Returns null if the operator picker is cancelled or the keystore can't be
27
+ * decrypted; caller should bail out early on null.
28
+ *
29
+ * The unlock spinner is rendered with the passed `spinnerLabel` so each caller
30
+ * keeps its own copy.
31
+ *
32
+ * Caller MUST call `close()` once done with the privkey, even on success, to
33
+ * release WC sessions / keystore tmpfiles.
34
+ */
35
+ export async function unlockAgentSigner(
36
+ config: PromusConfig,
37
+ spinnerLabel = 'Fetching encrypted keystore + decrypting via operator wallet',
38
+ ): Promise<UnlockedAgent | null> {
39
+ if (!config.identity.iNFT || !config.identity.agent) return null
40
+ const network = config.network
41
+ const agentAddress = config.identity.agent as Address
42
+ const inftContract = config.identity.iNFT.contract as Address
43
+ const inftTokenId = BigInt(config.identity.iNFT.tokenId)
44
+ const finalAgentId = iNFTAgentId({ contractAddress: inftContract, tokenId: inftTokenId })
45
+ const paths = agentPaths.agent(finalAgentId)
46
+
47
+ const operator = await loadOrPickOperatorSigner({ network, hint: config.operator })
48
+ if (!operator) return null
49
+
50
+ const close = async () => {
51
+ await operator.close?.()
52
+ }
53
+
54
+ const s = spinner()
55
+ s.start(spinnerLabel)
56
+ try {
57
+ const decrypted = await withSilencedConsole(() =>
58
+ fetchAndDecryptKeystore({
59
+ network,
60
+ contractAddress: inftContract,
61
+ tokenId: inftTokenId,
62
+ signer: operator,
63
+ agentAddress,
64
+ cachePath: paths.keystore,
65
+ }),
66
+ )
67
+ s.stop(`unlocked (keystore source: ${decrypted.source})`)
68
+ return { agentPrivkey: decrypted.privkeyHex, agentAddress, network, close }
69
+ } catch (e) {
70
+ s.stop(`unlock failed: ${(e as Error).message.slice(0, 160)}`)
71
+ await close()
72
+ return null
73
+ }
74
+ }
@@ -0,0 +1,73 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { agentPaths, iNFTAgentId, placeholderAgentId } from '@promus/core'
3
+ import { type Address, getAddress } from 'viem'
4
+ import { findAndLoadConfig } from '../config/load'
5
+ import { SandboxClient } from '../sandbox/client'
6
+ import { loadOrPickOperatorSigner } from './init/operator-picker'
7
+
8
+ export async function runAdminAutotopupTick(): Promise<void> {
9
+ const found = await findAndLoadConfig()
10
+ if (!found) {
11
+ console.error('No promus.config.ts found. Run `promus init` first.')
12
+ process.exit(1)
13
+ }
14
+ const { config } = found
15
+
16
+ if (config.deployTarget === 'sandbox' && config.sandbox?.endpoint && config.sandbox.id) {
17
+ const signer = await loadOrPickOperatorSigner({
18
+ network: config.network,
19
+ hint: config.operator,
20
+ })
21
+ if (!signer) {
22
+ console.error('failed to load operator signer (cancelled or no key)')
23
+ process.exit(1)
24
+ }
25
+ const operatorAccount = await signer.account()
26
+ const client = new SandboxClient({
27
+ endpoint: config.sandbox.endpoint,
28
+ sandboxId: config.sandbox.id,
29
+ operator: operatorAccount,
30
+ })
31
+ try {
32
+ const result = await client.triggerAutoTopupTick()
33
+ console.log(JSON.stringify(result, null, 2))
34
+ if (!result.ok) process.exitCode = 1
35
+ } catch (e) {
36
+ console.error(`autotopup-tick failed: ${(e as Error).message.slice(0, 240)}`)
37
+ process.exit(1)
38
+ }
39
+ return
40
+ }
41
+
42
+ if (!config.identity.agent) {
43
+ console.error('No agent address in config. Run `promus init` first.')
44
+ process.exit(1)
45
+ }
46
+ // Slug derivation must match gateway-stop.ts (iNFT-based when minted, else
47
+ // address-placeholder); the daemon writes its sock under the same dir.
48
+ const slug = config.identity.iNFT
49
+ ? iNFTAgentId({
50
+ contractAddress: getAddress(config.identity.iNFT.contract as Address),
51
+ tokenId: BigInt(config.identity.iNFT.tokenId),
52
+ })
53
+ : placeholderAgentId(config.identity.agent)
54
+ const sockPath = `${agentPaths.agent(slug).dir}/gateway.sock`
55
+ if (!existsSync(sockPath)) {
56
+ console.error(
57
+ `Gateway socket not found at ${sockPath}. Start the gateway with \`promus gateway start\` or run \`promus\` first.`,
58
+ )
59
+ process.exit(1)
60
+ }
61
+ const r = await fetch('http://localhost/admin/autotopup/tick', {
62
+ method: 'POST',
63
+ unix: sockPath,
64
+ } as RequestInit & { unix: string })
65
+ if (!r.ok) {
66
+ const detail = await r.text().catch(() => '')
67
+ console.error(`autotopup-tick failed (${r.status}): ${detail}`)
68
+ process.exit(1)
69
+ }
70
+ const body = (await r.json()) as { ok: boolean; reason?: string }
71
+ console.log(JSON.stringify(body, null, 2))
72
+ if (!body.ok) process.exitCode = 1
73
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { parseAdminArgs, runAdmin } from './admin'
3
+
4
+ describe('parseAdminArgs', () => {
5
+ test('errors with usage when no subcommand', () => {
6
+ const r = parseAdminArgs([])
7
+ expect('error' in r).toBe(true)
8
+ if (!('error' in r)) throw new Error('unreachable')
9
+ expect(r.error).toMatch(/usage/i)
10
+ expect(r.error).toMatch(/autotopup-tick/)
11
+ })
12
+
13
+ test('errors on unknown subcommand', () => {
14
+ const r = parseAdminArgs(['nope'])
15
+ expect('error' in r).toBe(true)
16
+ if (!('error' in r)) throw new Error('unreachable')
17
+ expect(r.error).toMatch(/unknown subcommand/)
18
+ expect(r.error).toMatch(/'nope'/)
19
+ })
20
+
21
+ test('parses autotopup-tick', () => {
22
+ const r = parseAdminArgs(['autotopup-tick'])
23
+ expect('error' in r).toBe(false)
24
+ if ('error' in r) throw new Error(r.error)
25
+ expect(r.sub).toBe('autotopup-tick')
26
+ })
27
+ })
28
+
29
+ describe('runAdmin dispatch', () => {
30
+ test('runAdmin is callable + accepts AdminArgs shape', () => {
31
+ expect(typeof runAdmin).toBe('function')
32
+ expect(runAdmin.length).toBe(1)
33
+ })
34
+ })
@@ -0,0 +1,32 @@
1
+ // `promus admin <sub>` — operator-only ops dispatch. Mirrors `pairing.ts` shape.
2
+
3
+ export interface AdminArgs {
4
+ sub: 'autotopup-tick'
5
+ }
6
+
7
+ const VALID_SUBS = ['autotopup-tick'] as const
8
+
9
+ export type AdminParseResult = AdminArgs | { error: string }
10
+
11
+ export function parseAdminArgs(argv: string[]): AdminParseResult {
12
+ const sub = argv[0]
13
+ if (!sub) {
14
+ return {
15
+ error: `usage: promus admin <${VALID_SUBS.join(' | ')}>`,
16
+ }
17
+ }
18
+ if (!(VALID_SUBS as readonly string[]).includes(sub)) {
19
+ return { error: `unknown subcommand '${sub}' (expected: ${VALID_SUBS.join(' | ')})` }
20
+ }
21
+ return { sub: sub as AdminArgs['sub'] }
22
+ }
23
+
24
+ export async function runAdmin(args: AdminArgs): Promise<void> {
25
+ switch (args.sub) {
26
+ case 'autotopup-tick': {
27
+ const { runAdminAutotopupTick } = await import('./admin-autotopup-tick')
28
+ await runAdminAutotopupTick()
29
+ return
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,10 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { runBalance } from './balance'
3
+
4
+ describe('promus balance command surface', () => {
5
+ test('runBalance is exported and callable', () => {
6
+ expect(typeof runBalance).toBe('function')
7
+ // function signature: (opts: { agent?, cwd? }) → Promise<void>
8
+ expect(runBalance.length).toBe(1)
9
+ })
10
+ })
@@ -0,0 +1,112 @@
1
+ import {
2
+ type PromusNetwork,
3
+ NETWORK_RPC,
4
+ format0G,
5
+ getLedgerDetailReadOnly,
6
+ getSandboxBillingReserve,
7
+ } from '@promus/core'
8
+ import { http, type Address, createPublicClient } from 'viem'
9
+ import { findAndLoadConfig } from '../config/load'
10
+
11
+ export interface BalanceOpts {
12
+ agent?: string
13
+ cwd?: string
14
+ }
15
+
16
+ /**
17
+ * Operator-facing aggregator for the agent's full economic position. Mirrors
18
+ * `account.balance` brain tool but renders for terminals.
19
+ *
20
+ * Why: pre-v0.21.9, getting a full picture took `cast balance` × 2 networks +
21
+ * `promus ledger balance` (needs unlock) + a separate cast for sandbox billing.
22
+ * Operators kept under-counting by ~10x because the locked-in-providers split
23
+ * wasn't surfaced anywhere.
24
+ */
25
+ export async function runBalance(opts: BalanceOpts): Promise<void> {
26
+ const found = await findAndLoadConfig(opts.cwd)
27
+ if (!found) {
28
+ console.error('No promus.config.ts found. Run `promus init` first.')
29
+ process.exit(1)
30
+ }
31
+ const { config } = found
32
+ const agentAddress = (opts.agent ?? config.identity.agent) as Address | undefined
33
+ if (!agentAddress) {
34
+ console.error('No agent address. Run `promus init` first or pass `--agent 0x...`.')
35
+ process.exit(1)
36
+ }
37
+
38
+ const network = config.network as PromusNetwork
39
+ const operatorAddress = config.identity.operator as Address | undefined
40
+ const isSandbox = config.deployTarget === 'sandbox'
41
+
42
+ const mainnetClient = createPublicClient({ transport: http(NETWORK_RPC['0g-mainnet']) })
43
+ const testnetClient = createPublicClient({ transport: http(NETWORK_RPC['0g-testnet']) })
44
+
45
+ const [eoaMainnetWei, eoaTestnetWei, ledger, sandboxReserve] = await Promise.all([
46
+ mainnetClient.getBalance({ address: agentAddress }).catch(() => 0n),
47
+ testnetClient.getBalance({ address: agentAddress }).catch(() => 0n),
48
+ getLedgerDetailReadOnly({ network, agentAddress }).catch(() => null),
49
+ isSandbox && operatorAddress
50
+ ? getSandboxBillingReserve({ recipient: operatorAddress }).catch(() => 0n)
51
+ : Promise.resolve(null),
52
+ ])
53
+
54
+ console.log('')
55
+ console.log(`agent ${agentAddress}${config.subname ? ` (${config.subname}.promus.0g)` : ''}`)
56
+ console.log(`network ${network}`)
57
+ console.log(`target ${config.deployTarget ?? 'local'}`)
58
+ console.log('')
59
+ console.log('mainnet (chain 16661)')
60
+ console.log(` EOA balance ${format0G(eoaMainnetWei)} 0G`)
61
+ if (ledger) {
62
+ console.log(` compute ledger total ${format0G(ledger.totalBalance)} 0G`)
63
+ console.log(` available ${format0G(ledger.availableBalance)} 0G`)
64
+ console.log(` locked in providers ${format0G(ledger.lockedBalance)} 0G`)
65
+ } else {
66
+ console.log(' compute ledger (not opened — call `promus topup --compute N` to seed)')
67
+ }
68
+ console.log('')
69
+ console.log('testnet/galileo (chain 16602)')
70
+ console.log(` EOA balance ${format0G(eoaTestnetWei)} 0G`)
71
+ if (isSandbox && operatorAddress) {
72
+ if (sandboxReserve !== null) {
73
+ console.log(
74
+ ` sandbox billing reserve ${format0G(sandboxReserve)} 0G (operator ${operatorAddress})`,
75
+ )
76
+ } else {
77
+ console.log(' sandbox billing reserve (unavailable — RPC error)')
78
+ }
79
+ } else if (isSandbox) {
80
+ console.log(' sandbox billing reserve (operator address missing in config)')
81
+ }
82
+
83
+ console.log('')
84
+ console.log('position summary')
85
+ const mainnetTotal = eoaMainnetWei + (ledger?.totalBalance ?? 0n)
86
+ const testnetTotal = eoaTestnetWei + (sandboxReserve ?? 0n)
87
+ console.log(` mainnet total ${format0G(mainnetTotal)} 0G (EOA + ledger)`)
88
+ console.log(` testnet total ${format0G(testnetTotal)} 0G (EOA + sandbox reserve)`)
89
+
90
+ const warnings: string[] = []
91
+ if (eoaMainnetWei < 2_000_000_000_000_000_000n) {
92
+ warnings.push(
93
+ 'EOA mainnet below 2 0G notify threshold — auto-topup will fire wallet-low events',
94
+ )
95
+ }
96
+ if (ledger && ledger.availableBalance < 500_000_000_000_000_000n) {
97
+ warnings.push(
98
+ 'Compute ledger available below 0.5 0G — auto-topup may transfer from EOA into provider envelopes',
99
+ )
100
+ }
101
+ if (isSandbox && sandboxReserve !== null && sandboxReserve < 1_000_000_000_000_000_000n) {
102
+ warnings.push(
103
+ 'Sandbox billing reserve below 1 0G — top up via `promus topup --sandbox N` to extend container runtime',
104
+ )
105
+ }
106
+ if (warnings.length) {
107
+ console.log('')
108
+ console.log('warnings')
109
+ for (const w of warnings) console.log(` · ${w}`)
110
+ }
111
+ console.log('')
112
+ }