@adityaprotocol/cli 0.1.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 +114 -0
- package/bin/aditya.mjs +251 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @adityaprotocol/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [Aditya Protocol](https://adityaprotocol.com) — manage commands, approvals, and runs from your terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @adityaprotocol/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @adityaprotocol/cli health
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Set your operator token as an environment variable:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export ADITYA_TOKEN="apt_user_your_token_here"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Optionally set a default workspace:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export ADITYA_WORKSPACE="your-workspace-id"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `aditya health` | Check API status |
|
|
36
|
+
| `aditya workspaces` | List your workspaces |
|
|
37
|
+
| `aditya nodes <ws-id>` | List nodes in a workspace |
|
|
38
|
+
| `aditya commands <ws-id> [status]` | List commands (optional status filter) |
|
|
39
|
+
| `aditya pending <ws-id>` | List commands awaiting approval |
|
|
40
|
+
| `aditya approve <cmd-id> [reason]` | Approve a pending command |
|
|
41
|
+
| `aditya reject <cmd-id> [reason]` | Reject a pending command |
|
|
42
|
+
| `aditya runs <ws-id>` | List runs |
|
|
43
|
+
| `aditya audit <ws-id>` | Show recent audit trail |
|
|
44
|
+
| `aditya create <ws-id> <node-id> <type>` | Create a new command |
|
|
45
|
+
| `aditya export <ws-id>` | Export all workspace data (JSON) |
|
|
46
|
+
|
|
47
|
+
## Usage Examples
|
|
48
|
+
|
|
49
|
+
### Check API health
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
$ aditya health
|
|
53
|
+
API: healthy (2026-04-05T12:00:00.000Z)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### List pending approvals
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
$ aditya pending bd6e32d0-ab21-428e-9e0b-a78cd8dce23c
|
|
60
|
+
|
|
61
|
+
Pending approval:
|
|
62
|
+
ID Type Node Created
|
|
63
|
+
-------- ------ -------- -------
|
|
64
|
+
a1b2c3d4 deploy e5f6g7h8 2m ago
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Approve a command
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ aditya approve a1b2c3d4-full-uuid "Verified deployment is safe"
|
|
71
|
+
Command a1b2c3d4... approved. Status: approved
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Create a command and wait for approval
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ aditya create $ADITYA_WORKSPACE $NODE_ID deploy
|
|
78
|
+
Command created: a1b2c3d4-...
|
|
79
|
+
Status: pending_approval (awaiting approval)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Export workspace data
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
$ aditya export $ADITYA_WORKSPACE > workspace-backup.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Environment Variables
|
|
89
|
+
|
|
90
|
+
| Variable | Required | Description |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `ADITYA_TOKEN` | Yes (except `health`) | Operator token starting with `apt_user_` |
|
|
93
|
+
| `ADITYA_API` | No | API base URL (default: `https://api.adityaprotocol.com`) |
|
|
94
|
+
| `ADITYA_WORKSPACE` | No | Default workspace ID for commands that need one |
|
|
95
|
+
|
|
96
|
+
## CI/CD Integration
|
|
97
|
+
|
|
98
|
+
Use the CLI in GitHub Actions:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
- name: Check Aditya Protocol health
|
|
102
|
+
run: npx @adityaprotocol/cli health
|
|
103
|
+
|
|
104
|
+
- name: Create deployment command
|
|
105
|
+
env:
|
|
106
|
+
ADITYA_TOKEN: ${{ secrets.ADITYA_TOKEN }}
|
|
107
|
+
run: npx @adityaprotocol/cli create $WORKSPACE_ID $NODE_ID deploy
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For a full approval gate workflow, see the [Aditya Protocol GitHub Action](https://github.com/rrslt6d3/aditya-protocol/tree/main/packages/github-action).
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
package/bin/aditya.mjs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const API = process.env.ADITYA_API || "https://api.adityaprotocol.com";
|
|
4
|
+
const TOKEN = process.env.ADITYA_TOKEN || "";
|
|
5
|
+
|
|
6
|
+
const [, , command, ...args] = process.argv;
|
|
7
|
+
|
|
8
|
+
const headers = {
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function api(method, path, body) {
|
|
14
|
+
const res = await fetch(`${API}${path}`, {
|
|
15
|
+
method,
|
|
16
|
+
headers,
|
|
17
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
18
|
+
});
|
|
19
|
+
const text = await res.text();
|
|
20
|
+
let data;
|
|
21
|
+
try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
console.error(`Error ${res.status}: ${data.error || text}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function table(rows, columns) {
|
|
30
|
+
if (!rows.length) { console.log(" (none)"); return; }
|
|
31
|
+
const widths = columns.map((col) =>
|
|
32
|
+
Math.max(col.label.length, ...rows.map((r) => String(col.get(r) ?? "").length))
|
|
33
|
+
);
|
|
34
|
+
const header = columns.map((col, i) => col.label.padEnd(widths[i])).join(" ");
|
|
35
|
+
const sep = widths.map((w) => "-".repeat(w)).join(" ");
|
|
36
|
+
console.log(` ${header}`);
|
|
37
|
+
console.log(` ${sep}`);
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
const line = columns.map((col, i) => String(col.get(row) ?? "").padEnd(widths[i])).join(" ");
|
|
40
|
+
console.log(` ${line}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function short(id) { return id ? id.slice(0, 8) + "..." : "-"; }
|
|
45
|
+
function ago(ts) {
|
|
46
|
+
if (!ts) return "-";
|
|
47
|
+
const ms = Date.now() - new Date(ts).getTime();
|
|
48
|
+
if (ms < 60000) return `${Math.floor(ms / 1000)}s ago`;
|
|
49
|
+
if (ms < 3600000) return `${Math.floor(ms / 60000)}m ago`;
|
|
50
|
+
if (ms < 86400000) return `${Math.floor(ms / 3600000)}h ago`;
|
|
51
|
+
return `${Math.floor(ms / 86400000)}d ago`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function run() {
|
|
55
|
+
if (!TOKEN && command !== "health" && command !== "help") {
|
|
56
|
+
console.error("Set ADITYA_TOKEN environment variable first:");
|
|
57
|
+
console.error(' export ADITYA_TOKEN="apt_user_..."');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (command) {
|
|
62
|
+
case "health": {
|
|
63
|
+
const data = await api("GET", "/health");
|
|
64
|
+
console.log(`API: ${data.ok ? "healthy" : "unhealthy"} (${data.timestamp})`);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case "workspaces":
|
|
69
|
+
case "ws": {
|
|
70
|
+
const data = await api("GET", "/workspaces");
|
|
71
|
+
console.log("\nWorkspaces:");
|
|
72
|
+
table(data.workspaces, [
|
|
73
|
+
{ label: "ID", get: (r) => short(r.id) },
|
|
74
|
+
{ label: "Slug", get: (r) => r.slug },
|
|
75
|
+
{ label: "Name", get: (r) => r.name },
|
|
76
|
+
{ label: "Created", get: (r) => ago(r.created_at) },
|
|
77
|
+
]);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case "nodes": {
|
|
82
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
83
|
+
if (!wsId) { console.error("Usage: aditya nodes <workspace-id>"); process.exit(1); }
|
|
84
|
+
const data = await api("GET", `/nodes?workspace_id=${wsId}`);
|
|
85
|
+
console.log("\nNodes:");
|
|
86
|
+
table(data.nodes, [
|
|
87
|
+
{ label: "ID", get: (r) => short(r.id) },
|
|
88
|
+
{ label: "Name", get: (r) => r.name },
|
|
89
|
+
{ label: "Environment", get: (r) => r.environment },
|
|
90
|
+
{ label: "Status", get: (r) => r.status },
|
|
91
|
+
{ label: "Last seen", get: (r) => ago(r.last_seen_at) },
|
|
92
|
+
]);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case "commands":
|
|
97
|
+
case "cmds": {
|
|
98
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
99
|
+
if (!wsId) { console.error("Usage: aditya commands <workspace-id> [status]"); process.exit(1); }
|
|
100
|
+
const status = args[1] || "";
|
|
101
|
+
const query = status ? `?workspace_id=${wsId}&status=${status}` : `?workspace_id=${wsId}`;
|
|
102
|
+
const data = await api("GET", `/commands${query}`);
|
|
103
|
+
console.log(`\nCommands${status ? ` (${status})` : ""}:`);
|
|
104
|
+
table(data.commands, [
|
|
105
|
+
{ label: "ID", get: (r) => short(r.id) },
|
|
106
|
+
{ label: "Type", get: (r) => r.command_type },
|
|
107
|
+
{ label: "Status", get: (r) => r.status },
|
|
108
|
+
{ label: "Node", get: (r) => short(r.node_id) },
|
|
109
|
+
{ label: "Created", get: (r) => ago(r.created_at) },
|
|
110
|
+
]);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case "pending": {
|
|
115
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
116
|
+
if (!wsId) { console.error("Usage: aditya pending <workspace-id>"); process.exit(1); }
|
|
117
|
+
const data = await api("GET", `/commands?workspace_id=${wsId}&status=pending_approval`);
|
|
118
|
+
console.log("\nPending approval:");
|
|
119
|
+
if (!data.commands.length) { console.log(" No commands awaiting approval."); break; }
|
|
120
|
+
table(data.commands, [
|
|
121
|
+
{ label: "ID", get: (r) => short(r.id) },
|
|
122
|
+
{ label: "Type", get: (r) => r.command_type },
|
|
123
|
+
{ label: "Node", get: (r) => short(r.node_id) },
|
|
124
|
+
{ label: "Created", get: (r) => ago(r.created_at) },
|
|
125
|
+
]);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case "approve": {
|
|
130
|
+
const cmdId = args[0];
|
|
131
|
+
const reason = args.slice(1).join(" ") || "Approved via CLI";
|
|
132
|
+
if (!cmdId) { console.error("Usage: aditya approve <command-id> [reason]"); process.exit(1); }
|
|
133
|
+
const data = await api("POST", `/commands/${cmdId}/approve`, {
|
|
134
|
+
reviewer_name: "cli-operator",
|
|
135
|
+
reason,
|
|
136
|
+
});
|
|
137
|
+
console.log(`Command ${short(cmdId)} approved. Status: ${data.command.status}`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "reject": {
|
|
142
|
+
const cmdId = args[0];
|
|
143
|
+
const reason = args.slice(1).join(" ") || "Rejected via CLI";
|
|
144
|
+
if (!cmdId) { console.error("Usage: aditya reject <command-id> [reason]"); process.exit(1); }
|
|
145
|
+
const data = await api("POST", `/commands/${cmdId}/reject`, {
|
|
146
|
+
reviewer_name: "cli-operator",
|
|
147
|
+
reason,
|
|
148
|
+
});
|
|
149
|
+
console.log(`Command ${short(cmdId)} rejected. Status: ${data.command.status}`);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "runs": {
|
|
154
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
155
|
+
if (!wsId) { console.error("Usage: aditya runs <workspace-id>"); process.exit(1); }
|
|
156
|
+
const data = await api("GET", `/runs?workspace_id=${wsId}`);
|
|
157
|
+
console.log("\nRuns:");
|
|
158
|
+
table(data.runs, [
|
|
159
|
+
{ label: "ID", get: (r) => short(r.id) },
|
|
160
|
+
{ label: "Status", get: (r) => r.status },
|
|
161
|
+
{ label: "Command", get: (r) => short(r.command_id) },
|
|
162
|
+
{ label: "Node", get: (r) => short(r.node_id) },
|
|
163
|
+
{ label: "Exit", get: (r) => r.exit_code ?? "-" },
|
|
164
|
+
{ label: "Created", get: (r) => ago(r.created_at) },
|
|
165
|
+
]);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "audit": {
|
|
170
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
171
|
+
if (!wsId) { console.error("Usage: aditya audit <workspace-id>"); process.exit(1); }
|
|
172
|
+
const data = await api("GET", `/audit-events?workspace_id=${wsId}`);
|
|
173
|
+
console.log("\nAudit trail:");
|
|
174
|
+
table(data.audit_events.slice(0, 20), [
|
|
175
|
+
{ label: "Time", get: (r) => ago(r.created_at) },
|
|
176
|
+
{ label: "Event", get: (r) => r.event_type },
|
|
177
|
+
{ label: "Actor", get: (r) => r.actor_id ? (r.actor_id.length > 20 ? r.actor_id.slice(0, 20) + "..." : r.actor_id) : "-" },
|
|
178
|
+
{ label: "Entity", get: (r) => `${r.entity_type}/${short(r.entity_id)}` },
|
|
179
|
+
]);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case "create-command":
|
|
184
|
+
case "create": {
|
|
185
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
186
|
+
const nodeId = args[1];
|
|
187
|
+
const kind = args[2];
|
|
188
|
+
if (!wsId || !nodeId || !kind) {
|
|
189
|
+
console.error("Usage: aditya create <workspace-id> <node-id> <command-type>");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const data = await api("POST", "/commands", {
|
|
193
|
+
workspace_id: wsId,
|
|
194
|
+
node_id: nodeId,
|
|
195
|
+
kind,
|
|
196
|
+
payload: {},
|
|
197
|
+
});
|
|
198
|
+
console.log(`Command created: ${data.command.id}`);
|
|
199
|
+
console.log(`Status: ${data.command.status} (awaiting approval)`);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case "export": {
|
|
204
|
+
const wsId = args[0] || process.env.ADITYA_WORKSPACE;
|
|
205
|
+
if (!wsId) { console.error("Usage: aditya export <workspace-id>"); process.exit(1); }
|
|
206
|
+
const data = await api("GET", `/export/workspace/${wsId}`);
|
|
207
|
+
console.log(JSON.stringify(data, null, 2));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case "help":
|
|
212
|
+
default: {
|
|
213
|
+
console.log(`
|
|
214
|
+
Aditya Protocol CLI v0.1.0
|
|
215
|
+
|
|
216
|
+
Usage: aditya <command> [args]
|
|
217
|
+
|
|
218
|
+
Environment:
|
|
219
|
+
ADITYA_TOKEN Operator token (required, starts with apt_user_)
|
|
220
|
+
ADITYA_API API base URL (default: https://api.adityaprotocol.com)
|
|
221
|
+
ADITYA_WORKSPACE Default workspace ID (optional)
|
|
222
|
+
|
|
223
|
+
Commands:
|
|
224
|
+
health Check API status
|
|
225
|
+
workspaces, ws List your workspaces
|
|
226
|
+
nodes <ws-id> List nodes in a workspace
|
|
227
|
+
commands, cmds <ws-id> [status] List commands (optional status filter)
|
|
228
|
+
pending <ws-id> List commands awaiting approval
|
|
229
|
+
approve <cmd-id> [reason] Approve a pending command
|
|
230
|
+
reject <cmd-id> [reason] Reject a pending command
|
|
231
|
+
runs <ws-id> List runs
|
|
232
|
+
audit <ws-id> Show recent audit trail
|
|
233
|
+
create <ws-id> <node-id> <type> Create a new command
|
|
234
|
+
export <ws-id> Export all workspace data (JSON)
|
|
235
|
+
help Show this help
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
aditya health
|
|
239
|
+
aditya pending bd6e32d0-ab21-428e-9e0b-a78cd8dce23c
|
|
240
|
+
aditya approve a1b2c3d4-... "Verified deployment is safe"
|
|
241
|
+
aditya audit bd6e32d0-ab21-428e-9e0b-a78cd8dce23c
|
|
242
|
+
`);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
run().catch((err) => {
|
|
249
|
+
console.error(`Fatal: ${err.message}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adityaprotocol/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Aditya Protocol — manage commands, approvals, and runs from your terminal",
|
|
5
|
+
"bin": {
|
|
6
|
+
"aditya": "./bin/aditya.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/rrslt6d3/aditya-protocol.git",
|
|
13
|
+
"directory": "packages/cli"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://adityaprotocol.com",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/rrslt6d3/aditya-protocol/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["aditya-protocol", "cli", "approval", "operations", "control-plane", "human-in-the-loop"],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"README.md"
|
|
26
|
+
]
|
|
27
|
+
}
|