@kenhkl/agent-moss 0.15.1 → 0.15.4

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/cli.d.ts CHANGED
@@ -1 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * AgentMoss CLI — TypeScript version.
4
+ * Supports: init | analyze | server
5
+ */
6
+ /** 命令模式核心:把一条 shell 命令当 a_next 分析,输出 Deny/Allow(--json 时完整
7
+ * JSON),返回退出码(Allow=0 / Deny=1)。导出供测试直接调用(CLI 分支再 process.exit)。
8
+ * 零文件读写——镜像 Python cli.py 的 _analyze_command。 */
9
+ declare function analyzeRawCommand(command: string, useJson?: boolean): Promise<number>;
10
+
11
+ export { analyzeRawCommand };
package/dist/cli.js CHANGED
@@ -161,8 +161,12 @@ function resolveConfigPath() {
161
161
  if (envPath) return envPath;
162
162
  const userConfig = path2.join(getConfigDir(), "config.json");
163
163
  if (fs.existsSync(userConfig)) return userConfig;
164
- const defaultConfig = path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example");
165
- if (fs.existsSync(defaultConfig)) {
164
+ const defaultCandidates = [
165
+ path2.join(__dirname, "config.json.example"),
166
+ path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
167
+ ];
168
+ const defaultConfig = defaultCandidates.find((p) => fs.existsSync(p));
169
+ if (defaultConfig) {
166
170
  const configDir = path2.dirname(userConfig);
167
171
  fs.mkdirSync(configDir, { recursive: true });
168
172
  const pkgConfig = loadConfigFile(defaultConfig);
@@ -1435,6 +1439,7 @@ __export(runtime_config_exports, {
1435
1439
  getRuntimeConfigPath: () => getRuntimeConfigPath,
1436
1440
  getScenarioRules: () => getScenarioRules,
1437
1441
  hasRuntimeConfig: () => hasRuntimeConfig,
1442
+ invalidateRuntimeConfigCache: () => invalidateRuntimeConfigCache,
1438
1443
  isL2CategoryEnabled: () => isL2CategoryEnabled,
1439
1444
  isLayerEnabled: () => isLayerEnabled,
1440
1445
  loadRuntimeConfig: () => loadRuntimeConfig,
@@ -1453,7 +1458,7 @@ __export(runtime_config_exports, {
1453
1458
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1454
1459
  updateSkillEnabled: () => updateSkillEnabled
1455
1460
  });
1456
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1461
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1457
1462
  import { join as join3 } from "path";
1458
1463
  import { fileURLToPath as fileURLToPath5 } from "url";
1459
1464
  import { dirname as dirname3 } from "path";
@@ -1470,6 +1475,7 @@ function saveRuntimeConfig(config) {
1470
1475
  const dir = p.substring(0, p.lastIndexOf("/"));
1471
1476
  if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1472
1477
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1478
+ invalidateRuntimeConfigCache();
1473
1479
  }
1474
1480
  function emptyRuntimeConfig() {
1475
1481
  return {
@@ -1483,6 +1489,9 @@ function emptyRuntimeConfig() {
1483
1489
  scenario_rules: {}
1484
1490
  };
1485
1491
  }
1492
+ function invalidateRuntimeConfigCache() {
1493
+ runtimeConfigCache = null;
1494
+ }
1486
1495
  function loadRuntimeConfig() {
1487
1496
  const p = getRuntimeConfigPath();
1488
1497
  if (!existsSync4(p)) {
@@ -1494,9 +1503,14 @@ function loadRuntimeConfig() {
1494
1503
  return seeded;
1495
1504
  }
1496
1505
  try {
1506
+ const st = statSync(p);
1507
+ const cacheKey = `${p}|${st.mtimeMs}|${st.size}`;
1508
+ if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
1509
+ return runtimeConfigCache.value;
1510
+ }
1497
1511
  const data = JSON.parse(readFileSync4(p, "utf-8"));
1498
1512
  const merged = mergeConfig(data, generateSourceDefaults());
1499
- return {
1513
+ const result = {
1500
1514
  version: data.version || 1,
1501
1515
  layers: {
1502
1516
  L1_heuristic: data.layers?.L1_heuristic ?? true,
@@ -1510,6 +1524,8 @@ function loadRuntimeConfig() {
1510
1524
  agent_rules: merged.agent_rules || {},
1511
1525
  scenario_rules: merged.scenario_rules || {}
1512
1526
  };
1527
+ runtimeConfigCache = { key: cacheKey, value: result };
1528
+ return result;
1513
1529
  } catch {
1514
1530
  return emptyRuntimeConfig();
1515
1531
  }
@@ -2499,7 +2515,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2499
2515
  saveRuntimeConfig(runtime);
2500
2516
  return runtime;
2501
2517
  }
2502
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2518
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2503
2519
  var init_runtime_config = __esm({
2504
2520
  "src/infra/runtime-config.ts"() {
2505
2521
  "use strict";
@@ -2529,6 +2545,7 @@ var init_runtime_config = __esm({
2529
2545
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2530
2546
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2531
2547
  };
2548
+ runtimeConfigCache = null;
2532
2549
  _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2533
2550
  _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2534
2551
  _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
@@ -2823,6 +2840,56 @@ function isInlineScriptCommand(actionDetail) {
2823
2840
  const re = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
2824
2841
  return re.test(actionDetail);
2825
2842
  }
2843
+ function resolvePathTokens(segment) {
2844
+ const out = [];
2845
+ const low = segment.toLowerCase();
2846
+ for (const m of low.matchAll(HOME_PREFIX_RE)) {
2847
+ const prefix = m[0];
2848
+ const endIdx = (m.index ?? 0) + m[0].length;
2849
+ const tail = low.slice(endIdx);
2850
+ if (!tail || !(tail[0] === "/" || tail[0] === " ")) {
2851
+ out.push([prefix, prefix]);
2852
+ continue;
2853
+ }
2854
+ const token = tail.trim().split(/\s+/)[0] || "";
2855
+ if (!token.startsWith("/")) continue;
2856
+ const cleaned = token.replace(/\/+$/, "").replace(/^['"]|['"]$/g, "");
2857
+ const segments = cleaned.split("/").filter((s) => s && s !== ".");
2858
+ if (!segments.some((s) => s === "..")) continue;
2859
+ let depth = 0;
2860
+ const kept = [];
2861
+ let escaped = false;
2862
+ for (const s of segments) {
2863
+ if (s === "..") {
2864
+ depth -= 1;
2865
+ if (kept.length) kept.pop();
2866
+ if (depth < 0) {
2867
+ escaped = true;
2868
+ depth = 0;
2869
+ }
2870
+ } else {
2871
+ depth += 1;
2872
+ kept.push(s);
2873
+ }
2874
+ }
2875
+ let resolved = kept.length ? "~/" + kept.join("/") : "~";
2876
+ if (escaped) resolved += "\uFF08\u26A0\uFE0F .. \u5F39\u6808\u8D85\u51FA\u4E3B\u76EE\u5F55\u6839\uFF0C\u5B9E\u9645\u76EE\u6807\u5728\u4E3B\u76EE\u5F55\u4E4B\u5916\uFF09";
2877
+ out.push([prefix + token, resolved]);
2878
+ }
2879
+ return out;
2880
+ }
2881
+ function buildPathContext(actionDetail) {
2882
+ if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2883
+ const stripped = stripLiteralTextSegments(actionDetail);
2884
+ const lines = [];
2885
+ for (const seg of splitCommandSegments(stripped)) {
2886
+ for (const [raw, lex] of resolvePathTokens(seg)) {
2887
+ lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2888
+ }
2889
+ }
2890
+ if (!lines.length) return "";
2891
+ return "## \u8DEF\u5F84\u89E3\u6790\u4E8B\u5B9E\uFF08\u8BCD\u6CD5\u5206\u6790\uFF0C\u786E\u5B9A\u6027\u7ED3\u8BBA\uFF09\n" + lines.join("\n") + "\n\n## \u8FD0\u884C\u65F6\u672A\u9A8C\u8BC1\uFF08\u4F9B\u53C2\u8003\uFF0C\u975E\u4E8B\u5B9E\uFF09\n- \u5206\u6790\u5668\u4E0D\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A\u65E0\u6CD5\u786E\u8BA4\u8DEF\u5F84\u4E2D\u7684\u4E2D\u95F4\u76EE\u5F55\u662F\u5426\u4E3A\u7B26\u53F7\u94FE\u63A5\uFF1B\u82E5\u4E2D\u95F4\u6BB5\u5728\u6267\u884C\u65F6\u662F\uFF08\u6216\u88AB\u66FF\u6362\u4E3A\uFF09\u6307\u5411\u4E3B\u76EE\u5F55\u5916\u7684\u8F6F\u94FE\uFF0C\u5B9E\u9645\u64CD\u4F5C\u76EE\u6807\u53EF\u80FD\u968F\u4E4B\u6539\u53D8\u3002\u8BCD\u6CD5\u89E3\u6790\u7ED3\u679C\u57FA\u4E8E\u6807\u51C6\u8DEF\u5F84\u8BED\u4E49\uFF0C\u4E0D\u5E94\u56E0\u731C\u6D4B\u300C\u89E3\u6790\u4F9D\u8D56\u67D0\u6BB5\u662F\u5426\u5B58\u5728\u300D\u800C\u5426\u5B9A\u5B83\u3002";
2892
+ }
2826
2893
  function stripLiteralTextSegments(actionDetail) {
2827
2894
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2828
2895
  const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
@@ -3187,7 +3254,7 @@ function isArchiveReadOfDir(command, dirPath) {
3187
3254
  }
3188
3255
  if (fw === "cp" || fw === "rsync") {
3189
3256
  const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3190
- if (segPaths.length && segPaths[0].includes(dpath)) return true;
3257
+ if (segPaths[0]?.includes(dpath)) return true;
3191
3258
  }
3192
3259
  }
3193
3260
  return false;
@@ -3349,7 +3416,7 @@ function checkLateralMovement(actionDetail) {
3349
3416
  }
3350
3417
  return null;
3351
3418
  }
3352
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3419
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3353
3420
  var init_heuristic = __esm({
3354
3421
  "src/engine/heuristic.ts"() {
3355
3422
  "use strict";
@@ -3377,6 +3444,8 @@ var init_heuristic = __esm({
3377
3444
  description: c.reason
3378
3445
  }));
3379
3446
  })();
3447
+ PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3448
+ HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3380
3449
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3381
3450
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3382
3451
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
@@ -5108,7 +5177,7 @@ var init_tracker = __esm({
5108
5177
  });
5109
5178
 
5110
5179
  // src/engine/script-content-analyzer.ts
5111
- import { statSync as statSync2, existsSync as existsSync9 } from "fs";
5180
+ import { statSync as statSync3, existsSync as existsSync9 } from "fs";
5112
5181
  import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
5113
5182
  import { homedir as homedir4 } from "os";
5114
5183
  function emptyChain() {
@@ -5222,7 +5291,7 @@ function extractInlineScriptPath(actionDetail) {
5222
5291
  try {
5223
5292
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5224
5293
  if (existsSync9(resolved)) {
5225
- const stat = statSync2(resolved);
5294
+ const stat = statSync3(resolved);
5226
5295
  if (stat.isFile()) return resolved;
5227
5296
  }
5228
5297
  } catch {
@@ -5236,7 +5305,7 @@ function extractInlineScriptPath(actionDetail) {
5236
5305
  try {
5237
5306
  const resolved = resolvePath2(p);
5238
5307
  if (existsSync9(resolved)) {
5239
- const stat = statSync2(resolved);
5308
+ const stat = statSync3(resolved);
5240
5309
  if (stat.isFile()) return resolved;
5241
5310
  }
5242
5311
  } catch {
@@ -5252,7 +5321,7 @@ function extractInlineScriptPath(actionDetail) {
5252
5321
  try {
5253
5322
  resolved = resolvePath2(p);
5254
5323
  if (existsSync9(resolved)) {
5255
- const stat = statSync2(resolved);
5324
+ const stat = statSync3(resolved);
5256
5325
  if (stat.isFile()) return resolved;
5257
5326
  }
5258
5327
  } catch {
@@ -5729,7 +5798,7 @@ async function runLLMAnalysis(input, previous) {
5729
5798
  } else {
5730
5799
  scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
5731
5800
  }
5732
- const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText);
5801
+ const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText, previous.pathContext ?? "");
5733
5802
  logPrompt(prompt, input.aNext, previous.llmLogPath);
5734
5803
  const result = await analyzeWithLLM(prompt, config);
5735
5804
  if (llmCache.size >= LLM_CACHE_MAX) {
@@ -5753,7 +5822,7 @@ ${prompt}
5753
5822
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
5754
5823
  }
5755
5824
  }
5756
- function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
5825
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "", pathContext = "") {
5757
5826
  const skillsText = formatSkillsForPrompt(matchedSkills);
5758
5827
  const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
5759
5828
  const promptHistoryText = formatPromptHistory(input.promptHistory);
@@ -5761,7 +5830,7 @@ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis,
5761
5830
  const hintsText = formatHints(previous);
5762
5831
  const securityJudgeTemplate = templates["security_judge"];
5763
5832
  if (securityJudgeTemplate) {
5764
- return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{{", "{").replaceAll("}}", "}");
5833
+ return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{path_context}", pathContext || "\uFF08\u672C\u547D\u4EE4\u65E0\u4E3B\u76EE\u5F55\u76F8\u5173\u8DEF\u5F84\u6BB5\uFF0C\u4E0D\u9002\u7528\uFF09").replaceAll("{{", "{").replaceAll("}}", "}");
5765
5834
  }
5766
5835
  console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
5767
5836
  return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -8182,6 +8251,21 @@ async function runAnalysis(input) {
8182
8251
  if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
8183
8252
  return effectiveResult;
8184
8253
  }
8254
+ } else {
8255
+ if (isInlineScriptCmd || isLiteralTextCmd) {
8256
+ const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
8257
+ if (inlineJudgment.actionable && !inlineJudgment.allowed) {
8258
+ return {
8259
+ allowed: false,
8260
+ reason: inlineJudgment.reason,
8261
+ riskLevel: inlineJudgment.riskLevel,
8262
+ riskType: inlineJudgment.riskType,
8263
+ confidence: 90,
8264
+ source: "logic_rule",
8265
+ violatedLayers: ["L2.1"]
8266
+ };
8267
+ }
8268
+ }
8185
8269
  }
8186
8270
  if (isLogicRulesEnabled()) {
8187
8271
  const formalSessions = [];
@@ -8283,12 +8367,14 @@ async function runAnalysis(input) {
8283
8367
  }
8284
8368
  let llmResult;
8285
8369
  try {
8370
+ const pathContext = buildPathContext(input.aNext.actionDetail);
8286
8371
  llmResult = await runLLMAnalysis(input, {
8287
8372
  heuristic: heuristicResult,
8288
8373
  logic: logicResult,
8289
8374
  skipL3Hints,
8290
8375
  scriptChain: scriptChain ?? void 0,
8291
- llmLogPath: input.llmLogPath
8376
+ llmLogPath: input.llmLogPath,
8377
+ pathContext
8292
8378
  });
8293
8379
  } catch (e) {
8294
8380
  const { getLLMFailMode: getLLMFailMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -10170,10 +10256,14 @@ function printHelp() {
10170
10256
 
10171
10257
  Commands:
10172
10258
  init Generate input.json template
10173
- analyze <input> Run security analysis on input JSON
10259
+ analyze <input> Analyze an input JSON file, or a raw shell command string
10260
+ e.g. agent-moss analyze "cat ~/.ssh/id_rsa" \u2192 prints Deny/Allow
10174
10261
  server Start HTTP API server
10175
10262
  install Install systemd service for AgentMoss
10176
10263
 
10264
+ Analyze options:
10265
+ --json Command mode: print the full JSON result instead of plain Deny/Allow
10266
+
10177
10267
  Server options:
10178
10268
  --host <host> Listen host (default: 127.0.0.1)
10179
10269
  --port <port> Listen port (default: 9090, 0=auto assign)
@@ -10182,6 +10272,22 @@ Install options:
10182
10272
  --enable Enable service on boot (systemctl enable)
10183
10273
  `);
10184
10274
  }
10275
+ async function analyzeRawCommand(command, useJson = false) {
10276
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
10277
+ const result = await runAnalysis2({
10278
+ sessionId: "cli-command",
10279
+ promptSession: "",
10280
+ actionHistory: [],
10281
+ aNext: { actionType: "bash", actionDetail: command },
10282
+ reason: "",
10283
+ osType: process.platform === "win32" ? "windows" : "linux",
10284
+ cwd: process.cwd(),
10285
+ agentId: ""
10286
+ });
10287
+ if (useJson) console.log(JSON.stringify(result, null, 2));
10288
+ else console.log(result.allowed ? "Allow" : "Deny");
10289
+ return result.allowed ? 0 : 1;
10290
+ }
10185
10291
  async function main() {
10186
10292
  const args = process.argv.slice(2);
10187
10293
  const command = args[0];
@@ -10207,14 +10313,33 @@ async function main() {
10207
10313
  break;
10208
10314
  }
10209
10315
  case "analyze": {
10210
- if (!args[1]) {
10211
- console.error("Usage: agent-moss analyze <input.json>");
10316
+ const target = args[1];
10317
+ if (!target) {
10318
+ console.error('Usage: agent-moss analyze <input.json | "shell command">');
10212
10319
  process.exit(1);
10213
10320
  }
10214
10321
  const fs7 = await import("fs");
10215
10322
  const path6 = await import("path");
10216
10323
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
10217
- const filePath = path6.resolve(args[1]);
10324
+ const useJson = args.includes("--json");
10325
+ const osType = process.platform === "win32" ? "windows" : "linux";
10326
+ const isFile = fs7.existsSync(target) && fs7.statSync(target).isFile();
10327
+ if (!isFile) {
10328
+ if (fs7.existsSync(target)) {
10329
+ console.error(`\u9519\u8BEF: \u4E0D\u662F\u6587\u4EF6: ${target}`);
10330
+ process.exit(2);
10331
+ }
10332
+ if (/\.(json|ya?ml|toml)$/i.test(target)) {
10333
+ console.error(`\u9519\u8BEF: \u6587\u4EF6\u4E0D\u5B58\u5728: ${target}`);
10334
+ process.exit(2);
10335
+ }
10336
+ if (!target.trim()) {
10337
+ console.error("\u9519\u8BEF: \u8F93\u5165\u4E3A\u7A7A\uFF08\u8BF7\u7ED9 JSON \u6587\u4EF6\u8DEF\u5F84\u6216 shell \u547D\u4EE4\uFF09");
10338
+ process.exit(2);
10339
+ }
10340
+ process.exit(await analyzeRawCommand(target, useJson));
10341
+ }
10342
+ const filePath = path6.resolve(target);
10218
10343
  const data = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
10219
10344
  const result = await runAnalysis2({
10220
10345
  sessionId: data.session_id || "unknown",
@@ -10426,6 +10551,9 @@ if (isMain) {
10426
10551
  process.exit(1);
10427
10552
  });
10428
10553
  }
10554
+ export {
10555
+ analyzeRawCommand
10556
+ };
10429
10557
  //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
10430
10558
  //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
10431
10559
  //! properties.py ALL_PROPERTIES 保持一致并双端测试确认。