@axisagent/cli 0.1.2 → 0.1.3
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 +4 -3
- package/dist/cli.js +3 -2
- package/dist/packageWriter.d.ts +34 -0
- package/dist/packageWriter.js +9 -0
- package/dist/relayClient.d.ts +1 -0
- package/dist/relayClient.js +94 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npx @axisagent/cli list --chain Base
|
|
|
13
13
|
npm run axis -- inspect bankr-launch-feed
|
|
14
14
|
npx @axisagent/cli install bankr-launch-feed --target .axis/skills
|
|
15
15
|
npx @axisagent/cli scan .axis/skills/bankr-launch-feed
|
|
16
|
-
npx @axisagent/cli connect --pair AXIS-1234 --api https://api.axisagent.xyz
|
|
16
|
+
npx @axisagent/cli connect --pair AXIS-1234 --api https://api.axisagent.xyz --run-approved
|
|
17
17
|
npx @axisagent/cli serve --port 8788
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -24,10 +24,11 @@ The public dashboard should connect through Axis API, not direct browser-to-loca
|
|
|
24
24
|
Flow:
|
|
25
25
|
|
|
26
26
|
1. Dashboard creates a pairing code through `POST /v1/pairing/create`.
|
|
27
|
-
2. User runs `npx @axisagent/cli connect --pair AXIS-1234 --api https://api.axisagent.xyz`.
|
|
27
|
+
2. User runs `npx @axisagent/cli connect --pair AXIS-1234 --api https://api.axisagent.xyz --run-approved`.
|
|
28
28
|
3. The local connector opens an outbound session to Axis API.
|
|
29
29
|
4. The connector sends heartbeats, detected agents, capabilities and registry stats.
|
|
30
|
-
5.
|
|
30
|
+
5. When the user clicks `Authorize + send`, the connector can run approved Axis install+scan tasks locally.
|
|
31
|
+
6. The dashboard polls the task result and opens the Axis approval ticket.
|
|
31
32
|
|
|
32
33
|
For local API testing:
|
|
33
34
|
|
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ Commands:
|
|
|
26
26
|
install <skill-id> [--target .axis/skills] [--scan] [--json]
|
|
27
27
|
scan <skill-dir> [--json]
|
|
28
28
|
validate
|
|
29
|
-
connect [--pair AXIS-4821] [--api https://api.axisagent.xyz] [--once]
|
|
29
|
+
connect [--pair AXIS-4821] [--api https://api.axisagent.xyz] [--once] [--run-approved]
|
|
30
30
|
serve [--port 8788]
|
|
31
31
|
`);
|
|
32
32
|
}
|
|
@@ -137,7 +137,8 @@ async function run() {
|
|
|
137
137
|
pairingCode,
|
|
138
138
|
baseDir: userCwd,
|
|
139
139
|
heartbeatIntervalMs,
|
|
140
|
-
once: hasFlag("--once")
|
|
140
|
+
once: hasFlag("--once"),
|
|
141
|
+
runApprovedTasks: hasFlag("--run-approved")
|
|
141
142
|
});
|
|
142
143
|
return;
|
|
143
144
|
}
|
package/dist/packageWriter.d.ts
CHANGED
|
@@ -48,4 +48,38 @@ export declare function writeSkillPackage(skillId: string, targetDir: string): P
|
|
|
48
48
|
skillDir: string;
|
|
49
49
|
files: string[];
|
|
50
50
|
}>;
|
|
51
|
+
export declare function installAndScanSkillPackage(skillId: string, targetDir: string): Promise<{
|
|
52
|
+
scan: ScanResult;
|
|
53
|
+
skill: {
|
|
54
|
+
id: string;
|
|
55
|
+
title: string;
|
|
56
|
+
category: string;
|
|
57
|
+
summary: string;
|
|
58
|
+
useWhen: string;
|
|
59
|
+
chains: string[];
|
|
60
|
+
protocols: string[];
|
|
61
|
+
risk: "low" | "medium" | "high" | "critical";
|
|
62
|
+
executionMode: "read-only" | "approval-required" | "live-execution";
|
|
63
|
+
permissions: ("network" | "market-data" | "wallet-read" | "wallet-signature" | "transaction-preview" | "trading" | "api-key" | "notifications" | "contract-call" | "filesystem-read" | "filesystem-write" | "secrets-read" | "paid-api" | "governance" | "browser")[];
|
|
64
|
+
installTargets: ("codex" | "claude" | "openclaw" | "cursor" | "generic-mcp")[];
|
|
65
|
+
source: string;
|
|
66
|
+
tokenUtility: string;
|
|
67
|
+
auditNotes: string[];
|
|
68
|
+
operations: string[];
|
|
69
|
+
tests: string[];
|
|
70
|
+
policy: {
|
|
71
|
+
defaultMode: "read-only" | "approval-required" | "live-execution";
|
|
72
|
+
requiresApproval: boolean;
|
|
73
|
+
networkAccess: boolean;
|
|
74
|
+
walletWrite: boolean;
|
|
75
|
+
secrets: string[];
|
|
76
|
+
allowedChains: string[];
|
|
77
|
+
maxSpendUsd: number | null;
|
|
78
|
+
sessionLimitMinutes: number;
|
|
79
|
+
humanReadableRule: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
skillDir: string;
|
|
83
|
+
files: string[];
|
|
84
|
+
}>;
|
|
51
85
|
export declare function scanSkillPackage(skillDir: string): Promise<ScanResult>;
|
package/dist/packageWriter.js
CHANGED
|
@@ -17,6 +17,15 @@ export async function writeSkillPackage(skillId, targetDir) {
|
|
|
17
17
|
files: ["SKILL.md", "policy.json", "README.md"]
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
export async function installAndScanSkillPackage(skillId, targetDir) {
|
|
21
|
+
await mkdir(targetDir, { recursive: true });
|
|
22
|
+
const install = await writeSkillPackage(skillId, targetDir);
|
|
23
|
+
const scan = await scanSkillPackage(install.skillDir);
|
|
24
|
+
return {
|
|
25
|
+
...install,
|
|
26
|
+
scan
|
|
27
|
+
};
|
|
28
|
+
}
|
|
20
29
|
export async function scanSkillPackage(skillDir) {
|
|
21
30
|
const skillMarkdown = await readFile(join(skillDir, "SKILL.md"), "utf8");
|
|
22
31
|
const policyRaw = await readFile(join(skillDir, "policy.json"), "utf8");
|
package/dist/relayClient.d.ts
CHANGED
package/dist/relayClient.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import {
|
|
3
|
+
import { resolve, relative, join } from "node:path";
|
|
4
|
+
import { installAndScanSkillPackage, scanSkillPackage } from "./packageWriter.js";
|
|
5
|
+
import { getRegistryStats, getSkillById } from "./skillRegistry.js";
|
|
5
6
|
async function pathExists(path) {
|
|
6
7
|
try {
|
|
7
8
|
await access(path);
|
|
@@ -73,6 +74,70 @@ async function postJson(url, body, token) {
|
|
|
73
74
|
}
|
|
74
75
|
return payload;
|
|
75
76
|
}
|
|
77
|
+
function getOptionFromCommand(command, name) {
|
|
78
|
+
if (!command)
|
|
79
|
+
return null;
|
|
80
|
+
const parts = command.trim().split(/\s+/);
|
|
81
|
+
const index = parts.indexOf(name);
|
|
82
|
+
if (index === -1)
|
|
83
|
+
return null;
|
|
84
|
+
return parts[index + 1] ?? null;
|
|
85
|
+
}
|
|
86
|
+
function resolveSafeTarget(baseDir, target) {
|
|
87
|
+
if (target.startsWith("/") || target.includes("..")) {
|
|
88
|
+
throw new Error(`Unsafe target path: ${target}`);
|
|
89
|
+
}
|
|
90
|
+
const resolved = resolve(baseDir, target);
|
|
91
|
+
const relation = relative(baseDir, resolved);
|
|
92
|
+
if (relation.startsWith("..") || relation === "") {
|
|
93
|
+
throw new Error(`Target must stay inside the connected workspace: ${target}`);
|
|
94
|
+
}
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
function validateInstallTask(task) {
|
|
98
|
+
if (task.kind !== "install") {
|
|
99
|
+
throw new Error(`Task kind ${task.kind} is not auto-executable`);
|
|
100
|
+
}
|
|
101
|
+
if (!task.skillId || !getSkillById(task.skillId)) {
|
|
102
|
+
throw new Error(`Unknown Axis skill: ${task.skillId ?? "missing"}`);
|
|
103
|
+
}
|
|
104
|
+
const target = getOptionFromCommand(task.command, "--target") ?? ".axis/skills";
|
|
105
|
+
const commandSkill = task.command?.split(/\s+/).includes(task.skillId) ?? false;
|
|
106
|
+
const hasScan = task.command?.split(/\s+/).includes("--scan") ?? false;
|
|
107
|
+
if (task.command && (!commandSkill || !hasScan)) {
|
|
108
|
+
throw new Error("Install task command does not match Axis install+scan policy");
|
|
109
|
+
}
|
|
110
|
+
return target;
|
|
111
|
+
}
|
|
112
|
+
async function runApprovedAxisTask(baseDir, task) {
|
|
113
|
+
if (task.kind === "install") {
|
|
114
|
+
const target = validateInstallTask(task);
|
|
115
|
+
const targetDir = resolveSafeTarget(baseDir, target);
|
|
116
|
+
const install = await installAndScanSkillPackage(task.skillId, targetDir);
|
|
117
|
+
return {
|
|
118
|
+
status: install.scan.ok ? "completed" : "failed",
|
|
119
|
+
summary: install.scan.ok
|
|
120
|
+
? `Installed and scanned ${install.skill.title}. ${install.scan.findings.length} checks passed.`
|
|
121
|
+
: `Installed ${install.skill.title}, but Axis scan found blocked checks.`,
|
|
122
|
+
scan: install.scan,
|
|
123
|
+
path: install.skillDir
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (task.kind === "scan" || task.kind === "check") {
|
|
127
|
+
if (!task.skillId)
|
|
128
|
+
throw new Error("Scan task is missing skill id");
|
|
129
|
+
const target = getOptionFromCommand(task.command, "--target") ?? ".axis/skills";
|
|
130
|
+
const skillDir = resolveSafeTarget(baseDir, join(target, task.skillId));
|
|
131
|
+
const scan = await scanSkillPackage(skillDir);
|
|
132
|
+
return {
|
|
133
|
+
status: scan.ok ? "completed" : "failed",
|
|
134
|
+
summary: scan.ok ? `Axis scan passed for ${task.skillId}.` : `Axis scan blocked ${task.skillId}.`,
|
|
135
|
+
scan,
|
|
136
|
+
path: skillDir
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
throw new Error("Prompt-only tasks are written to inbox and require the agent to decide next steps");
|
|
140
|
+
}
|
|
76
141
|
function safeTaskFileName(task) {
|
|
77
142
|
const title = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64) || "task";
|
|
78
143
|
return `${new Date(task.createdAt).toISOString().replace(/[:.]/g, "-")}-${title}.md`;
|
|
@@ -103,7 +168,10 @@ async function writeTaskInbox(baseDir, task) {
|
|
|
103
168
|
await writeFile(filePath, body, "utf8");
|
|
104
169
|
return filePath;
|
|
105
170
|
}
|
|
106
|
-
async function
|
|
171
|
+
async function postTaskResult(apiUrl, pairingCode, token, task, result) {
|
|
172
|
+
await postJson(`${apiUrl}/v1/relay/${pairingCode}/tasks/${task.id}/result`, result, token);
|
|
173
|
+
}
|
|
174
|
+
async function processRelayTasks(baseDir, apiUrl, pairingCode, token, tasks, runApprovedTasks) {
|
|
107
175
|
for (const task of tasks) {
|
|
108
176
|
const filePath = await writeTaskInbox(baseDir, task);
|
|
109
177
|
console.log("");
|
|
@@ -114,6 +182,27 @@ async function processRelayTasks(baseDir, tasks) {
|
|
|
114
182
|
console.log(`Inbox: ${filePath}`);
|
|
115
183
|
console.log("Prompt:");
|
|
116
184
|
console.log(task.prompt);
|
|
185
|
+
if (runApprovedTasks) {
|
|
186
|
+
try {
|
|
187
|
+
const result = await runApprovedAxisTask(baseDir, task);
|
|
188
|
+
await postTaskResult(apiUrl, pairingCode, token, task, result);
|
|
189
|
+
console.log(`Axis auto-run: ${result.summary}`);
|
|
190
|
+
if (result.path)
|
|
191
|
+
console.log(`Path: ${result.path}`);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const message = error instanceof Error ? error.message : "Unknown task execution error";
|
|
195
|
+
await postTaskResult(apiUrl, pairingCode, token, task, {
|
|
196
|
+
status: "failed",
|
|
197
|
+
summary: `Axis auto-run failed: ${message}`,
|
|
198
|
+
error: message
|
|
199
|
+
});
|
|
200
|
+
console.log(`Axis auto-run failed: ${message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
console.log("Auto-run disabled. Start the connector with --run-approved to execute Axis-approved install tasks locally.");
|
|
205
|
+
}
|
|
117
206
|
console.log("");
|
|
118
207
|
}
|
|
119
208
|
}
|
|
@@ -125,7 +214,7 @@ async function createRelayPayload(baseDir, pairingCode) {
|
|
|
125
214
|
version: "0.1.0",
|
|
126
215
|
baseDir,
|
|
127
216
|
runtime: `node ${process.version}`,
|
|
128
|
-
capabilities: ["heartbeat", "skill-install", "skill-scan", "axis-check", "approval-ticket"]
|
|
217
|
+
capabilities: ["heartbeat", "skill-install", "skill-scan", "axis-check", "approval-ticket", "approved-task-runner"]
|
|
129
218
|
},
|
|
130
219
|
agents: await detectAgents(baseDir),
|
|
131
220
|
registry: getRegistryStats()
|
|
@@ -150,7 +239,7 @@ export async function connectRelay(options) {
|
|
|
150
239
|
const heartbeat = await postJson(`${apiUrl}/v1/relay/${pairingCode}/heartbeat`, heartbeatPayload, connected.relayToken);
|
|
151
240
|
console.log(`[${heartbeat.session.lastSeenAt}] relay ${heartbeat.session.status}`);
|
|
152
241
|
if (heartbeat.tasks?.length) {
|
|
153
|
-
await processRelayTasks(options.baseDir, heartbeat.tasks);
|
|
242
|
+
await processRelayTasks(options.baseDir, apiUrl, pairingCode, connected.relayToken, heartbeat.tasks, options.runApprovedTasks);
|
|
154
243
|
}
|
|
155
244
|
}
|
|
156
245
|
}
|