@otonix/cli 1.0.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 +182 -0
- package/dist/cli.js +539 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @otonix/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for the [Otonix](https://otonix.tech) sovereign compute platform.
|
|
4
|
+
|
|
5
|
+
Initialize, register, and manage autonomous AI agents from your terminal.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @otonix/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @otonix/cli <command>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Generate an API key (requires dashboard token)
|
|
23
|
+
otonix keygen my-vps-key
|
|
24
|
+
|
|
25
|
+
# 2. Configure the CLI
|
|
26
|
+
otonix init
|
|
27
|
+
|
|
28
|
+
# 3. Register your agent
|
|
29
|
+
otonix register --name my-agent --model claude-opus-4-6
|
|
30
|
+
|
|
31
|
+
# 4. Start heartbeat loop
|
|
32
|
+
otonix heartbeat:loop
|
|
33
|
+
|
|
34
|
+
# 5. Log actions
|
|
35
|
+
otonix log "Trade executed BTC/USDC" --category trading
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
### Setup
|
|
41
|
+
|
|
42
|
+
| Command | Description |
|
|
43
|
+
|---------|-------------|
|
|
44
|
+
| `otonix init` | Configure API key and endpoint interactively |
|
|
45
|
+
| `otonix keygen <name>` | Generate a new API key (requires dashboard token) |
|
|
46
|
+
| `otonix whoami` | Show current configuration |
|
|
47
|
+
|
|
48
|
+
### Agent Management
|
|
49
|
+
|
|
50
|
+
| Command | Description |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| `otonix register` | Register an agent on this VPS |
|
|
53
|
+
| `otonix status` | Show current agent status |
|
|
54
|
+
| `otonix heartbeat` | Send a single heartbeat ping |
|
|
55
|
+
| `otonix heartbeat:loop` | Start continuous heartbeat (Ctrl+C to stop) |
|
|
56
|
+
| `otonix log <message>` | Log an agent action |
|
|
57
|
+
| `otonix actions` | Show agent action log |
|
|
58
|
+
|
|
59
|
+
### Infrastructure
|
|
60
|
+
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `otonix agents` | List all connected agents |
|
|
64
|
+
| `otonix sandboxes` | List VPS sandboxes |
|
|
65
|
+
| `otonix domains` | List registered domains |
|
|
66
|
+
|
|
67
|
+
### Platform
|
|
68
|
+
|
|
69
|
+
| Command | Description |
|
|
70
|
+
|---------|-------------|
|
|
71
|
+
| `otonix engine` | Show autonomic engine status |
|
|
72
|
+
| `otonix x402` | Show x402 payment configuration |
|
|
73
|
+
|
|
74
|
+
## Command Options
|
|
75
|
+
|
|
76
|
+
### `otonix register`
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
otonix register --name my-agent --model claude-opus-4-6 --wallet 0x... --interval 60
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Option | Default | Description |
|
|
83
|
+
|--------|---------|-------------|
|
|
84
|
+
| `--name` | (interactive) | Agent name |
|
|
85
|
+
| `--model` | `claude-opus-4-6` | AI model |
|
|
86
|
+
| `--wallet` | — | Wallet address for payments |
|
|
87
|
+
| `--interval` | `60` | Heartbeat interval in seconds |
|
|
88
|
+
|
|
89
|
+
### `otonix log`
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
otonix log "Task completed" --category compute --details "Processed 1000 records"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Option | Default | Description |
|
|
96
|
+
|--------|---------|-------------|
|
|
97
|
+
| `--category` | `system` | Action category |
|
|
98
|
+
| `--details` | — | Additional details |
|
|
99
|
+
|
|
100
|
+
### `otonix actions`
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
otonix actions --limit 50
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Option | Default | Description |
|
|
107
|
+
|--------|---------|-------------|
|
|
108
|
+
| `--limit` | `20` | Number of actions to show |
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
Config is stored at `~/.otonix/config.json` with file permissions `600`.
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"apiKey": "otonix_xxxx",
|
|
117
|
+
"endpoint": "https://app.otonix.tech",
|
|
118
|
+
"agentId": "uuid-xxxx",
|
|
119
|
+
"agentName": "my-agent"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Example Session
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
$ otonix init
|
|
127
|
+
Otonix CLI Setup
|
|
128
|
+
|
|
129
|
+
Endpoint [https://app.otonix.tech]:
|
|
130
|
+
API Key (otonix_xxx): otonix_a1b2c3d4e5f6...
|
|
131
|
+
|
|
132
|
+
Config saved to /root/.otonix/config.json
|
|
133
|
+
Authenticated successfully.
|
|
134
|
+
|
|
135
|
+
$ otonix register --name sentinel-01
|
|
136
|
+
Agent Registered:
|
|
137
|
+
ID: e2998be4-b77c-495e-a535-a6e4ca9dc768
|
|
138
|
+
Name: sentinel-01
|
|
139
|
+
Model: claude-opus-4-6
|
|
140
|
+
VPS IP: 10.0.1.1
|
|
141
|
+
Tier: active
|
|
142
|
+
Interval: 60s
|
|
143
|
+
|
|
144
|
+
$ otonix heartbeat
|
|
145
|
+
Heartbeat sent — sentinel-01 [active] tier:active credits:$50.00
|
|
146
|
+
|
|
147
|
+
$ otonix log "Deployed monitoring stack" --category infra
|
|
148
|
+
Logged: [infra] Deployed monitoring stack (completed)
|
|
149
|
+
|
|
150
|
+
$ otonix status
|
|
151
|
+
Agent Status:
|
|
152
|
+
ID: e2998be4-b77c-495e-a535-a6e4ca9dc768
|
|
153
|
+
Name: sentinel-01
|
|
154
|
+
Status: active
|
|
155
|
+
Tier: full
|
|
156
|
+
Credits: $50.00
|
|
157
|
+
Model: claude-opus-4-6
|
|
158
|
+
VPS IP: 10.0.1.1
|
|
159
|
+
Heartbeat: 12s ago
|
|
160
|
+
Actions: 3
|
|
161
|
+
|
|
162
|
+
$ otonix engine
|
|
163
|
+
Autonomic Engine Status:
|
|
164
|
+
Running: yes
|
|
165
|
+
Last Cycle: 45s ago
|
|
166
|
+
Tier Updates: 0
|
|
167
|
+
Heal Attempts: 0
|
|
168
|
+
Renewals: 0/0
|
|
169
|
+
Cherry Servers: configured
|
|
170
|
+
Vercel Domains: configured
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Links
|
|
174
|
+
|
|
175
|
+
- [Platform](https://app.otonix.tech)
|
|
176
|
+
- [Website](https://otonix.tech)
|
|
177
|
+
- [SDK](https://www.npmjs.com/package/@otonix/sdk)
|
|
178
|
+
- [GitHub](https://github.com/otonix-ai)
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_sdk = require("@otonix/sdk");
|
|
28
|
+
var fs = __toESM(require("fs"));
|
|
29
|
+
var path = __toESM(require("path"));
|
|
30
|
+
var readline = __toESM(require("readline"));
|
|
31
|
+
var VERSION = "1.0.0";
|
|
32
|
+
var CONFIG_DIR = path.join(process.env.HOME || "~", ".otonix");
|
|
33
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
34
|
+
function loadConfig() {
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
37
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function saveConfig(config) {
|
|
44
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
45
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
48
|
+
fs.chmodSync(CONFIG_FILE, 384);
|
|
49
|
+
}
|
|
50
|
+
function getClient() {
|
|
51
|
+
const config = loadConfig();
|
|
52
|
+
if (!config) {
|
|
53
|
+
console.error("Error: Not configured. Run 'otonix init' first.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
return new import_sdk.OtonixClient({ apiKey: config.apiKey, endpoint: config.endpoint });
|
|
57
|
+
}
|
|
58
|
+
function prompt(question) {
|
|
59
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
rl.question(question, (answer) => {
|
|
62
|
+
rl.close();
|
|
63
|
+
resolve(answer.trim());
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function printTable(rows) {
|
|
68
|
+
if (!rows.length) return;
|
|
69
|
+
const keys = Object.keys(rows[0]);
|
|
70
|
+
const widths = keys.map(
|
|
71
|
+
(k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "\u2014").length))
|
|
72
|
+
);
|
|
73
|
+
const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
|
|
74
|
+
const sep = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
|
|
75
|
+
console.log(` ${header}`);
|
|
76
|
+
console.log(` ${sep}`);
|
|
77
|
+
rows.forEach((row) => {
|
|
78
|
+
const line = keys.map((k, i) => String(row[k] ?? "\u2014").padEnd(widths[i])).join(" ");
|
|
79
|
+
console.log(` ${line}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function formatTime(dateStr) {
|
|
83
|
+
if (!dateStr) return "\u2014";
|
|
84
|
+
const ago = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
|
|
85
|
+
if (ago < 60) return `${ago}s ago`;
|
|
86
|
+
if (ago < 3600) return `${Math.floor(ago / 60)}m ago`;
|
|
87
|
+
if (ago < 86400) return `${Math.floor(ago / 3600)}h ago`;
|
|
88
|
+
return `${Math.floor(ago / 86400)}d ago`;
|
|
89
|
+
}
|
|
90
|
+
var HELP = `
|
|
91
|
+
otonix \u2014 CLI for the Otonix sovereign compute platform
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
otonix <command> [options]
|
|
95
|
+
|
|
96
|
+
Commands:
|
|
97
|
+
init Configure API key and endpoint
|
|
98
|
+
keygen <name> Generate a new API key
|
|
99
|
+
register Register an agent on this VPS
|
|
100
|
+
heartbeat Send a heartbeat ping
|
|
101
|
+
heartbeat:loop Start continuous heartbeat loop
|
|
102
|
+
status Show agent status
|
|
103
|
+
agents List all connected agents
|
|
104
|
+
actions [--limit N] Show agent action log
|
|
105
|
+
log <message> Log an agent action
|
|
106
|
+
domains List registered domains
|
|
107
|
+
sandboxes List VPS sandboxes
|
|
108
|
+
engine Show autonomic engine status
|
|
109
|
+
x402 Show x402 payment config
|
|
110
|
+
whoami Show current configuration
|
|
111
|
+
version Show CLI version
|
|
112
|
+
help Show this help
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
otonix init
|
|
116
|
+
otonix keygen my-vps-key
|
|
117
|
+
otonix register --name my-agent --model claude-opus-4-6
|
|
118
|
+
otonix heartbeat:loop
|
|
119
|
+
otonix log "Trade executed BTC/USDC" --category trading
|
|
120
|
+
otonix actions --limit 20
|
|
121
|
+
`;
|
|
122
|
+
async function cmdInit() {
|
|
123
|
+
console.log("\n Otonix CLI Setup\n");
|
|
124
|
+
const endpoint = await prompt(" Endpoint [https://app.otonix.tech]: ") || "https://app.otonix.tech";
|
|
125
|
+
const apiKey = await prompt(" API Key (otonix_xxx): ");
|
|
126
|
+
if (!apiKey.startsWith("otonix_")) {
|
|
127
|
+
console.error(" Error: API key must start with 'otonix_'");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
const client = new import_sdk.OtonixClient({ apiKey, endpoint });
|
|
131
|
+
try {
|
|
132
|
+
const auth = await client.getAuthStatus();
|
|
133
|
+
if (!auth.authenticated) {
|
|
134
|
+
console.error(" Error: Invalid API key \u2014 authentication failed.");
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(` Error: Could not connect \u2014 ${err.message}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
saveConfig({ apiKey, endpoint });
|
|
142
|
+
console.log(`
|
|
143
|
+
Config saved to ${CONFIG_FILE}`);
|
|
144
|
+
console.log(" Authenticated successfully.\n");
|
|
145
|
+
}
|
|
146
|
+
async function cmdKeygen(name) {
|
|
147
|
+
if (!name) {
|
|
148
|
+
console.error(" Usage: otonix keygen <name>");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
const config = loadConfig();
|
|
152
|
+
const endpoint = config?.endpoint || "https://app.otonix.tech";
|
|
153
|
+
const dashToken = await prompt(" Dashboard Token (SESSION_SECRET): ");
|
|
154
|
+
const res = await fetch(`${endpoint}/api/keys/generate`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
"X-Dashboard-Token": dashToken
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({ name })
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
164
|
+
console.error(` Error: ${err.error || err.message || "Failed to generate key"}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const key = await res.json();
|
|
168
|
+
console.log(`
|
|
169
|
+
API Key Generated:`);
|
|
170
|
+
console.log(` Name: ${key.name}`);
|
|
171
|
+
console.log(` Key: ${key.key}`);
|
|
172
|
+
console.log(` Status: ${key.status}`);
|
|
173
|
+
console.log(`
|
|
174
|
+
Save this key \u2014 it cannot be retrieved again.
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
async function cmdRegister(args) {
|
|
178
|
+
const client = getClient();
|
|
179
|
+
const config = loadConfig();
|
|
180
|
+
let name = "";
|
|
181
|
+
let model = "claude-opus-4-6";
|
|
182
|
+
let wallet = "";
|
|
183
|
+
let interval = 60;
|
|
184
|
+
for (let i = 0; i < args.length; i++) {
|
|
185
|
+
if (args[i] === "--name" && args[i + 1]) name = args[++i];
|
|
186
|
+
else if (args[i] === "--model" && args[i + 1]) model = args[++i];
|
|
187
|
+
else if (args[i] === "--wallet" && args[i + 1]) wallet = args[++i];
|
|
188
|
+
else if (args[i] === "--interval" && args[i + 1]) interval = parseInt(args[++i]);
|
|
189
|
+
}
|
|
190
|
+
if (!name) name = await prompt(" Agent name: ");
|
|
191
|
+
if (!name) {
|
|
192
|
+
console.error(" Error: name is required");
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
const ip = await getVpsIp();
|
|
196
|
+
try {
|
|
197
|
+
const agent = await client.register({
|
|
198
|
+
name,
|
|
199
|
+
model,
|
|
200
|
+
vpsIp: ip,
|
|
201
|
+
walletAddress: wallet || void 0,
|
|
202
|
+
heartbeatInterval: interval
|
|
203
|
+
});
|
|
204
|
+
saveConfig({ ...config, agentId: agent.id, agentName: agent.name });
|
|
205
|
+
console.log(`
|
|
206
|
+
Agent Registered:`);
|
|
207
|
+
console.log(` ID: ${agent.id}`);
|
|
208
|
+
console.log(` Name: ${agent.name}`);
|
|
209
|
+
console.log(` Model: ${agent.model}`);
|
|
210
|
+
console.log(` VPS IP: ${agent.vpsIp}`);
|
|
211
|
+
console.log(` Tier: ${agent.survivalTier}`);
|
|
212
|
+
console.log(` Interval: ${agent.heartbeatInterval}s
|
|
213
|
+
`);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.error(` Error: ${err.message}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function getVpsIp() {
|
|
220
|
+
try {
|
|
221
|
+
const res = await fetch("https://api.ipify.org?format=json", { signal: AbortSignal.timeout(5e3) });
|
|
222
|
+
const data = await res.json();
|
|
223
|
+
return data.ip;
|
|
224
|
+
} catch {
|
|
225
|
+
return "0.0.0.0";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function cmdHeartbeat() {
|
|
229
|
+
const client = getClient();
|
|
230
|
+
const config = loadConfig();
|
|
231
|
+
if (!config.agentId) {
|
|
232
|
+
console.error(" Error: No agent registered. Run 'otonix register' first.");
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const agent = await client.heartbeat(config.agentId);
|
|
237
|
+
console.log(` Heartbeat sent \u2014 ${agent.name} [${agent.status}] tier:${agent.survivalTier} credits:$${agent.credits.toFixed(2)}`);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error(` Heartbeat failed: ${err.message}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function cmdHeartbeatLoop() {
|
|
244
|
+
const client = getClient();
|
|
245
|
+
const config = loadConfig();
|
|
246
|
+
if (!config.agentId) {
|
|
247
|
+
console.error(" Error: No agent registered. Run 'otonix register' first.");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
const agent = await client.getAgent(config.agentId);
|
|
251
|
+
const interval = agent.heartbeatInterval || 60;
|
|
252
|
+
console.log(` Starting heartbeat loop for ${agent.name} (every ${interval}s)`);
|
|
253
|
+
console.log(" Press Ctrl+C to stop.\n");
|
|
254
|
+
const hb = client.createHeartbeatLoop(config.agentId, interval);
|
|
255
|
+
process.on("SIGINT", () => {
|
|
256
|
+
hb.stop();
|
|
257
|
+
console.log("\n Heartbeat stopped.");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
});
|
|
260
|
+
setInterval(() => {
|
|
261
|
+
}, 1e3);
|
|
262
|
+
}
|
|
263
|
+
async function cmdStatus() {
|
|
264
|
+
const client = getClient();
|
|
265
|
+
const config = loadConfig();
|
|
266
|
+
if (!config.agentId) {
|
|
267
|
+
console.error(" Error: No agent registered. Run 'otonix register' first.");
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const agent = await client.getAgent(config.agentId);
|
|
272
|
+
console.log(`
|
|
273
|
+
Agent Status:`);
|
|
274
|
+
console.log(` ID: ${agent.id}`);
|
|
275
|
+
console.log(` Name: ${agent.name}`);
|
|
276
|
+
console.log(` Status: ${agent.status}`);
|
|
277
|
+
console.log(` Tier: ${agent.survivalTier}`);
|
|
278
|
+
console.log(` Credits: $${agent.credits.toFixed(2)}`);
|
|
279
|
+
console.log(` Model: ${agent.model}`);
|
|
280
|
+
console.log(` VPS IP: ${agent.vpsIp || "\u2014"}`);
|
|
281
|
+
console.log(` Wallet: ${agent.walletAddress || "\u2014"}`);
|
|
282
|
+
console.log(` Heartbeat: ${formatTime(agent.lastHeartbeat)}`);
|
|
283
|
+
console.log(` Interval: ${agent.heartbeatInterval}s`);
|
|
284
|
+
console.log(` Actions: ${agent.totalActions}`);
|
|
285
|
+
console.log(` Replicas: ${agent.childrenCount}`);
|
|
286
|
+
console.log(` Registered: ${new Date(agent.createdAt).toLocaleString()}
|
|
287
|
+
`);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error(` Error: ${err.message}`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function cmdAgents() {
|
|
294
|
+
const client = getClient();
|
|
295
|
+
const agents = await client.listAgents();
|
|
296
|
+
if (!agents.length) {
|
|
297
|
+
console.log(" No agents connected.");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
console.log(`
|
|
301
|
+
Connected Agents (${agents.length}):
|
|
302
|
+
`);
|
|
303
|
+
printTable(agents.map((a) => ({
|
|
304
|
+
ID: a.id.slice(0, 8),
|
|
305
|
+
Name: a.name,
|
|
306
|
+
Status: a.status,
|
|
307
|
+
Tier: a.survivalTier,
|
|
308
|
+
Credits: `$${a.credits.toFixed(2)}`,
|
|
309
|
+
Heartbeat: formatTime(a.lastHeartbeat),
|
|
310
|
+
Actions: a.totalActions
|
|
311
|
+
})));
|
|
312
|
+
console.log();
|
|
313
|
+
}
|
|
314
|
+
async function cmdActions(args) {
|
|
315
|
+
const client = getClient();
|
|
316
|
+
const config = loadConfig();
|
|
317
|
+
if (!config.agentId) {
|
|
318
|
+
console.error(" Error: No agent registered. Run 'otonix register' first.");
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
let limit = 20;
|
|
322
|
+
for (let i = 0; i < args.length; i++) {
|
|
323
|
+
if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i]);
|
|
324
|
+
}
|
|
325
|
+
const actions = await client.getAgentActions(config.agentId);
|
|
326
|
+
const display = actions.slice(0, limit);
|
|
327
|
+
if (!display.length) {
|
|
328
|
+
console.log(" No actions recorded.");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
console.log(`
|
|
332
|
+
Agent Actions (${display.length} of ${actions.length}):
|
|
333
|
+
`);
|
|
334
|
+
printTable(display.map((a) => ({
|
|
335
|
+
Time: formatTime(a.createdAt),
|
|
336
|
+
Category: a.category,
|
|
337
|
+
Action: a.action.length > 50 ? a.action.slice(0, 47) + "..." : a.action,
|
|
338
|
+
Status: a.status,
|
|
339
|
+
Auto: a.autonomous ? "yes" : "no"
|
|
340
|
+
})));
|
|
341
|
+
console.log();
|
|
342
|
+
}
|
|
343
|
+
async function cmdLog(args) {
|
|
344
|
+
const client = getClient();
|
|
345
|
+
const config = loadConfig();
|
|
346
|
+
if (!config.agentId) {
|
|
347
|
+
console.error(" Error: No agent registered. Run 'otonix register' first.");
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
let message = "";
|
|
351
|
+
let category = "system";
|
|
352
|
+
let details = "";
|
|
353
|
+
const positional = [];
|
|
354
|
+
for (let i = 0; i < args.length; i++) {
|
|
355
|
+
if (args[i] === "--category" && args[i + 1]) category = args[++i];
|
|
356
|
+
else if (args[i] === "--details" && args[i + 1]) details = args[++i];
|
|
357
|
+
else positional.push(args[i]);
|
|
358
|
+
}
|
|
359
|
+
message = positional.join(" ");
|
|
360
|
+
if (!message) {
|
|
361
|
+
console.error(' Usage: otonix log "message" [--category system] [--details "..."]');
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const action = await client.logAction(config.agentId, {
|
|
366
|
+
action: message,
|
|
367
|
+
category,
|
|
368
|
+
details: details || void 0
|
|
369
|
+
});
|
|
370
|
+
console.log(` Logged: [${action.category}] ${action.action} (${action.status})`);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.error(` Error: ${err.message}`);
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async function cmdDomains() {
|
|
377
|
+
const client = getClient();
|
|
378
|
+
const domains = await client.listDomains();
|
|
379
|
+
if (!domains.length) {
|
|
380
|
+
console.log(" No domains registered.");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
console.log(`
|
|
384
|
+
Registered Domains (${domains.length}):
|
|
385
|
+
`);
|
|
386
|
+
printTable(domains.map((d) => ({
|
|
387
|
+
Name: d.name,
|
|
388
|
+
Status: d.status,
|
|
389
|
+
AutoRenew: d.autoRenew ? "yes" : "no",
|
|
390
|
+
Expires: d.expiresAt ? new Date(d.expiresAt).toLocaleDateString() : "\u2014",
|
|
391
|
+
DNS: d.dnsRecords?.length || 0,
|
|
392
|
+
Agent: d.agentId?.slice(0, 8) || "\u2014"
|
|
393
|
+
})));
|
|
394
|
+
console.log();
|
|
395
|
+
}
|
|
396
|
+
async function cmdSandboxes() {
|
|
397
|
+
const client = getClient();
|
|
398
|
+
const sandboxes = await client.listSandboxes();
|
|
399
|
+
if (!sandboxes.length) {
|
|
400
|
+
console.log(" No sandboxes provisioned.");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
console.log(`
|
|
404
|
+
VPS Sandboxes (${sandboxes.length}):
|
|
405
|
+
`);
|
|
406
|
+
printTable(sandboxes.map((s) => ({
|
|
407
|
+
ID: s.id.slice(0, 8),
|
|
408
|
+
Name: s.name,
|
|
409
|
+
Status: s.status,
|
|
410
|
+
OS: s.os,
|
|
411
|
+
CPU: `${s.cpu} vCPU`,
|
|
412
|
+
RAM: `${s.memory}MB`,
|
|
413
|
+
Region: s.region,
|
|
414
|
+
IP: s.ipAddress || "\u2014"
|
|
415
|
+
})));
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
async function cmdEngine() {
|
|
419
|
+
const client = getClient();
|
|
420
|
+
const status = await client.getAutonomicStatus();
|
|
421
|
+
console.log(`
|
|
422
|
+
Autonomic Engine Status:`);
|
|
423
|
+
console.log(` Running: ${status.running ? "yes" : "no"}`);
|
|
424
|
+
console.log(` Last Cycle: ${formatTime(status.lastRunAt)}`);
|
|
425
|
+
console.log(` Cycle Interval: ${status.cycleIntervalSeconds}s`);
|
|
426
|
+
console.log(` Tier Updates: ${status.totalTierUpdates}`);
|
|
427
|
+
console.log(` Heal Attempts: ${status.totalHealingAttempts}`);
|
|
428
|
+
console.log(` Heal Success: ${status.totalHealingSuccesses}`);
|
|
429
|
+
console.log(` Renewals: ${status.totalRenewalSuccesses}/${status.totalRenewalAttempts}`);
|
|
430
|
+
console.log(` Cooldowns: ${status.activeCooldowns}`);
|
|
431
|
+
console.log(` Cherry Servers: ${status.cherryServersConfigured ? "configured" : "not configured"}`);
|
|
432
|
+
console.log(` Vercel Domains: ${status.vercelDomainsConfigured ? "configured" : "not configured"}`);
|
|
433
|
+
console.log(`
|
|
434
|
+
Tier Thresholds:`);
|
|
435
|
+
for (const [key, value] of Object.entries(status.tierThresholds)) {
|
|
436
|
+
console.log(` ${status.tierLabels[key] || key}: $${value}+`);
|
|
437
|
+
}
|
|
438
|
+
console.log();
|
|
439
|
+
}
|
|
440
|
+
async function cmdX402() {
|
|
441
|
+
const client = getClient();
|
|
442
|
+
const config = await client.getX402Config();
|
|
443
|
+
console.log(`
|
|
444
|
+
x402 Payment Config:`);
|
|
445
|
+
console.log(` Treasury: ${config.treasuryAddress}`);
|
|
446
|
+
console.log(` USDC: ${config.usdcContract}`);
|
|
447
|
+
console.log(` Chain ID: ${config.chainId}`);
|
|
448
|
+
console.log(` Network: ${config.network}`);
|
|
449
|
+
console.log(` Facilitator: ${config.facilitatorUrl}
|
|
450
|
+
`);
|
|
451
|
+
}
|
|
452
|
+
function cmdWhoami() {
|
|
453
|
+
const config = loadConfig();
|
|
454
|
+
if (!config) {
|
|
455
|
+
console.log(" Not configured. Run 'otonix init' first.");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
console.log(`
|
|
459
|
+
Current Config:`);
|
|
460
|
+
console.log(` Endpoint: ${config.endpoint}`);
|
|
461
|
+
console.log(` API Key: ${config.apiKey.slice(0, 12)}...${config.apiKey.slice(-4)}`);
|
|
462
|
+
console.log(` Agent ID: ${config.agentId || "not registered"}`);
|
|
463
|
+
console.log(` Agent: ${config.agentName || "\u2014"}`);
|
|
464
|
+
console.log(` Config: ${CONFIG_FILE}
|
|
465
|
+
`);
|
|
466
|
+
}
|
|
467
|
+
async function main() {
|
|
468
|
+
const args = process.argv.slice(2);
|
|
469
|
+
const command = args[0] || "help";
|
|
470
|
+
const rest = args.slice(1);
|
|
471
|
+
try {
|
|
472
|
+
switch (command) {
|
|
473
|
+
case "init":
|
|
474
|
+
await cmdInit();
|
|
475
|
+
break;
|
|
476
|
+
case "keygen":
|
|
477
|
+
await cmdKeygen(rest[0]);
|
|
478
|
+
break;
|
|
479
|
+
case "register":
|
|
480
|
+
await cmdRegister(rest);
|
|
481
|
+
break;
|
|
482
|
+
case "heartbeat":
|
|
483
|
+
await cmdHeartbeat();
|
|
484
|
+
break;
|
|
485
|
+
case "heartbeat:loop":
|
|
486
|
+
await cmdHeartbeatLoop();
|
|
487
|
+
break;
|
|
488
|
+
case "status":
|
|
489
|
+
await cmdStatus();
|
|
490
|
+
break;
|
|
491
|
+
case "agents":
|
|
492
|
+
await cmdAgents();
|
|
493
|
+
break;
|
|
494
|
+
case "actions":
|
|
495
|
+
await cmdActions(rest);
|
|
496
|
+
break;
|
|
497
|
+
case "log":
|
|
498
|
+
await cmdLog(rest);
|
|
499
|
+
break;
|
|
500
|
+
case "domains":
|
|
501
|
+
await cmdDomains();
|
|
502
|
+
break;
|
|
503
|
+
case "sandboxes":
|
|
504
|
+
await cmdSandboxes();
|
|
505
|
+
break;
|
|
506
|
+
case "engine":
|
|
507
|
+
await cmdEngine();
|
|
508
|
+
break;
|
|
509
|
+
case "x402":
|
|
510
|
+
await cmdX402();
|
|
511
|
+
break;
|
|
512
|
+
case "whoami":
|
|
513
|
+
cmdWhoami();
|
|
514
|
+
break;
|
|
515
|
+
case "version":
|
|
516
|
+
case "-v":
|
|
517
|
+
case "--version":
|
|
518
|
+
console.log(` otonix v${VERSION}`);
|
|
519
|
+
break;
|
|
520
|
+
case "help":
|
|
521
|
+
case "-h":
|
|
522
|
+
case "--help":
|
|
523
|
+
console.log(HELP);
|
|
524
|
+
break;
|
|
525
|
+
default:
|
|
526
|
+
console.error(` Unknown command: ${command}`);
|
|
527
|
+
console.log(HELP);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
} catch (err) {
|
|
531
|
+
if (err instanceof import_sdk.OtonixError) {
|
|
532
|
+
console.error(` API Error (${err.status}): ${err.message}`);
|
|
533
|
+
} else {
|
|
534
|
+
console.error(` Error: ${err.message}`);
|
|
535
|
+
}
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@otonix/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for the Otonix sovereign compute platform — initialize agents, generate API keys, register, monitor status, and manage infrastructure from the terminal.",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"otonix": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/cli.ts --format cjs --clean",
|
|
15
|
+
"dev": "tsup src/cli.ts --format cjs --watch",
|
|
16
|
+
"prepublishOnly": "echo 'already built'"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"otonix",
|
|
20
|
+
"cli",
|
|
21
|
+
"autonomous-agents",
|
|
22
|
+
"web4",
|
|
23
|
+
"x402",
|
|
24
|
+
"sovereign-compute",
|
|
25
|
+
"ai-agents"
|
|
26
|
+
],
|
|
27
|
+
"author": "Otonix <dev@otonix.tech>",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"homepage": "https://github.com/otonix-ai/cli",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/otonix-ai/cli.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/otonix-ai/cli/issues"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@otonix/sdk": "^1.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"typescript": "^5.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|