@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.
- package/README.md +18 -0
- package/bin/promus +33 -0
- package/package.json +51 -0
- package/src/commands/_agents.ts +14 -0
- package/src/commands/_inft-ref.ts +43 -0
- package/src/commands/_unlock.ts +74 -0
- package/src/commands/admin-autotopup-tick.ts +73 -0
- package/src/commands/admin.test.ts +34 -0
- package/src/commands/admin.ts +32 -0
- package/src/commands/balance.test.ts +10 -0
- package/src/commands/balance.ts +112 -0
- package/src/commands/chat-sandbox.tsx +520 -0
- package/src/commands/chat-telegram.ts +398 -0
- package/src/commands/chat.tsx +1916 -0
- package/src/commands/deploy.ts +204 -0
- package/src/commands/drain.ts +90 -0
- package/src/commands/gateway-logs.ts +47 -0
- package/src/commands/gateway-run.ts +54 -0
- package/src/commands/gateway-start.ts +218 -0
- package/src/commands/gateway-status.ts +88 -0
- package/src/commands/gateway-stop.ts +133 -0
- package/src/commands/gateway.ts +101 -0
- package/src/commands/init/cost.test.ts +169 -0
- package/src/commands/init/cost.ts +154 -0
- package/src/commands/init/funding-gate.ts +67 -0
- package/src/commands/init/model-picker.ts +81 -0
- package/src/commands/init/operator-picker.ts +263 -0
- package/src/commands/init/resume.ts +136 -0
- package/src/commands/init/sandbox-provision.test.ts +497 -0
- package/src/commands/init/sandbox-provision.ts +1177 -0
- package/src/commands/init/telegram-step.ts +229 -0
- package/src/commands/init/wizard-state.ts +95 -0
- package/src/commands/init.ts +612 -0
- package/src/commands/inspect.ts +529 -0
- package/src/commands/ledger.ts +176 -0
- package/src/commands/logs.ts +86 -0
- package/src/commands/migrate-keystore.ts +155 -0
- package/src/commands/model.ts +48 -0
- package/src/commands/pairing-approve.ts +114 -0
- package/src/commands/pairing-clear.ts +42 -0
- package/src/commands/pairing-list.ts +58 -0
- package/src/commands/pairing-revoke.ts +52 -0
- package/src/commands/pairing.test.ts +88 -0
- package/src/commands/pairing.ts +81 -0
- package/src/commands/pause.ts +99 -0
- package/src/commands/profile.ts +184 -0
- package/src/commands/restore.ts +221 -0
- package/src/commands/resume.ts +181 -0
- package/src/commands/status.ts +119 -0
- package/src/commands/sync.ts +147 -0
- package/src/commands/telegram-remove.ts +65 -0
- package/src/commands/telegram-setup.ts +74 -0
- package/src/commands/telegram-status.ts +89 -0
- package/src/commands/telegram.test.ts +50 -0
- package/src/commands/telegram.ts +44 -0
- package/src/commands/topup.ts +303 -0
- package/src/commands/transfer.test.ts +111 -0
- package/src/commands/transfer.ts +520 -0
- package/src/commands/upgrade.test.ts +137 -0
- package/src/commands/upgrade.ts +690 -0
- package/src/config/load.ts +35 -0
- package/src/config/render.test.ts +96 -0
- package/src/config/render.ts +110 -0
- package/src/index.ts +378 -0
- package/src/sandbox/client.test.ts +251 -0
- package/src/sandbox/client.ts +424 -0
- package/src/ui/app.tsx +677 -0
- package/src/ui/approval-summary.test.ts +154 -0
- package/src/ui/approval-summary.ts +34 -0
- package/src/ui/markdown-parse.ts +219 -0
- package/src/ui/markdown.test.ts +146 -0
- package/src/ui/markdown.tsx +37 -0
- package/src/ui/state.test.ts +74 -0
- package/src/ui/state.ts +198 -0
- package/src/util/bootstrap-mode.test.ts +40 -0
- package/src/util/bootstrap-mode.ts +25 -0
- package/src/util/bootstrap-progress-box.test.ts +190 -0
- package/src/util/bootstrap-progress-box.ts +378 -0
- package/src/util/brain-secrets.ts +96 -0
- package/src/util/cli-version.ts +28 -0
- package/src/util/format.test.ts +16 -0
- package/src/util/format.ts +11 -0
- package/src/util/gateway-spawn.test.ts +86 -0
- package/src/util/gateway-spawn.ts +128 -0
- package/src/util/gateway-version.test.ts +113 -0
- package/src/util/gateway-version.ts +154 -0
- package/src/util/github-releases.test.ts +116 -0
- package/src/util/github-releases.ts +79 -0
- package/src/util/profile-key.test.ts +60 -0
- package/src/util/profile-key.ts +25 -0
- package/src/util/ref-resolver.test.ts +77 -0
- package/src/util/ref-resolver.ts +55 -0
- package/src/util/silence-console.test.ts +53 -0
- package/src/util/silence-console.ts +40 -0
- package/src/util/telegram-secrets.test.ts +227 -0
- package/src/util/telegram-secrets.ts +223 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { type PromusConfig, agentPaths } from '@promus/core'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load the user's `promus.config.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Phase 6.6: canonical location is `~/.promus/config.ts` (returned by
|
|
9
|
+
* `agentPaths.config`). If that file exists, it wins. Otherwise, fall back
|
|
10
|
+
* to walking upward from cwd looking for `promus.config.ts` (legacy v0.5.0
|
|
11
|
+
* pattern, kept so existing dev setups still work without a migration step).
|
|
12
|
+
*/
|
|
13
|
+
export async function findAndLoadConfig(
|
|
14
|
+
startDir: string = process.cwd(),
|
|
15
|
+
): Promise<{ config: PromusConfig; path: string } | null> {
|
|
16
|
+
const canonical = agentPaths.config
|
|
17
|
+
if (existsSync(canonical)) {
|
|
18
|
+
const mod = (await import(canonical)) as { default: PromusConfig }
|
|
19
|
+
if (!mod.default) throw new Error(`promus config at ${canonical} has no default export`)
|
|
20
|
+
return { config: mod.default, path: canonical }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let dir = resolve(startDir)
|
|
24
|
+
while (true) {
|
|
25
|
+
const candidate = resolve(dir, 'promus.config.ts')
|
|
26
|
+
if (existsSync(candidate)) {
|
|
27
|
+
const mod = (await import(candidate)) as { default: PromusConfig }
|
|
28
|
+
if (!mod.default) throw new Error(`promus.config.ts at ${candidate} has no default export`)
|
|
29
|
+
return { config: mod.default, path: candidate }
|
|
30
|
+
}
|
|
31
|
+
const parent = resolve(dir, '..')
|
|
32
|
+
if (parent === dir) return null
|
|
33
|
+
dir = parent
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { PromusConfig } from '@promus/core'
|
|
3
|
+
import { renderConfigTs } from './render'
|
|
4
|
+
|
|
5
|
+
const baseConfig: PromusConfig = {
|
|
6
|
+
identity: { iNFT: null, operator: null, agent: null },
|
|
7
|
+
network: '0g-mainnet',
|
|
8
|
+
storage: { network: '0g-mainnet' },
|
|
9
|
+
brain: { provider: null, model: null },
|
|
10
|
+
plugins: ['system'],
|
|
11
|
+
tools: {},
|
|
12
|
+
imports: { claudeCode: true },
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('renderConfigTs sandbox block', () => {
|
|
16
|
+
test('fresh config (no sandbox set) emits default + commented examples for each tier', () => {
|
|
17
|
+
const out = renderConfigTs(baseConfig)
|
|
18
|
+
// Active default
|
|
19
|
+
expect(out).toContain(`sandbox: { mode: 'none' }`)
|
|
20
|
+
// Commented OPTION 2 (os)
|
|
21
|
+
expect(out).toContain('OPTION 2: os')
|
|
22
|
+
expect(out).toContain(`// sandbox: { mode: 'os' }`)
|
|
23
|
+
// Commented OPTION 3 (docker)
|
|
24
|
+
expect(out).toContain('OPTION 3: docker')
|
|
25
|
+
expect(out).toContain(`// mode: 'docker'`)
|
|
26
|
+
expect(out).toContain(`// dockerImage: 'nikolaik/python-nodejs:python3.11-nodejs20'`)
|
|
27
|
+
expect(out).toContain('// dockerMountWorkspace: false')
|
|
28
|
+
// PROMUS_SANDBOX_MODE override hint
|
|
29
|
+
expect(out).toContain('PROMUS_SANDBOX_MODE')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('config with sandbox.mode="os" already set emits the chosen value, not the template', () => {
|
|
33
|
+
const out = renderConfigTs({ ...baseConfig, sandbox: { mode: 'os' } })
|
|
34
|
+
expect(out).toContain(`"mode": "os"`)
|
|
35
|
+
// No verbose template when operator already chose
|
|
36
|
+
expect(out).not.toContain('OPTION 2: os')
|
|
37
|
+
expect(out).not.toContain('OPTION 3: docker')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('config with sandbox.mode="docker" + image emits the full chosen object', () => {
|
|
41
|
+
const out = renderConfigTs({
|
|
42
|
+
...baseConfig,
|
|
43
|
+
sandbox: {
|
|
44
|
+
mode: 'docker',
|
|
45
|
+
dockerImage: 'custom/img:tag',
|
|
46
|
+
dockerMountWorkspace: true,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
expect(out).toContain(`"mode": "docker"`)
|
|
50
|
+
expect(out).toContain(`"dockerImage": "custom/img:tag"`)
|
|
51
|
+
expect(out).toContain(`"dockerMountWorkspace": true`)
|
|
52
|
+
expect(out).not.toContain('OPTION')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('annotated template documents resource caps with hermes default values', () => {
|
|
56
|
+
const out = renderConfigTs(baseConfig)
|
|
57
|
+
expect(out).toContain('// dockerCpu: 1')
|
|
58
|
+
expect(out).toContain('// dockerMemoryMb: 5120')
|
|
59
|
+
expect(out).toContain('// dockerDiskMb: 51200')
|
|
60
|
+
expect(out).toContain('// dockerNoNetwork: true')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('config with sandbox docker + resource caps emits chosen numeric values', () => {
|
|
64
|
+
const out = renderConfigTs({
|
|
65
|
+
...baseConfig,
|
|
66
|
+
sandbox: {
|
|
67
|
+
mode: 'docker',
|
|
68
|
+
dockerCpu: 2,
|
|
69
|
+
dockerMemoryMb: 4096,
|
|
70
|
+
dockerNoNetwork: true,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
expect(out).toContain(`"dockerCpu": 2`)
|
|
74
|
+
expect(out).toContain(`"dockerMemoryMb": 4096`)
|
|
75
|
+
expect(out).toContain(`"dockerNoNetwork": true`)
|
|
76
|
+
expect(out).not.toContain('OPTION')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('output is valid TypeScript (parses as a default-export module)', async () => {
|
|
80
|
+
const out = renderConfigTs(baseConfig)
|
|
81
|
+
const { writeFile, rm, mkdtemp } = await import('node:fs/promises')
|
|
82
|
+
const { tmpdir } = await import('node:os')
|
|
83
|
+
const { join } = await import('node:path')
|
|
84
|
+
const dir = await mkdtemp(join(tmpdir(), 'promus-render-test-'))
|
|
85
|
+
const path = join(dir, 'config.ts')
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(path, out, 'utf8')
|
|
88
|
+
const mod = await import(path)
|
|
89
|
+
expect(mod.default).toBeDefined()
|
|
90
|
+
expect(mod.default.sandbox).toEqual({ mode: 'none' })
|
|
91
|
+
expect(mod.default.network).toBe('0g-mainnet')
|
|
92
|
+
} finally {
|
|
93
|
+
await rm(dir, { recursive: true, force: true })
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
import type { PromusConfig } from '@promus/core'
|
|
4
|
+
|
|
5
|
+
export interface RenderConfigOpts {
|
|
6
|
+
header?: string
|
|
7
|
+
subname?: string | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialize an PromusConfig into a `~/.promus/config.ts` file body.
|
|
12
|
+
*
|
|
13
|
+
* Phase 6.6: the config lives at `~/.promus/config.ts` which is outside any
|
|
14
|
+
* workspace, so it MUST NOT import `@promus/core` (the import won't
|
|
15
|
+
* resolve from `~/.promus/`). We emit a plain `export default { ... }` object;
|
|
16
|
+
* the runtime loader treats it as `PromusConfig` directly.
|
|
17
|
+
*/
|
|
18
|
+
export function renderConfigTs(cfg: PromusConfig, opts: RenderConfigOpts = {}): string {
|
|
19
|
+
const header = opts.header ?? ''
|
|
20
|
+
const subnameLine =
|
|
21
|
+
opts.subname !== undefined ? ` subname: ${JSON.stringify(opts.subname)},\n` : ''
|
|
22
|
+
const operatorLine = cfg.operator ? ` operator: ${JSON.stringify(cfg.operator)},\n` : ''
|
|
23
|
+
// Phase 9.5 / v0.10.1: emit either the operator's chosen sandbox config OR an
|
|
24
|
+
// annotated "OPTION 1/2/3" block so the operator can opt in by uncommenting.
|
|
25
|
+
// Mirrors hermes-agent's cli-config.yaml.example pattern: documentation IS
|
|
26
|
+
// the UX, not an interactive wizard. Default mode stays `none` (passthrough)
|
|
27
|
+
// for back-compat.
|
|
28
|
+
const sandboxBlock = renderSandboxBlock(cfg.sandbox)
|
|
29
|
+
const deployTargetLine =
|
|
30
|
+
cfg.deployTarget && cfg.deployTarget !== 'local'
|
|
31
|
+
? ` deployTarget: ${JSON.stringify(cfg.deployTarget)},\n`
|
|
32
|
+
: ''
|
|
33
|
+
return `${header ? `${header}\n\n` : ''}export default {
|
|
34
|
+
identity: ${JSON.stringify(cfg.identity)},
|
|
35
|
+
network: ${JSON.stringify(cfg.network)},
|
|
36
|
+
storage: { network: ${JSON.stringify(cfg.storage.network)} },
|
|
37
|
+
brain: {
|
|
38
|
+
provider: ${JSON.stringify(cfg.brain.provider)},
|
|
39
|
+
model: ${JSON.stringify(cfg.brain.model)},
|
|
40
|
+
},
|
|
41
|
+
plugins: ${JSON.stringify(cfg.plugins)},
|
|
42
|
+
tools: ${JSON.stringify(cfg.tools)},
|
|
43
|
+
imports: { claudeCode: ${cfg.imports.claudeCode} },
|
|
44
|
+
${deployTargetLine}${operatorLine}${subnameLine}${sandboxBlock}}
|
|
45
|
+
`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderSandboxBlock(sandbox: PromusConfig['sandbox']): string {
|
|
49
|
+
// Phase 11: deploy-target sandbox metadata (id/providerAddress/endpoint/
|
|
50
|
+
// snapshotName) OR Phase 9.5 limb-sandbox mode = anything non-default → emit
|
|
51
|
+
// verbatim. Only surface the doc-comment template when the operator has
|
|
52
|
+
// touched neither.
|
|
53
|
+
const hasPhase11Metadata =
|
|
54
|
+
sandbox?.id || sandbox?.providerAddress || sandbox?.endpoint || sandbox?.snapshotName
|
|
55
|
+
const hasNonDefaultLimbMode = sandbox?.mode && sandbox.mode !== 'none'
|
|
56
|
+
if (hasPhase11Metadata || hasNonDefaultLimbMode) {
|
|
57
|
+
return ` sandbox: ${JSON.stringify(sandbox, null, 2).replace(/\n/g, '\n ')},\n`
|
|
58
|
+
}
|
|
59
|
+
// Fresh install: write the active default + commented examples for the
|
|
60
|
+
// other tiers. Operator can opt in by uncommenting and editing.
|
|
61
|
+
return ` sandbox: { mode: 'none' },
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Limb sandbox (Phase 9.5). Defense-in-depth beneath the permission floor:
|
|
64
|
+
// even when the modal grants 'allow session' or YOLO disables prompts,
|
|
65
|
+
// the sandbox profile/container blocks writes outside an allowlist.
|
|
66
|
+
// All shell.run / code.execute / shell.process_start spawns route through
|
|
67
|
+
// the chosen backend. fs.* and browser.* still run on the host (PathGuard
|
|
68
|
+
// applies). Override at runtime via PROMUS_SANDBOX_MODE=os|docker|none.
|
|
69
|
+
//
|
|
70
|
+
// OPTION 1: none (default): passthrough, fastest, permission floor only.
|
|
71
|
+
//
|
|
72
|
+
// OPTION 2: os (macOS sandbox-exec / seatbelt). Allows writes to agentDir +
|
|
73
|
+
// cwd + /tmp/promus-* + /var/folders. Denies reads of ~/.ssh, ~/.aws,
|
|
74
|
+
// ~/Library/Keychains, ~/.config/gcloud. Linux bubblewrap pending.
|
|
75
|
+
// sandbox: { mode: 'os' },
|
|
76
|
+
//
|
|
77
|
+
// OPTION 3: docker (long-lived container per session), every shell-class
|
|
78
|
+
// spawn through 'docker exec'. Auto-detects Docker Desktop or Podman.
|
|
79
|
+
// Default image 'nikolaik/python-nodejs:python3.11-nodejs20' (matches
|
|
80
|
+
// hermes; ~700 MB; bash + python3 + node + npm + git on standard PATH).
|
|
81
|
+
// Override with 'oven/bun:1' (~250 MB) if you only need bun/ts.
|
|
82
|
+
// 'dockerMountWorkspace: true' bind-mounts your launch cwd into
|
|
83
|
+
// /workspace (off by default for max isolation). 'dockerRuntimePath'
|
|
84
|
+
// forces a specific runtime binary. Resource caps are unset by default
|
|
85
|
+
// (container competes fairly with host work). Set them to mirror hermes'
|
|
86
|
+
// production hardening: dockerCpu=1, dockerMemoryMb=5120, dockerDiskMb=
|
|
87
|
+
// 51200 (Linux+overlay2 only). dockerNoNetwork=true blocks all internet
|
|
88
|
+
// access from the container (max paranoia for code.execute).
|
|
89
|
+
// sandbox: {
|
|
90
|
+
// mode: 'docker',
|
|
91
|
+
// dockerImage: 'nikolaik/python-nodejs:python3.11-nodejs20',
|
|
92
|
+
// dockerMountWorkspace: false,
|
|
93
|
+
// // dockerRuntimePath: '/opt/homebrew/bin/podman',
|
|
94
|
+
// // dockerCpu: 1, // CPU cores cap
|
|
95
|
+
// // dockerMemoryMb: 5120, // 5 GB memory cap
|
|
96
|
+
// // dockerDiskMb: 51200, // 50 GB disk cap (Linux + overlay2 only)
|
|
97
|
+
// // dockerNoNetwork: true, // block all network from inside container
|
|
98
|
+
// },
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function writeConfigTs(
|
|
104
|
+
path: string,
|
|
105
|
+
cfg: PromusConfig,
|
|
106
|
+
opts: RenderConfigOpts = {},
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
await mkdir(dirname(path), { recursive: true })
|
|
109
|
+
await writeFile(path, renderConfigTs(cfg, opts), 'utf8')
|
|
110
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argv dispatch. Keeps phase 2 minimal: no subcommand → chat REPL,
|
|
3
|
+
* otherwise route to commands/<name>.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const argv = process.argv.slice(2)
|
|
7
|
+
// First arg starting with `--` means the user invoked the default subcommand
|
|
8
|
+
// (chat) with flags, e.g. `promus --yolo`. Treat it as if `chat` were implicit.
|
|
9
|
+
// Exception: `--help` and `--version` are top-level commands, not chat flags.
|
|
10
|
+
const first = argv[0]
|
|
11
|
+
const isTopLevelFlag = first === '--help' || first === '--version'
|
|
12
|
+
const sub = first?.startsWith('--') && !isTopLevelFlag ? 'chat' : first
|
|
13
|
+
|
|
14
|
+
async function main(): Promise<void> {
|
|
15
|
+
switch (sub) {
|
|
16
|
+
case undefined:
|
|
17
|
+
case 'chat': {
|
|
18
|
+
const { runChat } = await import('./commands/chat')
|
|
19
|
+
const resumeIdx = argv.indexOf('--resume')
|
|
20
|
+
const resumeSession = resumeIdx >= 0 ? argv[resumeIdx + 1] : undefined
|
|
21
|
+
await runChat({ yolo: argv.includes('--yolo'), resume: resumeSession })
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
case 'init': {
|
|
25
|
+
if (argv.includes('--resume')) {
|
|
26
|
+
const { findAndLoadConfig } = await import('./config/load')
|
|
27
|
+
const loaded = await findAndLoadConfig()
|
|
28
|
+
if (!loaded) {
|
|
29
|
+
console.error('promus init --resume: no promus.config.ts found in cwd or parents.')
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
const { runResumeInit } = await import('./commands/init/resume')
|
|
33
|
+
await runResumeInit({ config: loaded.config, configPath: loaded.path })
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
const { runInit } = await import('./commands/init')
|
|
37
|
+
await runInit()
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
case 'status': {
|
|
41
|
+
const { runStatus } = await import('./commands/status')
|
|
42
|
+
await runStatus()
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
case 'logs': {
|
|
46
|
+
const { runLogs } = await import('./commands/logs')
|
|
47
|
+
const tailIdx = argv.indexOf('--tail')
|
|
48
|
+
const tail = tailIdx >= 0 ? Number(argv[tailIdx + 1]) : undefined
|
|
49
|
+
const agentIdx = argv.indexOf('--agent')
|
|
50
|
+
const agent = agentIdx >= 0 ? argv[agentIdx + 1] : undefined
|
|
51
|
+
await runLogs({ agent, tail })
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
case 'restore': {
|
|
55
|
+
const ref = argv[1]
|
|
56
|
+
if (!ref) {
|
|
57
|
+
console.error(
|
|
58
|
+
'usage: promus restore <iNFT-ref>\n ref formats:\n eip155:16661:0x<contract>:<tokenId>\n 0g-mainnet:0x<contract>:<tokenId>\n 0g-testnet:0x<contract>:<tokenId>',
|
|
59
|
+
)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
const { runRestore } = await import('./commands/restore')
|
|
63
|
+
await runRestore({ ref })
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
case 'transfer': {
|
|
67
|
+
const { parseTransferArgs, runTransfer } = await import('./commands/transfer')
|
|
68
|
+
const parsed = parseTransferArgs(argv.slice(1))
|
|
69
|
+
if ('error' in parsed) {
|
|
70
|
+
console.error(
|
|
71
|
+
`promus transfer: ${parsed.error}\n usage: promus transfer <iNFT-ref> --to <addr> [--recipient-key 0x...] [--dry-run] [--yes] [--no-purge]`,
|
|
72
|
+
)
|
|
73
|
+
process.exit(1)
|
|
74
|
+
}
|
|
75
|
+
await runTransfer(parsed)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
case 'topup': {
|
|
79
|
+
const agentIdx = argv.indexOf('--agent')
|
|
80
|
+
const computeIdx = argv.indexOf('--compute')
|
|
81
|
+
// v0.21.5: --sandbox is the canonical flag for SandboxBilling (Galileo
|
|
82
|
+
// testnet runtime fees). --provider stays as a deprecated alias for
|
|
83
|
+
// backwards compat with v0.17.1+ runbooks.
|
|
84
|
+
const sandboxIdx = argv.indexOf('--sandbox')
|
|
85
|
+
const providerIdx = argv.indexOf('--provider')
|
|
86
|
+
const visionIdx = argv.indexOf('--vision')
|
|
87
|
+
const parseAmount = (flag: string, raw: string | undefined): number | undefined => {
|
|
88
|
+
if (raw === undefined) return undefined
|
|
89
|
+
const n = Number(raw)
|
|
90
|
+
if (!Number.isFinite(n) || n <= 0 || n > 1e6) {
|
|
91
|
+
console.error(
|
|
92
|
+
`Bad amount for ${flag}: ${raw}\n Each topup flag takes an amount in 0G, not an address: ${flag} <amount>\n Examples: promus topup --compute 2 promus topup --sandbox 5 promus topup --agent 1 promus topup --vision 1`,
|
|
93
|
+
)
|
|
94
|
+
process.exit(2)
|
|
95
|
+
}
|
|
96
|
+
return n
|
|
97
|
+
}
|
|
98
|
+
const agent = parseAmount('--agent', agentIdx >= 0 ? argv[agentIdx + 1] : undefined)
|
|
99
|
+
const compute = parseAmount('--compute', computeIdx >= 0 ? argv[computeIdx + 1] : undefined)
|
|
100
|
+
const sandboxArg = parseAmount(
|
|
101
|
+
'--sandbox',
|
|
102
|
+
sandboxIdx >= 0 ? argv[sandboxIdx + 1] : undefined,
|
|
103
|
+
)
|
|
104
|
+
const providerLegacyArg = parseAmount(
|
|
105
|
+
'--provider',
|
|
106
|
+
providerIdx >= 0 ? argv[providerIdx + 1] : undefined,
|
|
107
|
+
)
|
|
108
|
+
const vision = parseAmount('--vision', visionIdx >= 0 ? argv[visionIdx + 1] : undefined)
|
|
109
|
+
if (providerLegacyArg !== undefined && sandboxArg === undefined) {
|
|
110
|
+
console.warn(
|
|
111
|
+
'[deprecated] `promus topup --provider` is renamed to `--sandbox` (Galileo testnet billing); both flags work for now but `--provider` will be removed in a future release.',
|
|
112
|
+
)
|
|
113
|
+
} else if (providerLegacyArg !== undefined && sandboxArg !== undefined) {
|
|
114
|
+
console.error(
|
|
115
|
+
'promus topup: cannot pass both --sandbox and --provider; pick one (--sandbox is canonical).',
|
|
116
|
+
)
|
|
117
|
+
process.exit(2)
|
|
118
|
+
}
|
|
119
|
+
const sandbox = sandboxArg ?? providerLegacyArg
|
|
120
|
+
const { runTopup } = await import('./commands/topup')
|
|
121
|
+
await runTopup({ agent, compute, sandbox, vision })
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
case 'model': {
|
|
125
|
+
const { runModel } = await import('./commands/model')
|
|
126
|
+
await runModel()
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
case 'sync': {
|
|
130
|
+
const { runSync } = await import('./commands/sync')
|
|
131
|
+
await runSync()
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
case 'profile': {
|
|
135
|
+
// v0.23.0: `promus profile init` seeds user/profile.md if missing, derives
|
|
136
|
+
// the operator-scoped PROFILE AES key, and either (sandbox) POSTs it to
|
|
137
|
+
// /admin/profile-key or (local) runs a sync that anchors the slot.
|
|
138
|
+
const profileSub = argv[1]
|
|
139
|
+
if (profileSub === 'init') {
|
|
140
|
+
const { runProfileInit } = await import('./commands/profile')
|
|
141
|
+
await runProfileInit()
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
console.error(
|
|
145
|
+
`Unknown profile subcommand: ${profileSub ?? '(none)'} — try 'promus profile init'`,
|
|
146
|
+
)
|
|
147
|
+
process.exit(1)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
case 'migrate-keystore': {
|
|
151
|
+
const { runMigrateKeystore } = await import('./commands/migrate-keystore')
|
|
152
|
+
await runMigrateKeystore()
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
case 'deploy': {
|
|
156
|
+
const { runDeploy } = await import('./commands/deploy')
|
|
157
|
+
await runDeploy()
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
case 'upgrade': {
|
|
161
|
+
const { parseUpgradeArgs, runUpgrade } = await import('./commands/upgrade')
|
|
162
|
+
await runUpgrade(parseUpgradeArgs(argv.slice(1)))
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
case 'resume': {
|
|
166
|
+
const yes = argv.includes('--yes') || argv.includes('-y')
|
|
167
|
+
const { runResume } = await import('./commands/resume')
|
|
168
|
+
await runResume({ yes })
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
case 'pause': {
|
|
172
|
+
const yes = argv.includes('--yes') || argv.includes('-y')
|
|
173
|
+
const { runPause } = await import('./commands/pause')
|
|
174
|
+
await runPause({ yes })
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
case 'ledger': {
|
|
178
|
+
const sub = argv[1]
|
|
179
|
+
const validSubs = ['balance', 'refund', 'retrieve', 'close'] as const
|
|
180
|
+
type Sub = (typeof validSubs)[number]
|
|
181
|
+
if (sub && !validSubs.includes(sub as Sub)) {
|
|
182
|
+
console.error(
|
|
183
|
+
`promus ledger: unknown subcommand '${sub}' (expected: ${validSubs.join(' | ')})`,
|
|
184
|
+
)
|
|
185
|
+
process.exit(1)
|
|
186
|
+
}
|
|
187
|
+
const chosen = (sub ?? 'balance') as Sub
|
|
188
|
+
const amountIdx = argv.indexOf('--amount')
|
|
189
|
+
const amount = amountIdx >= 0 ? Number(argv[amountIdx + 1]) : undefined
|
|
190
|
+
const all = argv.includes('--all')
|
|
191
|
+
const yes = argv.includes('--yes') || argv.includes('-y')
|
|
192
|
+
const { runLedger } = await import('./commands/ledger')
|
|
193
|
+
await runLedger({ sub: chosen, amount, all, yes })
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
case 'balance': {
|
|
197
|
+
const agentIdx = argv.indexOf('--agent')
|
|
198
|
+
const agent = agentIdx >= 0 ? argv[agentIdx + 1] : undefined
|
|
199
|
+
const { runBalance } = await import('./commands/balance')
|
|
200
|
+
await runBalance({ agent })
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
case 'drain': {
|
|
204
|
+
const toIdx = argv.indexOf('--to')
|
|
205
|
+
const to = toIdx >= 0 ? argv[toIdx + 1] : undefined
|
|
206
|
+
const yes = argv.includes('--yes') || argv.includes('-y')
|
|
207
|
+
const { runDrain } = await import('./commands/drain')
|
|
208
|
+
await runDrain({ to, yes })
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
case 'telegram': {
|
|
212
|
+
const { parseTelegramArgs, runTelegram } = await import('./commands/telegram')
|
|
213
|
+
const parsed = parseTelegramArgs(argv.slice(1))
|
|
214
|
+
if ('error' in parsed) {
|
|
215
|
+
console.error(`promus telegram: ${parsed.error}`)
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
await runTelegram(parsed)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
case 'pairing': {
|
|
222
|
+
const { parsePairingArgs, runPairing } = await import('./commands/pairing')
|
|
223
|
+
const parsed = parsePairingArgs(argv.slice(1))
|
|
224
|
+
if ('error' in parsed) {
|
|
225
|
+
console.error(`promus pairing: ${parsed.error}`)
|
|
226
|
+
process.exit(1)
|
|
227
|
+
}
|
|
228
|
+
await runPairing(parsed)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
case 'admin': {
|
|
232
|
+
const { parseAdminArgs, runAdmin } = await import('./commands/admin')
|
|
233
|
+
const parsed = parseAdminArgs(argv.slice(1))
|
|
234
|
+
if ('error' in parsed) {
|
|
235
|
+
console.error(`promus admin: ${parsed.error}`)
|
|
236
|
+
process.exit(1)
|
|
237
|
+
}
|
|
238
|
+
await runAdmin(parsed)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
case 'gateway': {
|
|
242
|
+
const { parseGatewayArgs, runGateway } = await import('./commands/gateway')
|
|
243
|
+
const parsed = parseGatewayArgs(argv.slice(1))
|
|
244
|
+
if ('error' in parsed) {
|
|
245
|
+
console.error(`promus gateway: ${parsed.error}`)
|
|
246
|
+
process.exit(1)
|
|
247
|
+
}
|
|
248
|
+
await runGateway(parsed)
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
case 'inspect': {
|
|
252
|
+
const { runInspect, isValidSlot } = await import('./commands/inspect')
|
|
253
|
+
const remaining = argv.slice(1)
|
|
254
|
+
const positional: string[] = []
|
|
255
|
+
const flags: Record<string, string | boolean> = {}
|
|
256
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
257
|
+
const a = remaining[i]!
|
|
258
|
+
if (a === '--raw' || a === '--diff' || a === '--json' || a === '--full') {
|
|
259
|
+
flags[a.slice(2)] = true
|
|
260
|
+
} else if (a === '--slot' || a === '--tx' || a === '--out') {
|
|
261
|
+
const v = remaining[++i]
|
|
262
|
+
if (!v) {
|
|
263
|
+
console.error(`promus inspect: ${a} requires a value`)
|
|
264
|
+
process.exit(1)
|
|
265
|
+
}
|
|
266
|
+
flags[a.slice(2)] = v
|
|
267
|
+
} else if (a.startsWith('--')) {
|
|
268
|
+
console.error(`promus inspect: unknown flag ${a}`)
|
|
269
|
+
process.exit(1)
|
|
270
|
+
} else {
|
|
271
|
+
positional.push(a)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (positional.length > 1) {
|
|
275
|
+
console.error('promus inspect: at most one positional ref allowed')
|
|
276
|
+
process.exit(1)
|
|
277
|
+
}
|
|
278
|
+
const slotFlag = flags.slot
|
|
279
|
+
let slotName: import('@promus/core').IntelligentDataSlot | undefined
|
|
280
|
+
if (typeof slotFlag === 'string') {
|
|
281
|
+
if (!isValidSlot(slotFlag)) {
|
|
282
|
+
console.error(
|
|
283
|
+
'promus inspect: --slot must be one of memory-index, identity, persona, profile, keystore, activity-log',
|
|
284
|
+
)
|
|
285
|
+
process.exit(1)
|
|
286
|
+
}
|
|
287
|
+
slotName = slotFlag
|
|
288
|
+
}
|
|
289
|
+
const txFlag = flags.tx
|
|
290
|
+
if (typeof txFlag === 'string' && !/^0x[0-9a-fA-F]{64}$/.test(txFlag)) {
|
|
291
|
+
console.error('promus inspect: --tx must be a 32-byte hex hash')
|
|
292
|
+
process.exit(1)
|
|
293
|
+
}
|
|
294
|
+
await runInspect({
|
|
295
|
+
ref: positional[0],
|
|
296
|
+
slot: slotName,
|
|
297
|
+
tx: typeof txFlag === 'string' ? (txFlag as `0x${string}`) : undefined,
|
|
298
|
+
raw: flags.raw === true,
|
|
299
|
+
diff: flags.diff === true,
|
|
300
|
+
json: flags.json === true,
|
|
301
|
+
full: flags.full === true,
|
|
302
|
+
out: typeof flags.out === 'string' ? flags.out : undefined,
|
|
303
|
+
})
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
case '-h':
|
|
307
|
+
case '--help':
|
|
308
|
+
case 'help': {
|
|
309
|
+
printHelp()
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
case '-v':
|
|
313
|
+
case '--version':
|
|
314
|
+
case 'version': {
|
|
315
|
+
const { resolveCliVersion } = await import('./util/cli-version')
|
|
316
|
+
const v = await resolveCliVersion()
|
|
317
|
+
console.log(v)
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
default: {
|
|
321
|
+
console.log(`Unknown command: ${sub}`)
|
|
322
|
+
printHelp()
|
|
323
|
+
process.exit(1)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function printHelp(): void {
|
|
329
|
+
console.log(
|
|
330
|
+
[
|
|
331
|
+
'promus: sovereign agent harness CLI',
|
|
332
|
+
'',
|
|
333
|
+
'Commands:',
|
|
334
|
+
' promus init bootstrap a new agent identity + keystore',
|
|
335
|
+
' promus [--yolo] interactive chat with your agent (default; --yolo skips approvals)',
|
|
336
|
+
' promus status show agent + wallet + config state',
|
|
337
|
+
' promus logs tail the activity log (flags: --tail N, --agent <id>)',
|
|
338
|
+
' promus restore <ref> recover an agent from an iNFT (ref: eip155:421614:0x..:N)',
|
|
339
|
+
' promus transfer <ref> transfer iNFT to a new operator with re-encrypted keystore',
|
|
340
|
+
' flags: --to <addr>, --recipient-key <hex>, --oracle-key <hex>,',
|
|
341
|
+
' --dry-run, --yes, --no-purge',
|
|
342
|
+
' promus balance full economic position: agent EOA + operator balance',
|
|
343
|
+
" flags: --agent <addr> (defaults to active config's agent)",
|
|
344
|
+
' promus drain --to <addr> sweep agent EOA balance to address (default: operator)',
|
|
345
|
+
' promus sync force flush memory + activity-log to IPFS + anchor on chain',
|
|
346
|
+
' promus migrate-keystore upgrade v0.5.0 passphrase keystore to v0.6 operator-wallet',
|
|
347
|
+
' promus upgrade [<ref>] roll harness to new ref in place (default: latest published release)',
|
|
348
|
+
' flags: --ref vX.Y.Z',
|
|
349
|
+
' promus telegram <sub> configure phone-DM gateway (subs: setup | status | remove)',
|
|
350
|
+
' flags: --yes (skip remove confirmation)',
|
|
351
|
+
' promus pairing <sub> manage DM pairing approvals (subs: list | approve | revoke | clear-pending)',
|
|
352
|
+
' usage: promus pairing approve telegram <code>',
|
|
353
|
+
' promus gateway <sub> always-on agent gateway daemon (subs: run | start | stop | restart | status | logs)',
|
|
354
|
+
' run = foreground, start = bg + Touch ID, stop = SIGTERM via lock',
|
|
355
|
+
' promus inspect [ref] audit on-chain memory slots (flags: --slot, --tx, --raw, --diff, --json, --full, --out <dir>)',
|
|
356
|
+
' promus version print CLI version (aliases: --version, -v)',
|
|
357
|
+
' promus help show this message (aliases: --help, -h)',
|
|
358
|
+
'',
|
|
359
|
+
'(`promus` remains a working alias for `promus`.)',
|
|
360
|
+
'',
|
|
361
|
+
].join('\n'),
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
main()
|
|
366
|
+
.then(() => {
|
|
367
|
+
// Force-exit on success because some 0G SDKs (Storage Indexer, Compute
|
|
368
|
+
// broker, WalletConnect relay) leak open handles (websockets, heartbeat
|
|
369
|
+
// timers) that we don't have hooks to drain. Without this, one-shot
|
|
370
|
+
// commands like `promus init` would hang at the prompt indefinitely after
|
|
371
|
+
// their work completed. `chat` returns only when the user actually quits,
|
|
372
|
+
// so this also gives chat a clean exit. Exit code 0 = normal success.
|
|
373
|
+
process.exit(0)
|
|
374
|
+
})
|
|
375
|
+
.catch(e => {
|
|
376
|
+
console.error('fatal:', (e as Error).message)
|
|
377
|
+
process.exit(1)
|
|
378
|
+
})
|