@aarmos/cli 0.2.0 → 0.2.1
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/dist/index.js +211 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,10 @@ import { Command as Command9 } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { mkdirSync, writeFileSync, existsSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
8
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, statSync, cpSync, mkdtempSync, rmSync } from "fs";
|
|
9
|
+
import { join, dirname, relative } from "path";
|
|
10
|
+
import { tmpdir } from "os";
|
|
11
|
+
import { spawnSync } from "child_process";
|
|
10
12
|
|
|
11
13
|
// src/lib/keys.ts
|
|
12
14
|
import { generateKeyPairSync } from "crypto";
|
|
@@ -53,13 +55,28 @@ var GITIGNORE_TEMPLATE = `# Aarmos local state
|
|
|
53
55
|
.aarmos/keys/
|
|
54
56
|
.aarmos/avar/
|
|
55
57
|
`;
|
|
58
|
+
var IMPORT_ALLOWLIST = [
|
|
59
|
+
"policy.aarmos.toml",
|
|
60
|
+
"avar.config.json",
|
|
61
|
+
"asp.yaml",
|
|
62
|
+
"asp.yml",
|
|
63
|
+
"policies",
|
|
64
|
+
"playbooks",
|
|
65
|
+
".aarmos/packs"
|
|
66
|
+
];
|
|
56
67
|
function initCommand() {
|
|
57
|
-
return new Command("init").description("Scaffold policy.aarmos.toml, avar.config.json, and local signing key").option("--force", "Overwrite existing files", false).
|
|
68
|
+
return new Command("init").description("Scaffold policy.aarmos.toml, avar.config.json, and local signing key").option("--force", "Overwrite existing files", false).option(
|
|
69
|
+
"--from <repo>",
|
|
70
|
+
"Seed policies/playbooks from a GitHub repo (owner/repo[#ref][/subdir]) or tarball URL. Only Aarmos config files are copied; source code and keys are never imported."
|
|
71
|
+
).action(async (opts) => {
|
|
58
72
|
const cwd = process.cwd();
|
|
59
73
|
const keyDir = join(cwd, ".aarmos", "keys");
|
|
60
74
|
const chainDir = join(cwd, ".aarmos", "avar");
|
|
61
75
|
mkdirSync(keyDir, { recursive: true });
|
|
62
76
|
mkdirSync(chainDir, { recursive: true });
|
|
77
|
+
if (opts.from) {
|
|
78
|
+
await seedFromRepo(opts.from, cwd, opts.force);
|
|
79
|
+
}
|
|
63
80
|
writeIfMissing(join(cwd, "policy.aarmos.toml"), POLICY_TEMPLATE, opts.force);
|
|
64
81
|
writeIfMissing(join(cwd, "avar.config.json"), AVAR_CONFIG_TEMPLATE, opts.force);
|
|
65
82
|
const gitignorePath = join(cwd, ".aarmos", ".gitignore");
|
|
@@ -78,16 +95,111 @@ function writeIfMissing(path, content, force) {
|
|
|
78
95
|
console.log(`\xB7 skipped (exists): ${path}`);
|
|
79
96
|
return;
|
|
80
97
|
}
|
|
98
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
81
99
|
writeFileSync(path, content);
|
|
82
100
|
console.log(`\u2713 wrote: ${path}`);
|
|
83
101
|
}
|
|
102
|
+
function parseFrom(from) {
|
|
103
|
+
if (from.startsWith("http://") || from.startsWith("https://")) {
|
|
104
|
+
return { tarballUrl: from, subdir: "", label: from };
|
|
105
|
+
}
|
|
106
|
+
const hashIdx = from.indexOf("#");
|
|
107
|
+
const [pathPart, refPart] = hashIdx >= 0 ? [from.slice(0, hashIdx), from.slice(hashIdx + 1)] : [from, "main"];
|
|
108
|
+
const segs = pathPart.split("/").filter(Boolean);
|
|
109
|
+
if (segs.length < 2) throw new Error(`--from: expected owner/repo, got "${from}"`);
|
|
110
|
+
const [owner, repo, ...rest] = segs;
|
|
111
|
+
const subdir = rest.join("/");
|
|
112
|
+
return {
|
|
113
|
+
tarballUrl: `https://codeload.github.com/${owner}/${repo}/tar.gz/refs/heads/${refPart}`,
|
|
114
|
+
subdir,
|
|
115
|
+
label: `${owner}/${repo}${refPart !== "main" ? `#${refPart}` : ""}${subdir ? `/${subdir}` : ""}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function seedFromRepo(from, cwd, force) {
|
|
119
|
+
const parsed = parseFrom(from);
|
|
120
|
+
console.log(`\u25B8 seeding from ${parsed.label}`);
|
|
121
|
+
const staging = mkdtempSync(join(tmpdir(), "aarmos-init-"));
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetch(parsed.tarballUrl);
|
|
124
|
+
if (!res.ok) throw new Error(`fetch ${parsed.tarballUrl}: ${res.status}`);
|
|
125
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
126
|
+
const tarPath = join(staging, "src.tar.gz");
|
|
127
|
+
writeFileSync(tarPath, buf);
|
|
128
|
+
const extractDir = join(staging, "extract");
|
|
129
|
+
mkdirSync(extractDir);
|
|
130
|
+
const tar = spawnSync("tar", ["-xzf", tarPath, "-C", extractDir, "--strip-components=1"], {
|
|
131
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
132
|
+
});
|
|
133
|
+
if (tar.status !== 0) throw new Error("tar extract failed (is `tar` on PATH?)");
|
|
134
|
+
const root = parsed.subdir ? join(extractDir, parsed.subdir) : extractDir;
|
|
135
|
+
if (!existsSync(root)) throw new Error(`subdir not found in repo: ${parsed.subdir}`);
|
|
136
|
+
let copied = 0;
|
|
137
|
+
for (const rel of IMPORT_ALLOWLIST) {
|
|
138
|
+
const src = join(root, rel);
|
|
139
|
+
if (!existsSync(src)) continue;
|
|
140
|
+
const dest = join(cwd, rel);
|
|
141
|
+
if (existsSync(dest) && !force) {
|
|
142
|
+
console.log(`\xB7 skipped (exists): ${rel} \u2014 pass --force to overwrite`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (statSync(src).isDirectory()) {
|
|
146
|
+
const violations = findKeyLikeFiles(src);
|
|
147
|
+
if (violations.length > 0) {
|
|
148
|
+
console.log(`\u2717 refusing to copy ${rel}: contains key-like files: ${violations.slice(0, 3).join(", ")}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
152
|
+
cpSync(src, dest, { recursive: true, force });
|
|
153
|
+
} else {
|
|
154
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
155
|
+
cpSync(src, dest, { force });
|
|
156
|
+
}
|
|
157
|
+
console.log(`\u2713 imported: ${rel}`);
|
|
158
|
+
copied++;
|
|
159
|
+
}
|
|
160
|
+
if (copied === 0) {
|
|
161
|
+
console.log("\xB7 no Aarmos config artifacts found in that repo; falling back to defaults");
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
try {
|
|
165
|
+
rmSync(staging, { recursive: true, force: true });
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function findKeyLikeFiles(dir) {
|
|
171
|
+
const out = [];
|
|
172
|
+
const walk = (d) => {
|
|
173
|
+
for (const name of readdirSync(d)) {
|
|
174
|
+
const full = join(d, name);
|
|
175
|
+
const st = statSync(full);
|
|
176
|
+
if (st.isDirectory()) {
|
|
177
|
+
walk(full);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (/\.(key|pem|p12|pfx)$/i.test(name) || /signing\.key$/i.test(name)) {
|
|
181
|
+
out.push(relative(dir, full));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (st.size < 32e3) {
|
|
185
|
+
try {
|
|
186
|
+
const head = readFileSync(full, "utf8").slice(0, 200);
|
|
187
|
+
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(head)) out.push(relative(dir, full));
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
walk(dir);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
84
196
|
|
|
85
197
|
// src/commands/run.ts
|
|
86
198
|
import { Command as Command2 } from "commander";
|
|
87
199
|
import { spawn } from "child_process";
|
|
88
200
|
|
|
89
201
|
// src/lib/avar.ts
|
|
90
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync } from "fs";
|
|
202
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
91
203
|
import { join as join2 } from "path";
|
|
92
204
|
import { createHash } from "crypto";
|
|
93
205
|
var CHAIN_DIR = ".aarmos/avar";
|
|
@@ -131,10 +243,10 @@ function serialize(receipt) {
|
|
|
131
243
|
}
|
|
132
244
|
|
|
133
245
|
// src/lib/policy.ts
|
|
134
|
-
import { existsSync as existsSync3, readFileSync as
|
|
246
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
135
247
|
import { createHash as createHash2 } from "crypto";
|
|
136
248
|
function loadPolicy(path) {
|
|
137
|
-
const raw = existsSync3(path) ?
|
|
249
|
+
const raw = existsSync3(path) ? readFileSync3(path, "utf8") : "";
|
|
138
250
|
const digest = createHash2("sha256").update(raw).digest("hex");
|
|
139
251
|
return { path, raw, digest };
|
|
140
252
|
}
|
|
@@ -242,7 +354,7 @@ function stripHopByHop(headers) {
|
|
|
242
354
|
|
|
243
355
|
// src/commands/daemon.ts
|
|
244
356
|
import { Command as Command4 } from "commander";
|
|
245
|
-
import { existsSync as existsSync4, readFileSync as
|
|
357
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
246
358
|
import { join as join3 } from "path";
|
|
247
359
|
import { spawn as spawn2 } from "child_process";
|
|
248
360
|
var PID_FILE = join3(process.cwd(), ".aarmos", "daemon.pid");
|
|
@@ -267,7 +379,7 @@ function daemonCommand() {
|
|
|
267
379
|
console.log("\xB7 no daemon running");
|
|
268
380
|
return;
|
|
269
381
|
}
|
|
270
|
-
const pid = Number(
|
|
382
|
+
const pid = Number(readFileSync4(PID_FILE, "utf8"));
|
|
271
383
|
try {
|
|
272
384
|
process.kill(pid);
|
|
273
385
|
} catch {
|
|
@@ -280,14 +392,14 @@ function daemonCommand() {
|
|
|
280
392
|
console.log("stopped");
|
|
281
393
|
return;
|
|
282
394
|
}
|
|
283
|
-
const pid = Number(
|
|
395
|
+
const pid = Number(readFileSync4(PID_FILE, "utf8"));
|
|
284
396
|
console.log(`running (pid ${pid})`);
|
|
285
397
|
});
|
|
286
398
|
return cmd;
|
|
287
399
|
}
|
|
288
400
|
function isRunning() {
|
|
289
401
|
if (!existsSync4(PID_FILE)) return false;
|
|
290
|
-
const pid = Number(
|
|
402
|
+
const pid = Number(readFileSync4(PID_FILE, "utf8"));
|
|
291
403
|
try {
|
|
292
404
|
process.kill(pid, 0);
|
|
293
405
|
return true;
|
|
@@ -298,7 +410,7 @@ function isRunning() {
|
|
|
298
410
|
|
|
299
411
|
// src/commands/verify.ts
|
|
300
412
|
import { Command as Command5 } from "commander";
|
|
301
|
-
import { existsSync as existsSync5, readFileSync as
|
|
413
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
302
414
|
import { unzipSync, strFromU8 } from "fflate";
|
|
303
415
|
|
|
304
416
|
// ../avar-core/src/canonicalize.ts
|
|
@@ -737,7 +849,7 @@ async function runVerify(opts) {
|
|
|
737
849
|
}
|
|
738
850
|
let bytes;
|
|
739
851
|
try {
|
|
740
|
-
bytes =
|
|
852
|
+
bytes = readFileSync5(file);
|
|
741
853
|
} catch (err) {
|
|
742
854
|
return emitError(
|
|
743
855
|
aarmosError("FILE_UNREADABLE", {
|
|
@@ -863,7 +975,7 @@ function verifyCommand() {
|
|
|
863
975
|
|
|
864
976
|
// src/commands/lint.ts
|
|
865
977
|
import { Command as Command6 } from "commander";
|
|
866
|
-
import { existsSync as existsSync6, readFileSync as
|
|
978
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
|
|
867
979
|
import { join as join4, extname } from "path";
|
|
868
980
|
|
|
869
981
|
// src/lib/asp-compile.ts
|
|
@@ -961,12 +1073,12 @@ function keygen() {
|
|
|
961
1073
|
// src/commands/lint.ts
|
|
962
1074
|
function collectFiles(target) {
|
|
963
1075
|
if (!existsSync6(target)) return [];
|
|
964
|
-
const st =
|
|
1076
|
+
const st = statSync2(target);
|
|
965
1077
|
if (st.isFile()) return [target];
|
|
966
1078
|
const out = [];
|
|
967
|
-
for (const name of
|
|
1079
|
+
for (const name of readdirSync2(target)) {
|
|
968
1080
|
const full = join4(target, name);
|
|
969
|
-
const s =
|
|
1081
|
+
const s = statSync2(full);
|
|
970
1082
|
if (s.isDirectory()) {
|
|
971
1083
|
if (name === "tests" || name === "node_modules" || name.startsWith(".")) continue;
|
|
972
1084
|
out.push(...collectFiles(full));
|
|
@@ -986,7 +1098,7 @@ function lintPath(target) {
|
|
|
986
1098
|
return issues;
|
|
987
1099
|
}
|
|
988
1100
|
for (const file of files) {
|
|
989
|
-
const src =
|
|
1101
|
+
const src = readFileSync6(file, "utf8");
|
|
990
1102
|
const result = compileAspYamlToCanonicalJson(src);
|
|
991
1103
|
if (!result.ok) {
|
|
992
1104
|
for (const m of result.errors) issues.push({ severity: "error", file, message: m });
|
|
@@ -1041,8 +1153,8 @@ function lintCommand() {
|
|
|
1041
1153
|
|
|
1042
1154
|
// src/commands/test.ts
|
|
1043
1155
|
import { Command as Command7 } from "commander";
|
|
1044
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1045
|
-
import { join as join5, extname as extname2, dirname } from "path";
|
|
1156
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
1157
|
+
import { join as join5, extname as extname2, dirname as dirname2 } from "path";
|
|
1046
1158
|
import { parse as parseYaml2 } from "yaml";
|
|
1047
1159
|
|
|
1048
1160
|
// src/lib/asp-eval.ts
|
|
@@ -1086,7 +1198,7 @@ function findAspYaml(startDir) {
|
|
|
1086
1198
|
const p = join5(d, cand);
|
|
1087
1199
|
if (existsSync7(p)) return p;
|
|
1088
1200
|
}
|
|
1089
|
-
const parent =
|
|
1201
|
+
const parent = dirname2(d);
|
|
1090
1202
|
if (parent === d) break;
|
|
1091
1203
|
d = parent;
|
|
1092
1204
|
}
|
|
@@ -1094,12 +1206,12 @@ function findAspYaml(startDir) {
|
|
|
1094
1206
|
}
|
|
1095
1207
|
function collectFixtures(target) {
|
|
1096
1208
|
if (!existsSync7(target)) return [];
|
|
1097
|
-
const st =
|
|
1209
|
+
const st = statSync3(target);
|
|
1098
1210
|
if (st.isFile()) return [target];
|
|
1099
1211
|
const out = [];
|
|
1100
|
-
for (const name of
|
|
1212
|
+
for (const name of readdirSync3(target)) {
|
|
1101
1213
|
const full = join5(target, name);
|
|
1102
|
-
const s =
|
|
1214
|
+
const s = statSync3(full);
|
|
1103
1215
|
if (s.isDirectory()) out.push(...collectFixtures(full));
|
|
1104
1216
|
else if ((extname2(name) === ".yaml" || extname2(name) === ".yml") && !name.endsWith(".asp.yaml"))
|
|
1105
1217
|
out.push(full);
|
|
@@ -1107,23 +1219,23 @@ function collectFixtures(target) {
|
|
|
1107
1219
|
return out;
|
|
1108
1220
|
}
|
|
1109
1221
|
function runFixtures(target) {
|
|
1110
|
-
const dir = existsSync7(target) &&
|
|
1222
|
+
const dir = existsSync7(target) && statSync3(target).isDirectory() ? target : "policies/tests";
|
|
1111
1223
|
const fixtures = collectFixtures(existsSync7(dir) ? dir : target);
|
|
1112
1224
|
const results = [];
|
|
1113
1225
|
for (const file of fixtures) {
|
|
1114
1226
|
let fx;
|
|
1115
1227
|
try {
|
|
1116
|
-
fx = parseYaml2(
|
|
1228
|
+
fx = parseYaml2(readFileSync7(file, "utf8"));
|
|
1117
1229
|
} catch (err) {
|
|
1118
1230
|
results.push({ file, name: "<parse>", ok: false, reason: err.message });
|
|
1119
1231
|
continue;
|
|
1120
1232
|
}
|
|
1121
|
-
const policyPath = fx.policy ? join5(
|
|
1233
|
+
const policyPath = fx.policy ? join5(dirname2(file), fx.policy) : findAspYaml(dirname2(file));
|
|
1122
1234
|
if (!policyPath || !existsSync7(policyPath)) {
|
|
1123
1235
|
results.push({ file, name: "<policy>", ok: false, reason: "no asp.yaml found" });
|
|
1124
1236
|
continue;
|
|
1125
1237
|
}
|
|
1126
|
-
const compiled = compileAspYamlToCanonicalJson(
|
|
1238
|
+
const compiled = compileAspYamlToCanonicalJson(readFileSync7(policyPath, "utf8"));
|
|
1127
1239
|
if (!compiled.ok) {
|
|
1128
1240
|
results.push({ file, name: "<compile>", ok: false, reason: compiled.errors.join("; ") });
|
|
1129
1241
|
continue;
|
|
@@ -1162,14 +1274,44 @@ ${results.length - failed.length}/${results.length} passed.
|
|
|
1162
1274
|
|
|
1163
1275
|
// src/commands/policy.ts
|
|
1164
1276
|
import { Command as Command8 } from "commander";
|
|
1165
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1277
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync4, watch as fsWatch, statSync as statSync4 } from "fs";
|
|
1278
|
+
import { dirname as dirname3, resolve } from "path";
|
|
1166
1279
|
function readAspOrDie(path) {
|
|
1167
1280
|
if (!existsSync8(path)) {
|
|
1168
1281
|
process.stderr.write(`\u2717 file not found: ${path}
|
|
1169
1282
|
`);
|
|
1170
1283
|
process.exit(1);
|
|
1171
1284
|
}
|
|
1172
|
-
return
|
|
1285
|
+
return readFileSync8(path, "utf8");
|
|
1286
|
+
}
|
|
1287
|
+
function runPolicyOnce(policyPath, testsDir, opts = {}) {
|
|
1288
|
+
const started = Date.now();
|
|
1289
|
+
let output = `
|
|
1290
|
+
\u25B8 ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} ${opts.label ?? policyPath}
|
|
1291
|
+
`;
|
|
1292
|
+
const src = readFileSync8(policyPath, "utf8");
|
|
1293
|
+
const compiled = compileAspYamlToCanonicalJson(src);
|
|
1294
|
+
if (!compiled.ok) {
|
|
1295
|
+
for (const m of compiled.errors) output += ` \u2717 compile: ${m}
|
|
1296
|
+
`;
|
|
1297
|
+
return { ok: false, output, passed: 0, total: 0 };
|
|
1298
|
+
}
|
|
1299
|
+
const toolCount = (compiled.body.policy.allowedTools?.length ?? 0) + (compiled.body.policy.deniedTools?.length ?? 0);
|
|
1300
|
+
output += ` \u2713 compiled (${toolCount} tool rule[s], issuer=${compiled.body.issuer?.pubKey ?? "(unsigned)"})
|
|
1301
|
+
`;
|
|
1302
|
+
if (!existsSync8(testsDir)) {
|
|
1303
|
+
output += ` \xB7 no fixtures at ${opts.testsLabel ?? testsDir} \u2014 skipping tests
|
|
1304
|
+
`;
|
|
1305
|
+
return { ok: true, output, passed: 0, total: 0 };
|
|
1306
|
+
}
|
|
1307
|
+
const results = runFixtures(testsDir);
|
|
1308
|
+
const failed = results.filter((r) => !r.ok);
|
|
1309
|
+
for (const r of failed) output += ` \u2717 ${r.file} :: ${r.name} \u2014 ${r.reason}
|
|
1310
|
+
`;
|
|
1311
|
+
const passed = results.length - failed.length;
|
|
1312
|
+
output += ` ${failed.length === 0 ? "\u2713" : "\u2717"} ${passed}/${results.length} passed (${Date.now() - started}ms)
|
|
1313
|
+
`;
|
|
1314
|
+
return { ok: failed.length === 0, output, passed, total: results.length };
|
|
1173
1315
|
}
|
|
1174
1316
|
function policyCommand() {
|
|
1175
1317
|
const cmd = new Command8("policy").description("Compile / sign ASP YAML policies.");
|
|
@@ -1224,6 +1366,46 @@ AARMOS_POLICY_PRIVATE_KEY (keep secret):
|
|
|
1224
1366
|
`
|
|
1225
1367
|
);
|
|
1226
1368
|
});
|
|
1369
|
+
cmd.command("watch <file>").description(
|
|
1370
|
+
"Recompile ASP YAML on save and re-run fixtures. Zero network, zero side effects \u2014 the tightest feedback loop for authoring policy."
|
|
1371
|
+
).option("--tests <dir>", "Fixtures directory to re-run on each change", "policies/tests").option("--debounce <ms>", "Debounce interval in ms", "150").action((file, opts) => {
|
|
1372
|
+
const policyPath = resolve(file);
|
|
1373
|
+
if (!existsSync8(policyPath)) {
|
|
1374
|
+
process.stderr.write(`\u2717 file not found: ${policyPath}
|
|
1375
|
+
`);
|
|
1376
|
+
process.exit(1);
|
|
1377
|
+
}
|
|
1378
|
+
const testsDir = resolve(opts.tests);
|
|
1379
|
+
const debounceMs = Math.max(0, Number(opts.debounce) || 150);
|
|
1380
|
+
const runOnce = () => {
|
|
1381
|
+
const { output } = runPolicyOnce(policyPath, testsDir, { label: file, testsLabel: opts.tests });
|
|
1382
|
+
process.stdout.write(output);
|
|
1383
|
+
};
|
|
1384
|
+
process.stdout.write(`aarmos policy watch
|
|
1385
|
+
policy : ${policyPath}
|
|
1386
|
+
tests : ${testsDir}
|
|
1387
|
+
`);
|
|
1388
|
+
runOnce();
|
|
1389
|
+
let timer = null;
|
|
1390
|
+
const schedule = () => {
|
|
1391
|
+
if (timer) clearTimeout(timer);
|
|
1392
|
+
timer = setTimeout(runOnce, debounceMs);
|
|
1393
|
+
};
|
|
1394
|
+
const watchTarget = (p) => {
|
|
1395
|
+
try {
|
|
1396
|
+
const st = statSync4(p);
|
|
1397
|
+
const opts2 = st.isDirectory() ? { recursive: true } : void 0;
|
|
1398
|
+
fsWatch(p, opts2, () => schedule());
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
watchTarget(policyPath);
|
|
1403
|
+
watchTarget(dirname3(policyPath));
|
|
1404
|
+
if (existsSync8(testsDir)) watchTarget(testsDir);
|
|
1405
|
+
process.stdout.write("\nwatching for changes \u2014 Ctrl+C to exit\n");
|
|
1406
|
+
setInterval(() => {
|
|
1407
|
+
}, 1 << 30);
|
|
1408
|
+
});
|
|
1227
1409
|
return cmd;
|
|
1228
1410
|
}
|
|
1229
1411
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aarmos/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Aarmos CLI — run any agent locally, across any protocol (MCP, OpenAPI, deep-link), with a signed AVAR receipt on every execution. Sovereign runtime, universal tool gateway, verifiable governance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aarmos",
|