@axisagent/cli 0.1.1 → 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 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. The dashboard polls `GET /v1/pairing/:code/status`.
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
  }
@@ -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>;
@@ -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");
@@ -4,6 +4,7 @@ type RelayConnectOptions = {
4
4
  baseDir: string;
5
5
  heartbeatIntervalMs: number;
6
6
  once: boolean;
7
+ runApprovedTasks: boolean;
7
8
  };
8
9
  export declare function connectRelay(options: RelayConnectOptions): Promise<{
9
10
  status: string;
@@ -1,7 +1,8 @@
1
- import { access, readdir } from "node:fs/promises";
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 { getRegistryStats } from "./skillRegistry.js";
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,138 @@ 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
+ }
141
+ function safeTaskFileName(task) {
142
+ const title = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64) || "task";
143
+ return `${new Date(task.createdAt).toISOString().replace(/[:.]/g, "-")}-${title}.md`;
144
+ }
145
+ async function writeTaskInbox(baseDir, task) {
146
+ const inboxDir = join(baseDir, ".axis", "inbox");
147
+ await mkdir(inboxDir, { recursive: true });
148
+ const filePath = join(inboxDir, safeTaskFileName(task));
149
+ const body = [
150
+ `# ${task.title}`,
151
+ "",
152
+ `- Task: ${task.id}`,
153
+ `- Kind: ${task.kind}`,
154
+ `- Target: ${task.targetAgent}`,
155
+ `- Skill: ${task.skillId ?? "n/a"}`,
156
+ `- Created: ${task.createdAt}`,
157
+ "",
158
+ task.command ? "## Command\n" : "",
159
+ task.command ? `\`\`\`bash\n${task.command}\n\`\`\`\n` : "",
160
+ "## Prompt",
161
+ "",
162
+ task.prompt,
163
+ "",
164
+ "## Axis Rule",
165
+ "",
166
+ "Do not execute wallet, API, market, or payment actions until the user approves the Axis review ticket."
167
+ ].filter(Boolean).join("\n");
168
+ await writeFile(filePath, body, "utf8");
169
+ return filePath;
170
+ }
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) {
175
+ for (const task of tasks) {
176
+ const filePath = await writeTaskInbox(baseDir, task);
177
+ console.log("");
178
+ console.log(`Axis task received: ${task.title}`);
179
+ console.log(`Target: ${task.targetAgent}`);
180
+ if (task.command)
181
+ console.log(`Command: ${task.command}`);
182
+ console.log(`Inbox: ${filePath}`);
183
+ console.log("Prompt:");
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
+ }
206
+ console.log("");
207
+ }
208
+ }
76
209
  async function createRelayPayload(baseDir, pairingCode) {
77
210
  return {
78
211
  pairingCode,
@@ -81,7 +214,7 @@ async function createRelayPayload(baseDir, pairingCode) {
81
214
  version: "0.1.0",
82
215
  baseDir,
83
216
  runtime: `node ${process.version}`,
84
- capabilities: ["heartbeat", "skill-install", "skill-scan", "axis-check", "approval-ticket"]
217
+ capabilities: ["heartbeat", "skill-install", "skill-scan", "axis-check", "approval-ticket", "approved-task-runner"]
85
218
  },
86
219
  agents: await detectAgents(baseDir),
87
220
  registry: getRegistryStats()
@@ -105,5 +238,8 @@ export async function connectRelay(options) {
105
238
  const heartbeatPayload = await createRelayPayload(options.baseDir, pairingCode);
106
239
  const heartbeat = await postJson(`${apiUrl}/v1/relay/${pairingCode}/heartbeat`, heartbeatPayload, connected.relayToken);
107
240
  console.log(`[${heartbeat.session.lastSeenAt}] relay ${heartbeat.session.status}`);
241
+ if (heartbeat.tasks?.length) {
242
+ await processRelayTasks(options.baseDir, apiUrl, pairingCode, connected.relayToken, heartbeat.tasks, options.runApprovedTasks);
243
+ }
108
244
  }
109
245
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axisagent/cli",
3
3
  "private": false,
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "type": "module",
6
6
  "description": "Local Axis runtime, skill registry, policy validator, and CLI.",
7
7
  "license": "MIT",