@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.
Files changed (3) hide show
  1. package/README.md +114 -0
  2. package/bin/aditya.mjs +251 -0
  3. 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
+ }