@aarmos/cli 0.2.0 → 0.2.2

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 (2) hide show
  1. package/dist/index.js +285 -29
  2. 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,102 @@ var GITIGNORE_TEMPLATE = `# Aarmos local state
53
55
  .aarmos/keys/
54
56
  .aarmos/avar/
55
57
  `;
58
+ var TEMPLATES = {
59
+ hello: {
60
+ description: "Minimal \u2014 policy + a single echo playbook. Great first run.",
61
+ files: {
62
+ "playbooks/hello.md": '# hello\n\nOne-step playbook. Ask the agent to echo a greeting.\n\n## steps\n\n1. Say: "hello from aarmos"\n',
63
+ "policy.aarmos.toml": `# hello template \u2014 deny-by-default, no external tools.
64
+ [defaults]
65
+ gates.destructive = "deny"
66
+ rate.per_minute = 30
67
+ `
68
+ }
69
+ },
70
+ mcp: {
71
+ description: "MCP starter \u2014 one MCP adapter wired with a read-only scope.",
72
+ files: {
73
+ "policy.aarmos.toml": `# mcp template \u2014 one MCP adapter, read-only scope.
74
+ [defaults]
75
+ gates.destructive = "confirm"
76
+ rate.per_minute = 60
77
+
78
+ [[adapters.mcp]]
79
+ name = "example"
80
+ url = "https://mcp.example.com/server"
81
+ scopes = ["read:*"]
82
+ `,
83
+ "playbooks/mcp-probe.md": "# mcp-probe\n\nList tools exposed by the configured MCP server and print their schemas. Read-only.\n"
84
+ }
85
+ },
86
+ policy: {
87
+ description: "ASP starter \u2014 YAML policy-as-code with a golden test.",
88
+ files: {
89
+ "asp.yaml": `# ASP \u2014 Agent Security Policy (draft)
90
+ version: asp/1
91
+ defaults:
92
+ gates:
93
+ destructive: confirm
94
+ rate:
95
+ per_minute: 60
96
+ rules:
97
+ - id: deny-shell
98
+ when:
99
+ tool: "shell.*"
100
+ decision: deny
101
+ reason: "Shell tools are not permitted in this workspace."
102
+ `,
103
+ "policies/tests/deny-shell.yaml": `# golden test \u2014 shell.* must be denied
104
+ input:
105
+ tool: shell.exec
106
+ args: { cmd: "ls" }
107
+ expect:
108
+ decision: deny
109
+ source: workspace-policy
110
+ `
111
+ }
112
+ }
113
+ };
114
+ var IMPORT_ALLOWLIST = [
115
+ "policy.aarmos.toml",
116
+ "avar.config.json",
117
+ "asp.yaml",
118
+ "asp.yml",
119
+ "policies",
120
+ "playbooks",
121
+ ".aarmos/packs"
122
+ ];
56
123
  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).action(async (opts) => {
124
+ return new Command("init").description("Scaffold policy.aarmos.toml, avar.config.json, and local signing key").option("--force", "Overwrite existing files", false).option(
125
+ "--template <name>",
126
+ `Embedded starter template: ${Object.keys(TEMPLATES).join(" | ")}. Writes template files before defaults; existing files are preserved unless --force.`
127
+ ).option(
128
+ "--from <repo>",
129
+ "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."
130
+ ).action(async (opts) => {
58
131
  const cwd = process.cwd();
59
132
  const keyDir = join(cwd, ".aarmos", "keys");
60
133
  const chainDir = join(cwd, ".aarmos", "avar");
61
134
  mkdirSync(keyDir, { recursive: true });
62
135
  mkdirSync(chainDir, { recursive: true });
136
+ if (opts.template) {
137
+ const name = opts.template;
138
+ const tpl = TEMPLATES[name];
139
+ if (!tpl) {
140
+ console.error(
141
+ `\u2717 unknown template "${opts.template}". Known: ${Object.keys(TEMPLATES).join(", ")}`
142
+ );
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ console.log(`\u25B8 template: ${name} \u2014 ${tpl.description}`);
147
+ for (const [rel, contents] of Object.entries(tpl.files)) {
148
+ writeIfMissing(join(cwd, rel), contents, opts.force);
149
+ }
150
+ }
151
+ if (opts.from) {
152
+ await seedFromRepo(opts.from, cwd, opts.force);
153
+ }
63
154
  writeIfMissing(join(cwd, "policy.aarmos.toml"), POLICY_TEMPLATE, opts.force);
64
155
  writeIfMissing(join(cwd, "avar.config.json"), AVAR_CONFIG_TEMPLATE, opts.force);
65
156
  const gitignorePath = join(cwd, ".aarmos", ".gitignore");
@@ -78,16 +169,111 @@ function writeIfMissing(path, content, force) {
78
169
  console.log(`\xB7 skipped (exists): ${path}`);
79
170
  return;
80
171
  }
172
+ mkdirSync(dirname(path), { recursive: true });
81
173
  writeFileSync(path, content);
82
174
  console.log(`\u2713 wrote: ${path}`);
83
175
  }
176
+ function parseFrom(from) {
177
+ if (from.startsWith("http://") || from.startsWith("https://")) {
178
+ return { tarballUrl: from, subdir: "", label: from };
179
+ }
180
+ const hashIdx = from.indexOf("#");
181
+ const [pathPart, refPart] = hashIdx >= 0 ? [from.slice(0, hashIdx), from.slice(hashIdx + 1)] : [from, "main"];
182
+ const segs = pathPart.split("/").filter(Boolean);
183
+ if (segs.length < 2) throw new Error(`--from: expected owner/repo, got "${from}"`);
184
+ const [owner, repo, ...rest] = segs;
185
+ const subdir = rest.join("/");
186
+ return {
187
+ tarballUrl: `https://codeload.github.com/${owner}/${repo}/tar.gz/refs/heads/${refPart}`,
188
+ subdir,
189
+ label: `${owner}/${repo}${refPart !== "main" ? `#${refPart}` : ""}${subdir ? `/${subdir}` : ""}`
190
+ };
191
+ }
192
+ async function seedFromRepo(from, cwd, force) {
193
+ const parsed = parseFrom(from);
194
+ console.log(`\u25B8 seeding from ${parsed.label}`);
195
+ const staging = mkdtempSync(join(tmpdir(), "aarmos-init-"));
196
+ try {
197
+ const res = await fetch(parsed.tarballUrl);
198
+ if (!res.ok) throw new Error(`fetch ${parsed.tarballUrl}: ${res.status}`);
199
+ const buf = Buffer.from(await res.arrayBuffer());
200
+ const tarPath = join(staging, "src.tar.gz");
201
+ writeFileSync(tarPath, buf);
202
+ const extractDir = join(staging, "extract");
203
+ mkdirSync(extractDir);
204
+ const tar = spawnSync("tar", ["-xzf", tarPath, "-C", extractDir, "--strip-components=1"], {
205
+ stdio: ["ignore", "inherit", "inherit"]
206
+ });
207
+ if (tar.status !== 0) throw new Error("tar extract failed (is `tar` on PATH?)");
208
+ const root = parsed.subdir ? join(extractDir, parsed.subdir) : extractDir;
209
+ if (!existsSync(root)) throw new Error(`subdir not found in repo: ${parsed.subdir}`);
210
+ let copied = 0;
211
+ for (const rel of IMPORT_ALLOWLIST) {
212
+ const src = join(root, rel);
213
+ if (!existsSync(src)) continue;
214
+ const dest = join(cwd, rel);
215
+ if (existsSync(dest) && !force) {
216
+ console.log(`\xB7 skipped (exists): ${rel} \u2014 pass --force to overwrite`);
217
+ continue;
218
+ }
219
+ if (statSync(src).isDirectory()) {
220
+ const violations = findKeyLikeFiles(src);
221
+ if (violations.length > 0) {
222
+ console.log(`\u2717 refusing to copy ${rel}: contains key-like files: ${violations.slice(0, 3).join(", ")}`);
223
+ continue;
224
+ }
225
+ mkdirSync(dirname(dest), { recursive: true });
226
+ cpSync(src, dest, { recursive: true, force });
227
+ } else {
228
+ mkdirSync(dirname(dest), { recursive: true });
229
+ cpSync(src, dest, { force });
230
+ }
231
+ console.log(`\u2713 imported: ${rel}`);
232
+ copied++;
233
+ }
234
+ if (copied === 0) {
235
+ console.log("\xB7 no Aarmos config artifacts found in that repo; falling back to defaults");
236
+ }
237
+ } finally {
238
+ try {
239
+ rmSync(staging, { recursive: true, force: true });
240
+ } catch {
241
+ }
242
+ }
243
+ }
244
+ function findKeyLikeFiles(dir) {
245
+ const out = [];
246
+ const walk = (d) => {
247
+ for (const name of readdirSync(d)) {
248
+ const full = join(d, name);
249
+ const st = statSync(full);
250
+ if (st.isDirectory()) {
251
+ walk(full);
252
+ continue;
253
+ }
254
+ if (/\.(key|pem|p12|pfx)$/i.test(name) || /signing\.key$/i.test(name)) {
255
+ out.push(relative(dir, full));
256
+ continue;
257
+ }
258
+ if (st.size < 32e3) {
259
+ try {
260
+ const head = readFileSync(full, "utf8").slice(0, 200);
261
+ if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(head)) out.push(relative(dir, full));
262
+ } catch {
263
+ }
264
+ }
265
+ }
266
+ };
267
+ walk(dir);
268
+ return out;
269
+ }
84
270
 
85
271
  // src/commands/run.ts
86
272
  import { Command as Command2 } from "commander";
87
273
  import { spawn } from "child_process";
88
274
 
89
275
  // src/lib/avar.ts
90
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync } from "fs";
276
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
91
277
  import { join as join2 } from "path";
92
278
  import { createHash } from "crypto";
93
279
  var CHAIN_DIR = ".aarmos/avar";
@@ -131,10 +317,10 @@ function serialize(receipt) {
131
317
  }
132
318
 
133
319
  // src/lib/policy.ts
134
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
320
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
135
321
  import { createHash as createHash2 } from "crypto";
136
322
  function loadPolicy(path) {
137
- const raw = existsSync3(path) ? readFileSync2(path, "utf8") : "";
323
+ const raw = existsSync3(path) ? readFileSync3(path, "utf8") : "";
138
324
  const digest = createHash2("sha256").update(raw).digest("hex");
139
325
  return { path, raw, digest };
140
326
  }
@@ -242,7 +428,7 @@ function stripHopByHop(headers) {
242
428
 
243
429
  // src/commands/daemon.ts
244
430
  import { Command as Command4 } from "commander";
245
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
431
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
246
432
  import { join as join3 } from "path";
247
433
  import { spawn as spawn2 } from "child_process";
248
434
  var PID_FILE = join3(process.cwd(), ".aarmos", "daemon.pid");
@@ -267,7 +453,7 @@ function daemonCommand() {
267
453
  console.log("\xB7 no daemon running");
268
454
  return;
269
455
  }
270
- const pid = Number(readFileSync3(PID_FILE, "utf8"));
456
+ const pid = Number(readFileSync4(PID_FILE, "utf8"));
271
457
  try {
272
458
  process.kill(pid);
273
459
  } catch {
@@ -280,14 +466,14 @@ function daemonCommand() {
280
466
  console.log("stopped");
281
467
  return;
282
468
  }
283
- const pid = Number(readFileSync3(PID_FILE, "utf8"));
469
+ const pid = Number(readFileSync4(PID_FILE, "utf8"));
284
470
  console.log(`running (pid ${pid})`);
285
471
  });
286
472
  return cmd;
287
473
  }
288
474
  function isRunning() {
289
475
  if (!existsSync4(PID_FILE)) return false;
290
- const pid = Number(readFileSync3(PID_FILE, "utf8"));
476
+ const pid = Number(readFileSync4(PID_FILE, "utf8"));
291
477
  try {
292
478
  process.kill(pid, 0);
293
479
  return true;
@@ -298,7 +484,7 @@ function isRunning() {
298
484
 
299
485
  // src/commands/verify.ts
300
486
  import { Command as Command5 } from "commander";
301
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
487
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
302
488
  import { unzipSync, strFromU8 } from "fflate";
303
489
 
304
490
  // ../avar-core/src/canonicalize.ts
@@ -737,7 +923,7 @@ async function runVerify(opts) {
737
923
  }
738
924
  let bytes;
739
925
  try {
740
- bytes = readFileSync4(file);
926
+ bytes = readFileSync5(file);
741
927
  } catch (err) {
742
928
  return emitError(
743
929
  aarmosError("FILE_UNREADABLE", {
@@ -863,7 +1049,7 @@ function verifyCommand() {
863
1049
 
864
1050
  // src/commands/lint.ts
865
1051
  import { Command as Command6 } from "commander";
866
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync, readdirSync } from "fs";
1052
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
867
1053
  import { join as join4, extname } from "path";
868
1054
 
869
1055
  // src/lib/asp-compile.ts
@@ -961,12 +1147,12 @@ function keygen() {
961
1147
  // src/commands/lint.ts
962
1148
  function collectFiles(target) {
963
1149
  if (!existsSync6(target)) return [];
964
- const st = statSync(target);
1150
+ const st = statSync2(target);
965
1151
  if (st.isFile()) return [target];
966
1152
  const out = [];
967
- for (const name of readdirSync(target)) {
1153
+ for (const name of readdirSync2(target)) {
968
1154
  const full = join4(target, name);
969
- const s = statSync(full);
1155
+ const s = statSync2(full);
970
1156
  if (s.isDirectory()) {
971
1157
  if (name === "tests" || name === "node_modules" || name.startsWith(".")) continue;
972
1158
  out.push(...collectFiles(full));
@@ -986,7 +1172,7 @@ function lintPath(target) {
986
1172
  return issues;
987
1173
  }
988
1174
  for (const file of files) {
989
- const src = readFileSync5(file, "utf8");
1175
+ const src = readFileSync6(file, "utf8");
990
1176
  const result = compileAspYamlToCanonicalJson(src);
991
1177
  if (!result.ok) {
992
1178
  for (const m of result.errors) issues.push({ severity: "error", file, message: m });
@@ -1041,8 +1227,8 @@ function lintCommand() {
1041
1227
 
1042
1228
  // src/commands/test.ts
1043
1229
  import { Command as Command7 } from "commander";
1044
- import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1045
- import { join as join5, extname as extname2, dirname } from "path";
1230
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1231
+ import { join as join5, extname as extname2, dirname as dirname2 } from "path";
1046
1232
  import { parse as parseYaml2 } from "yaml";
1047
1233
 
1048
1234
  // src/lib/asp-eval.ts
@@ -1086,7 +1272,7 @@ function findAspYaml(startDir) {
1086
1272
  const p = join5(d, cand);
1087
1273
  if (existsSync7(p)) return p;
1088
1274
  }
1089
- const parent = dirname(d);
1275
+ const parent = dirname2(d);
1090
1276
  if (parent === d) break;
1091
1277
  d = parent;
1092
1278
  }
@@ -1094,12 +1280,12 @@ function findAspYaml(startDir) {
1094
1280
  }
1095
1281
  function collectFixtures(target) {
1096
1282
  if (!existsSync7(target)) return [];
1097
- const st = statSync2(target);
1283
+ const st = statSync3(target);
1098
1284
  if (st.isFile()) return [target];
1099
1285
  const out = [];
1100
- for (const name of readdirSync2(target)) {
1286
+ for (const name of readdirSync3(target)) {
1101
1287
  const full = join5(target, name);
1102
- const s = statSync2(full);
1288
+ const s = statSync3(full);
1103
1289
  if (s.isDirectory()) out.push(...collectFixtures(full));
1104
1290
  else if ((extname2(name) === ".yaml" || extname2(name) === ".yml") && !name.endsWith(".asp.yaml"))
1105
1291
  out.push(full);
@@ -1107,23 +1293,23 @@ function collectFixtures(target) {
1107
1293
  return out;
1108
1294
  }
1109
1295
  function runFixtures(target) {
1110
- const dir = existsSync7(target) && statSync2(target).isDirectory() ? target : "policies/tests";
1296
+ const dir = existsSync7(target) && statSync3(target).isDirectory() ? target : "policies/tests";
1111
1297
  const fixtures = collectFixtures(existsSync7(dir) ? dir : target);
1112
1298
  const results = [];
1113
1299
  for (const file of fixtures) {
1114
1300
  let fx;
1115
1301
  try {
1116
- fx = parseYaml2(readFileSync6(file, "utf8"));
1302
+ fx = parseYaml2(readFileSync7(file, "utf8"));
1117
1303
  } catch (err) {
1118
1304
  results.push({ file, name: "<parse>", ok: false, reason: err.message });
1119
1305
  continue;
1120
1306
  }
1121
- const policyPath = fx.policy ? join5(dirname(file), fx.policy) : findAspYaml(dirname(file));
1307
+ const policyPath = fx.policy ? join5(dirname2(file), fx.policy) : findAspYaml(dirname2(file));
1122
1308
  if (!policyPath || !existsSync7(policyPath)) {
1123
1309
  results.push({ file, name: "<policy>", ok: false, reason: "no asp.yaml found" });
1124
1310
  continue;
1125
1311
  }
1126
- const compiled = compileAspYamlToCanonicalJson(readFileSync6(policyPath, "utf8"));
1312
+ const compiled = compileAspYamlToCanonicalJson(readFileSync7(policyPath, "utf8"));
1127
1313
  if (!compiled.ok) {
1128
1314
  results.push({ file, name: "<compile>", ok: false, reason: compiled.errors.join("; ") });
1129
1315
  continue;
@@ -1162,14 +1348,44 @@ ${results.length - failed.length}/${results.length} passed.
1162
1348
 
1163
1349
  // src/commands/policy.ts
1164
1350
  import { Command as Command8 } from "commander";
1165
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1351
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync4, watch as fsWatch, statSync as statSync4 } from "fs";
1352
+ import { dirname as dirname3, resolve } from "path";
1166
1353
  function readAspOrDie(path) {
1167
1354
  if (!existsSync8(path)) {
1168
1355
  process.stderr.write(`\u2717 file not found: ${path}
1169
1356
  `);
1170
1357
  process.exit(1);
1171
1358
  }
1172
- return readFileSync7(path, "utf8");
1359
+ return readFileSync8(path, "utf8");
1360
+ }
1361
+ function runPolicyOnce(policyPath, testsDir, opts = {}) {
1362
+ const started = Date.now();
1363
+ let output = `
1364
+ \u25B8 ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} ${opts.label ?? policyPath}
1365
+ `;
1366
+ const src = readFileSync8(policyPath, "utf8");
1367
+ const compiled = compileAspYamlToCanonicalJson(src);
1368
+ if (!compiled.ok) {
1369
+ for (const m of compiled.errors) output += ` \u2717 compile: ${m}
1370
+ `;
1371
+ return { ok: false, output, passed: 0, total: 0 };
1372
+ }
1373
+ const toolCount = (compiled.body.policy.allowedTools?.length ?? 0) + (compiled.body.policy.deniedTools?.length ?? 0);
1374
+ output += ` \u2713 compiled (${toolCount} tool rule[s], issuer=${compiled.body.issuer?.pubKey ?? "(unsigned)"})
1375
+ `;
1376
+ if (!existsSync8(testsDir)) {
1377
+ output += ` \xB7 no fixtures at ${opts.testsLabel ?? testsDir} \u2014 skipping tests
1378
+ `;
1379
+ return { ok: true, output, passed: 0, total: 0 };
1380
+ }
1381
+ const results = runFixtures(testsDir);
1382
+ const failed = results.filter((r) => !r.ok);
1383
+ for (const r of failed) output += ` \u2717 ${r.file} :: ${r.name} \u2014 ${r.reason}
1384
+ `;
1385
+ const passed = results.length - failed.length;
1386
+ output += ` ${failed.length === 0 ? "\u2713" : "\u2717"} ${passed}/${results.length} passed (${Date.now() - started}ms)
1387
+ `;
1388
+ return { ok: failed.length === 0, output, passed, total: results.length };
1173
1389
  }
1174
1390
  function policyCommand() {
1175
1391
  const cmd = new Command8("policy").description("Compile / sign ASP YAML policies.");
@@ -1224,6 +1440,46 @@ AARMOS_POLICY_PRIVATE_KEY (keep secret):
1224
1440
  `
1225
1441
  );
1226
1442
  });
1443
+ cmd.command("watch <file>").description(
1444
+ "Recompile ASP YAML on save and re-run fixtures. Zero network, zero side effects \u2014 the tightest feedback loop for authoring policy."
1445
+ ).option("--tests <dir>", "Fixtures directory to re-run on each change", "policies/tests").option("--debounce <ms>", "Debounce interval in ms", "150").action((file, opts) => {
1446
+ const policyPath = resolve(file);
1447
+ if (!existsSync8(policyPath)) {
1448
+ process.stderr.write(`\u2717 file not found: ${policyPath}
1449
+ `);
1450
+ process.exit(1);
1451
+ }
1452
+ const testsDir = resolve(opts.tests);
1453
+ const debounceMs = Math.max(0, Number(opts.debounce) || 150);
1454
+ const runOnce = () => {
1455
+ const { output } = runPolicyOnce(policyPath, testsDir, { label: file, testsLabel: opts.tests });
1456
+ process.stdout.write(output);
1457
+ };
1458
+ process.stdout.write(`aarmos policy watch
1459
+ policy : ${policyPath}
1460
+ tests : ${testsDir}
1461
+ `);
1462
+ runOnce();
1463
+ let timer = null;
1464
+ const schedule = () => {
1465
+ if (timer) clearTimeout(timer);
1466
+ timer = setTimeout(runOnce, debounceMs);
1467
+ };
1468
+ const watchTarget = (p) => {
1469
+ try {
1470
+ const st = statSync4(p);
1471
+ const opts2 = st.isDirectory() ? { recursive: true } : void 0;
1472
+ fsWatch(p, opts2, () => schedule());
1473
+ } catch {
1474
+ }
1475
+ };
1476
+ watchTarget(policyPath);
1477
+ watchTarget(dirname3(policyPath));
1478
+ if (existsSync8(testsDir)) watchTarget(testsDir);
1479
+ process.stdout.write("\nwatching for changes \u2014 Ctrl+C to exit\n");
1480
+ setInterval(() => {
1481
+ }, 1 << 30);
1482
+ });
1227
1483
  return cmd;
1228
1484
  }
1229
1485
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aarmos/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",