@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 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
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,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 processRelayTasks(baseDir, tasks) {
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axisagent/cli",
3
3
  "private": false,
4
- "version": "0.1.2",
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",