calllint 1.0.0 → 1.1.0

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 +786 -229
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync12 } from "node:fs";
4
+ import { readFileSync as readFileSync17 } from "node:fs";
5
5
  import { execFileSync } from "node:child_process";
6
6
 
7
7
  // src/args.ts
@@ -58,16 +58,23 @@ USAGE
58
58
 
59
59
  COMMANDS
60
60
  check [target] Compact safety decision for an MCP config or install snippet
61
+ inventory List all discovered agent configs (auto-discovery, no scan)
61
62
  scan-all Scan every agent-tool surface in the repo (compact table)
62
63
  explain <server> Explain the verdict for one server from the last scan
63
64
  verify [target] Compare a fresh scan against the baseline (drift / rug-pull)
64
65
 
65
66
  Advanced:
66
67
  scan [target] Full ScanReport for an MCP config / npm:<pkg> / github:<repo>
68
+ scan --auto Discover and scan all agent configs (auto-discovery)
69
+ scan --agent <type> Discover and scan a specific agent (cursor, claude-code, claude-desktop, vscode, windsurf)
70
+ action inspect <f> Preflight a planned external action (calllint.action.v0)
71
+ inbox inspect <f> Preflight a normalized agent inbox event
67
72
  diagnostics [target] Emit editor/agent-host diagnostics JSON (calllint.diagnostics.v0)
68
73
  baseline [target] Record the approved risk surface as a baseline
69
74
  approve Record the repo-wide capability surface as approved state (L4)
70
- receipt verify <f> Structurally validate a local calllint.receipt.v0 file
75
+ receipt verify <f> Validate a calllint.receipt.v0 (structure + signature if present)
76
+ receipt sign <f> Sign a receipt with a local key (--key; development/testing)
77
+ receipt keygen Generate a local ed25519 keypair (--out; development/testing)
71
78
  gen-rule --host <h> Emit the CallLint agent-safety rule for a host (CLAUDE.md, etc.)
72
79
  policy init Write a default calllint.policy.json
73
80
  policy explain Show the effective policy
@@ -85,6 +92,8 @@ TARGETS
85
92
  github:<owner/repo>[@ref] A GitHub repo (requires --online)
86
93
 
87
94
  SCAN OPTIONS
95
+ --auto Discover and scan all agent configs (P0+P1: Cursor, Claude Code, Claude Desktop, VS Code, Windsurf)
96
+ --agent <type> Discover and scan a specific agent type
88
97
  --changed Scan only the agent-tool configs changed in the git diff
89
98
  --json Emit the ScanReport JSON (stable, emoji-free)
90
99
  --compact One line per server
@@ -93,7 +102,7 @@ SCAN OPTIONS
93
102
  --markdown Emit Markdown for PR comments / GitHub Step Summary
94
103
  --html Emit a self-contained HTML report
95
104
  --badge Emit a shields.io endpoint badge JSON (SAFE/REVIEW/UNKNOWN/BLOCK)
96
- --receipt Also write a local calllint.receipt.v0 (offline reporting layer)
105
+ --receipt Generate a cryptographically-verifiable audit receipt (for CI/compliance)
97
106
  --receipt-out <f> Receipt output path (default: calllint-receipt.json)
98
107
  --policy <file> Use a policy file (default: built-in defaults)
99
108
  --stdin Read config JSON from stdin
@@ -108,6 +117,9 @@ VERIFY OPTIONS
108
117
  --json Emit the drift report JSON
109
118
 
110
119
  EXAMPLES
120
+ calllint inventory # List discovered agent configs
121
+ calllint scan --auto # Discover and scan all agents
122
+ calllint scan --agent cursor # Scan only Cursor configs
111
123
  calllint check .cursor/mcp.json
112
124
  calllint check npm:mcp-weather@1.0.0
113
125
  echo "npx -y demo-mcp@1.2.3" | calllint check --stdin
@@ -115,7 +127,10 @@ EXAMPLES
115
127
  calllint check ./mcp.json --json
116
128
  calllint scan .cursor/mcp.json --markdown
117
129
  calllint scan .cursor/mcp.json --badge > calllint-badge.json
118
- calllint scan .cursor/mcp.json --receipt && calllint receipt verify calllint-receipt.json
130
+ calllint scan .cursor/mcp.json --receipt # Generate audit receipt
131
+ calllint receipt verify calllint-receipt.json # Verify receipt integrity
132
+ calllint action inspect payment.json
133
+ calllint inbox inspect gmail-reply.normalized.json
119
134
  calllint verify ./mcp.json --ci
120
135
  calllint approve && calllint verify --approved --ci
121
136
  calllint explain filesystem
@@ -125,7 +140,7 @@ function helpCommand() {
125
140
  }
126
141
 
127
142
  // src/commands/scan.ts
128
- import { join as join6, resolve as resolve2 } from "node:path";
143
+ import { join as join11, resolve as resolve3 } from "node:path";
129
144
 
130
145
  // ../../packages/policy/src/defaultPolicy.ts
131
146
  function defaultPolicy() {
@@ -3883,11 +3898,437 @@ function readDocumentSurfaces(dir) {
3883
3898
  }
3884
3899
 
3885
3900
  // src/commands/scan.ts
3886
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
3901
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "node:fs";
3902
+
3903
+ // ../../packages/discovery/src/registry.ts
3904
+ var ExtractorRegistry = class {
3905
+ extractors = /* @__PURE__ */ new Map();
3906
+ /**
3907
+ * Register an agent extractor.
3908
+ */
3909
+ register(extractor) {
3910
+ if (this.extractors.has(extractor.agentType)) {
3911
+ throw new Error(`Extractor already registered for agent type: ${extractor.agentType}`);
3912
+ }
3913
+ this.extractors.set(extractor.agentType, extractor);
3914
+ }
3915
+ /**
3916
+ * Get extractor for a specific agent type.
3917
+ */
3918
+ get(agentType) {
3919
+ return this.extractors.get(agentType);
3920
+ }
3921
+ /**
3922
+ * Get all registered extractors.
3923
+ */
3924
+ getAll() {
3925
+ return Array.from(this.extractors.values());
3926
+ }
3927
+ /**
3928
+ * Get extractors filtered by agent types.
3929
+ */
3930
+ getByTypes(agentTypes) {
3931
+ return agentTypes.map((type) => this.extractors.get(type)).filter((e) => e !== void 0);
3932
+ }
3933
+ /**
3934
+ * Get extractors filtered by priority tier.
3935
+ */
3936
+ getByPriority(priority) {
3937
+ return this.getAll().filter((e) => e.priority === priority);
3938
+ }
3939
+ /**
3940
+ * Get all extractors sorted by priority (P0 first, P3 last).
3941
+ */
3942
+ getAllSortedByPriority() {
3943
+ const priorityOrder = {
3944
+ P0: 0,
3945
+ P1: 1,
3946
+ P2: 2,
3947
+ P3: 3
3948
+ };
3949
+ return this.getAll().sort((a, b) => {
3950
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
3951
+ });
3952
+ }
3953
+ /**
3954
+ * Clear all registered extractors (for testing).
3955
+ */
3956
+ clear() {
3957
+ this.extractors.clear();
3958
+ }
3959
+ };
3960
+ var registry = new ExtractorRegistry();
3961
+
3962
+ // ../../packages/discovery/src/extractors/base.ts
3963
+ var BaseAgentExtractor = class {
3964
+ /**
3965
+ * Resolve home directory (~) to absolute path.
3966
+ */
3967
+ resolveHome() {
3968
+ const home = process.env.HOME || process.env.USERPROFILE;
3969
+ if (!home) {
3970
+ throw new Error("Could not resolve home directory (HOME/USERPROFILE not set)");
3971
+ }
3972
+ return home;
3973
+ }
3974
+ /**
3975
+ * Get platform-specific application data directory.
3976
+ *
3977
+ * Windows: %APPDATA% (Roaming)
3978
+ * macOS: ~/Library/Application Support
3979
+ * Linux: ~/.config (XDG_CONFIG_HOME or default)
3980
+ */
3981
+ getAppDataDir() {
3982
+ const platform = process.platform;
3983
+ if (platform === "win32") {
3984
+ const appData = process.env.APPDATA;
3985
+ if (!appData) {
3986
+ throw new Error("Could not resolve APPDATA directory on Windows");
3987
+ }
3988
+ return appData;
3989
+ }
3990
+ if (platform === "darwin") {
3991
+ return `${this.resolveHome()}/Library/Application Support`;
3992
+ }
3993
+ return process.env.XDG_CONFIG_HOME || `${this.resolveHome()}/.config`;
3994
+ }
3995
+ };
3996
+
3997
+ // ../../packages/discovery/src/path-resolver.ts
3998
+ import { resolve as resolve2, join as join6, isAbsolute } from "node:path";
3999
+ import { existsSync as existsSync6, statSync as statSync3 } from "node:fs";
4000
+ function resolvePath(path, cwd) {
4001
+ if (path.startsWith("~/") || path === "~") {
4002
+ const home = process.env.HOME || process.env.USERPROFILE;
4003
+ if (!home) {
4004
+ throw new Error("Could not resolve home directory (HOME/USERPROFILE not set)");
4005
+ }
4006
+ return join6(home, path.slice(2));
4007
+ }
4008
+ if (isAbsolute(path)) {
4009
+ return path;
4010
+ }
4011
+ return resolve2(cwd, path);
4012
+ }
4013
+ function isRegularFile(path) {
4014
+ try {
4015
+ if (!existsSync6(path)) {
4016
+ return false;
4017
+ }
4018
+ const stats = statSync3(path);
4019
+ return stats.isFile();
4020
+ } catch {
4021
+ return false;
4022
+ }
4023
+ }
4024
+ function isReasonableSize(path) {
4025
+ try {
4026
+ const stats = statSync3(path);
4027
+ const maxSize = 10 * 1024 * 1024;
4028
+ return stats.size < maxSize;
4029
+ } catch {
4030
+ return false;
4031
+ }
4032
+ }
4033
+ function validateConfigPath(path) {
4034
+ return isRegularFile(path) && isReasonableSize(path);
4035
+ }
4036
+
4037
+ // ../../packages/discovery/src/extractors/cursor.ts
4038
+ import { readFileSync as readFileSync7 } from "node:fs";
4039
+ var CursorExtractor = class extends BaseAgentExtractor {
4040
+ agentType = "cursor";
4041
+ priority = "P0";
4042
+ discover(cwd) {
4043
+ const configs = [];
4044
+ const projectPath = resolvePath(".cursor/mcp.json", cwd);
4045
+ configs.push(this.createConfig(projectPath));
4046
+ try {
4047
+ const home = this.resolveHome();
4048
+ const userPath = resolvePath(".cursor/mcp.json", home);
4049
+ if (userPath !== projectPath) {
4050
+ configs.push(this.createConfig(userPath));
4051
+ }
4052
+ } catch {
4053
+ }
4054
+ return configs;
4055
+ }
4056
+ createConfig(configPath) {
4057
+ const exists = this.isValidConfig(configPath);
4058
+ return {
4059
+ agentType: this.agentType,
4060
+ configPath,
4061
+ exists,
4062
+ kind: "cursor-mcp-config",
4063
+ priority: this.priority
4064
+ };
4065
+ }
4066
+ /**
4067
+ * Check if path is a valid Cursor config.
4068
+ * Must exist, be regular file, reasonable size, and contain mcpServers key.
4069
+ */
4070
+ isValidConfig(path) {
4071
+ if (!validateConfigPath(path)) {
4072
+ return false;
4073
+ }
4074
+ try {
4075
+ const content = readFileSync7(path, "utf8");
4076
+ const json = JSON.parse(content);
4077
+ return typeof json === "object" && json !== null && "mcpServers" in json;
4078
+ } catch {
4079
+ return false;
4080
+ }
4081
+ }
4082
+ };
4083
+
4084
+ // ../../packages/discovery/src/extractors/claude-code.ts
4085
+ import { readFileSync as readFileSync8 } from "node:fs";
4086
+ import { join as join7 } from "node:path";
4087
+ var ClaudeCodeExtractor = class extends BaseAgentExtractor {
4088
+ agentType = "claude-code";
4089
+ priority = "P0";
4090
+ discover(cwd) {
4091
+ const configs = [];
4092
+ const projectPath = resolvePath(".claude/settings.json", cwd);
4093
+ configs.push(this.createConfig(projectPath));
4094
+ try {
4095
+ const userPath = this.getUserSettingsPath();
4096
+ if (userPath !== projectPath) {
4097
+ configs.push(this.createConfig(userPath));
4098
+ }
4099
+ } catch {
4100
+ }
4101
+ return configs;
4102
+ }
4103
+ getUserSettingsPath() {
4104
+ const appDataDir = this.getAppDataDir();
4105
+ return join7(appDataDir, "Claude", "settings.json");
4106
+ }
4107
+ createConfig(configPath) {
4108
+ const exists = this.isValidConfig(configPath);
4109
+ return {
4110
+ agentType: this.agentType,
4111
+ configPath,
4112
+ exists,
4113
+ kind: "claude-settings",
4114
+ priority: this.priority
4115
+ };
4116
+ }
4117
+ /**
4118
+ * Check if path is a valid Claude Code config.
4119
+ * Must exist, be regular file, reasonable size, and contain mcpServers key.
4120
+ */
4121
+ isValidConfig(path) {
4122
+ if (!validateConfigPath(path)) {
4123
+ return false;
4124
+ }
4125
+ try {
4126
+ const content = readFileSync8(path, "utf8");
4127
+ const json = JSON.parse(content);
4128
+ return typeof json === "object" && json !== null && "mcpServers" in json;
4129
+ } catch {
4130
+ return false;
4131
+ }
4132
+ }
4133
+ };
4134
+
4135
+ // ../../packages/discovery/src/extractors/claude-desktop.ts
4136
+ import { readFileSync as readFileSync9 } from "node:fs";
4137
+ import { join as join8 } from "node:path";
4138
+ var ClaudeDesktopExtractor = class extends BaseAgentExtractor {
4139
+ agentType = "claude-desktop";
4140
+ priority = "P0";
4141
+ discover(_cwd) {
4142
+ const configs = [];
4143
+ try {
4144
+ const userPath = this.getUserConfigPath();
4145
+ configs.push(this.createConfig(userPath));
4146
+ } catch {
4147
+ }
4148
+ return configs;
4149
+ }
4150
+ getUserConfigPath() {
4151
+ const appDataDir = this.getAppDataDir();
4152
+ return join8(appDataDir, "Claude", "claude_desktop_config.json");
4153
+ }
4154
+ createConfig(configPath) {
4155
+ const exists = this.isValidConfig(configPath);
4156
+ return {
4157
+ agentType: this.agentType,
4158
+ configPath,
4159
+ exists,
4160
+ kind: "claude-settings",
4161
+ priority: this.priority
4162
+ };
4163
+ }
4164
+ /**
4165
+ * Check if path is a valid Claude Desktop config.
4166
+ * Must exist, be regular file, reasonable size, and contain mcpServers key.
4167
+ */
4168
+ isValidConfig(path) {
4169
+ if (!validateConfigPath(path)) {
4170
+ return false;
4171
+ }
4172
+ try {
4173
+ const content = readFileSync9(path, "utf8");
4174
+ const json = JSON.parse(content);
4175
+ return typeof json === "object" && json !== null && "mcpServers" in json;
4176
+ } catch {
4177
+ return false;
4178
+ }
4179
+ }
4180
+ };
4181
+
4182
+ // ../../packages/discovery/src/extractors/vscode.ts
4183
+ import { readFileSync as readFileSync10 } from "node:fs";
4184
+ import { join as join9 } from "node:path";
4185
+ var VSCodeExtractor = class extends BaseAgentExtractor {
4186
+ agentType = "vscode";
4187
+ priority = "P1";
4188
+ discover(cwd) {
4189
+ const configs = [];
4190
+ try {
4191
+ const userPath = this.getUserConfigPath();
4192
+ configs.push(this.createConfig(userPath));
4193
+ } catch {
4194
+ }
4195
+ return configs;
4196
+ }
4197
+ getUserConfigPath() {
4198
+ const appDataDir = this.getAppDataDir();
4199
+ return join9(appDataDir, "Code", "User", "mcp.json");
4200
+ }
4201
+ createConfig(configPath) {
4202
+ const exists = this.isValidConfig(configPath);
4203
+ return {
4204
+ agentType: this.agentType,
4205
+ configPath,
4206
+ exists,
4207
+ kind: "vscode-mcp-config",
4208
+ priority: this.priority
4209
+ };
4210
+ }
4211
+ /**
4212
+ * Check if path is a valid VS Code MCP config.
4213
+ * Must exist, be regular file, reasonable size, and contain mcpServers key.
4214
+ */
4215
+ isValidConfig(path) {
4216
+ if (!validateConfigPath(path)) {
4217
+ return false;
4218
+ }
4219
+ try {
4220
+ const content = readFileSync10(path, "utf8");
4221
+ const json = JSON.parse(content);
4222
+ return typeof json === "object" && json !== null && "mcpServers" in json && json.mcpServers !== null;
4223
+ } catch {
4224
+ return false;
4225
+ }
4226
+ }
4227
+ };
4228
+
4229
+ // ../../packages/discovery/src/extractors/windsurf.ts
4230
+ import { readFileSync as readFileSync11 } from "node:fs";
4231
+ import { join as join10 } from "node:path";
4232
+ var WindsurfExtractor = class extends BaseAgentExtractor {
4233
+ agentType = "windsurf";
4234
+ priority = "P1";
4235
+ discover(cwd) {
4236
+ const configs = [];
4237
+ try {
4238
+ const userPath = this.getUserConfigPath();
4239
+ configs.push(this.createConfig(userPath));
4240
+ } catch {
4241
+ }
4242
+ return configs;
4243
+ }
4244
+ getUserConfigPath() {
4245
+ const appDataDir = this.getAppDataDir();
4246
+ return join10(appDataDir, "Windsurf", "mcp.json");
4247
+ }
4248
+ createConfig(configPath) {
4249
+ const exists = this.isValidConfig(configPath);
4250
+ return {
4251
+ agentType: this.agentType,
4252
+ configPath,
4253
+ exists,
4254
+ kind: "windsurf-mcp-config",
4255
+ priority: this.priority
4256
+ };
4257
+ }
4258
+ /**
4259
+ * Check if path is a valid Windsurf MCP config.
4260
+ * Must exist, be regular file, reasonable size, and contain mcpServers key.
4261
+ */
4262
+ isValidConfig(path) {
4263
+ if (!validateConfigPath(path)) {
4264
+ return false;
4265
+ }
4266
+ try {
4267
+ const content = readFileSync11(path, "utf8");
4268
+ const json = JSON.parse(content);
4269
+ return typeof json === "object" && json !== null && "mcpServers" in json && json.mcpServers !== null;
4270
+ } catch {
4271
+ return false;
4272
+ }
4273
+ }
4274
+ };
4275
+
4276
+ // ../../packages/discovery/src/bootstrap.ts
4277
+ function bootstrapExtractors() {
4278
+ registry.register(new CursorExtractor());
4279
+ registry.register(new ClaudeCodeExtractor());
4280
+ registry.register(new ClaudeDesktopExtractor());
4281
+ registry.register(new VSCodeExtractor());
4282
+ registry.register(new WindsurfExtractor());
4283
+ }
4284
+ bootstrapExtractors();
4285
+
4286
+ // ../../packages/discovery/src/discovery-engine.ts
4287
+ function discoverConfigs(options) {
4288
+ const { cwd, agentTypes, includeMissing = false } = options;
4289
+ const extractors = agentTypes ? registry.getByTypes(agentTypes) : registry.getAllSortedByPriority();
4290
+ const results = extractors.map((extractor) => {
4291
+ try {
4292
+ return extractor.discover(cwd);
4293
+ } catch (error) {
4294
+ console.error(`[discovery] Extractor ${extractor.agentType} failed:`, error);
4295
+ return [];
4296
+ }
4297
+ });
4298
+ const allDiscovered = results.flat();
4299
+ const discovered = includeMissing ? allDiscovered : allDiscovered.filter((config) => config.exists);
4300
+ const searchedPaths = allDiscovered.map((config) => config.configPath);
4301
+ return {
4302
+ cwd,
4303
+ discovered,
4304
+ searchedPaths
4305
+ };
4306
+ }
4307
+ function discoverAgent(agentType, options) {
4308
+ const extractor = registry.get(agentType);
4309
+ if (!extractor) {
4310
+ throw new Error(`Unknown agent type: ${agentType}`);
4311
+ }
4312
+ try {
4313
+ return extractor.discover(options.cwd);
4314
+ } catch (error) {
4315
+ console.error(`[discovery] Failed to discover ${agentType}:`, error);
4316
+ return [];
4317
+ }
4318
+ }
4319
+
4320
+ // src/commands/scan.ts
3887
4321
  function scanCommand(args, deps) {
3888
4322
  if (flagBool(args.flags, "changed")) {
3889
4323
  return scanChangedCommand(args, deps);
3890
4324
  }
4325
+ if (flagBool(args.flags, "auto")) {
4326
+ return scanAutoCommand(args, deps);
4327
+ }
4328
+ const agentType = flagStr(args.flags, "agent");
4329
+ if (agentType) {
4330
+ return scanAgentCommand(agentType, args, deps);
4331
+ }
3891
4332
  const policyPath = flagStr(args.flags, "policy");
3892
4333
  let policy;
3893
4334
  try {
@@ -3908,7 +4349,7 @@ function scanCommand(args, deps) {
3908
4349
  }
3909
4350
  function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true) {
3910
4351
  const surfaceDir = flagStr(args.flags, "surface-dir");
3911
- const surfaces = surfaceDir ? readDocumentSurfaces(resolve2(deps.cwd, surfaceDir)) : void 0;
4352
+ const surfaces = surfaceDir ? readDocumentSurfaces(resolve3(deps.cwd, surfaceDir)) : void 0;
3912
4353
  let summary;
3913
4354
  try {
3914
4355
  summary = scanConfigText(text, configPath, {
@@ -3930,7 +4371,7 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
3930
4371
  }
3931
4372
  if (deps.writeCacheFile !== false) {
3932
4373
  try {
3933
- writeCache(summary, join6(deps.cwd, ".calllint", "last-scan.json"));
4374
+ writeCache(summary, join11(deps.cwd, ".calllint", "last-scan.json"));
3934
4375
  } catch {
3935
4376
  }
3936
4377
  }
@@ -3956,7 +4397,7 @@ function writeReceiptFile(summary, text, configPath, policy, args, deps) {
3956
4397
  },
3957
4398
  deps.generatedAt
3958
4399
  );
3959
- const outPath = resolve2(
4400
+ const outPath = resolve3(
3960
4401
  deps.cwd,
3961
4402
  flagStr(args.flags, "receipt-out") ?? "calllint-receipt.json"
3962
4403
  );
@@ -4004,7 +4445,7 @@ function scanChangedCommand(args, deps) {
4004
4445
  };
4005
4446
  }
4006
4447
  const results = paths.map((p) => {
4007
- const text = readFileSync7(p, "utf8");
4448
+ const text = readFileSync12(p, "utf8");
4008
4449
  return scanOneConfig(text, p, policy, args, deps);
4009
4450
  });
4010
4451
  const exitCode = results.reduce((worst, r) => Math.max(worst, r.exitCode), EXIT.OK);
@@ -4017,6 +4458,95 @@ function scanChangedCommand(args, deps) {
4017
4458
  const stderr = results.map((r) => r.stderr).filter(Boolean).join("\n");
4018
4459
  return { stdout, exitCode, ...stderr ? { stderr } : {} };
4019
4460
  }
4461
+ function scanAutoCommand(args, deps) {
4462
+ const policyPath = flagStr(args.flags, "policy");
4463
+ let policy;
4464
+ try {
4465
+ policy = loadPolicyOrDefault(policyPath);
4466
+ } catch (err2) {
4467
+ return {
4468
+ stdout: "",
4469
+ stderr: err2 instanceof Error ? err2.message : String(err2),
4470
+ exitCode: EXIT.ERROR
4471
+ };
4472
+ }
4473
+ const discovered = discoverConfigs({ cwd: deps.cwd });
4474
+ if (discovered.discovered.length === 0) {
4475
+ return {
4476
+ stdout: "",
4477
+ stderr: "No agent configs discovered. Try: calllint inventory\n",
4478
+ exitCode: EXIT.ERROR
4479
+ };
4480
+ }
4481
+ const results = [];
4482
+ for (const config of discovered.discovered) {
4483
+ let text;
4484
+ try {
4485
+ text = readFileSync12(config.configPath, "utf8");
4486
+ } catch (err2) {
4487
+ results.push({
4488
+ stdout: "",
4489
+ stderr: `Could not read ${config.configPath}: ${err2 instanceof Error ? err2.message : String(err2)}`,
4490
+ exitCode: EXIT.ERROR
4491
+ });
4492
+ continue;
4493
+ }
4494
+ const result = scanOneConfig(text, config.configPath, policy, args, deps, false);
4495
+ results.push(result);
4496
+ }
4497
+ const isJson = flagBool(args.flags, "json");
4498
+ const stdout = isJson ? JSON.stringify(results.map((r) => JSON.parse(r.stdout)), null, 2) + "\n" : results.map((r) => r.stdout).join("\n---\n\n");
4499
+ const exitCode = flagBool(args.flags, "ci") ? Math.max(...results.map((r) => r.exitCode)) : EXIT.OK;
4500
+ const stderr = results.map((r) => r.stderr).filter(Boolean).join("\n");
4501
+ return { stdout, exitCode, ...stderr ? { stderr } : {} };
4502
+ }
4503
+ function scanAgentCommand(agentType, args, deps) {
4504
+ const policyPath = flagStr(args.flags, "policy");
4505
+ let policy;
4506
+ try {
4507
+ policy = loadPolicyOrDefault(policyPath);
4508
+ } catch (err2) {
4509
+ return {
4510
+ stdout: "",
4511
+ stderr: err2 instanceof Error ? err2.message : String(err2),
4512
+ exitCode: EXIT.ERROR
4513
+ };
4514
+ }
4515
+ const discovered = discoverAgent(agentType, { cwd: deps.cwd });
4516
+ const existing = discovered.filter((c) => c.exists);
4517
+ if (existing.length === 0) {
4518
+ return {
4519
+ stdout: "",
4520
+ stderr: `No config found for agent '${agentType}'. Try: calllint inventory
4521
+ `,
4522
+ exitCode: EXIT.ERROR
4523
+ };
4524
+ }
4525
+ const results = [];
4526
+ for (const config of existing) {
4527
+ let text;
4528
+ try {
4529
+ text = readFileSync12(config.configPath, "utf8");
4530
+ } catch (err2) {
4531
+ results.push({
4532
+ stdout: "",
4533
+ stderr: `Could not read ${config.configPath}: ${err2 instanceof Error ? err2.message : String(err2)}`,
4534
+ exitCode: EXIT.ERROR
4535
+ });
4536
+ continue;
4537
+ }
4538
+ const result = scanOneConfig(text, config.configPath, policy, args, deps, true);
4539
+ results.push(result);
4540
+ }
4541
+ if (results.length === 1) {
4542
+ return results[0];
4543
+ }
4544
+ const isJson = flagBool(args.flags, "json");
4545
+ const stdout = isJson ? JSON.stringify(results.map((r) => JSON.parse(r.stdout)), null, 2) + "\n" : results.map((r) => r.stdout).join("\n---\n\n");
4546
+ const exitCode = flagBool(args.flags, "ci") ? Math.max(...results.map((r) => r.exitCode)) : EXIT.OK;
4547
+ const stderr = results.map((r) => r.stderr).filter(Boolean).join("\n");
4548
+ return { stdout, exitCode, ...stderr ? { stderr } : {} };
4549
+ }
4020
4550
 
4021
4551
  // src/commands/check.ts
4022
4552
  function checkCommand(args, deps) {
@@ -4106,7 +4636,7 @@ function worstExit2(decisions) {
4106
4636
 
4107
4637
  // src/commands/genRule.ts
4108
4638
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "node:fs";
4109
- import { dirname as dirname3, join as join7, resolve as resolve3 } from "node:path";
4639
+ import { dirname as dirname3, join as join12, resolve as resolve4 } from "node:path";
4110
4640
  function isRuleHost(v) {
4111
4641
  return v !== void 0 && RULE_HOSTS.includes(v);
4112
4642
  }
@@ -4137,7 +4667,7 @@ Run \`calllint gen-rule\` to list hosts.`,
4137
4667
  return { stdout: content, exitCode: EXIT.OK };
4138
4668
  }
4139
4669
  const rel = flagStr(args.flags, "out") ?? RULE_TARGETS[host].path;
4140
- const abs = resolve3(deps.cwd, rel);
4670
+ const abs = resolve4(deps.cwd, rel);
4141
4671
  const write = deps.writeFile ?? defaultWrite;
4142
4672
  try {
4143
4673
  write(abs, content);
@@ -4203,7 +4733,7 @@ function diagnosticsCommand(args, deps) {
4203
4733
  }
4204
4734
 
4205
4735
  // src/commands/explain.ts
4206
- import { join as join8 } from "node:path";
4736
+ import { join as join13 } from "node:path";
4207
4737
  function explainCommand(args, deps) {
4208
4738
  const serverName = args.positionals[0];
4209
4739
  if (!serverName) {
@@ -4213,7 +4743,7 @@ function explainCommand(args, deps) {
4213
4743
  exitCode: EXIT.USAGE
4214
4744
  };
4215
4745
  }
4216
- const summary = readCache(join8(deps.cwd, ".calllint", "last-scan.json"));
4746
+ const summary = readCache(join13(deps.cwd, ".calllint", "last-scan.json"));
4217
4747
  if (!summary) {
4218
4748
  return {
4219
4749
  stdout: "",
@@ -4235,13 +4765,13 @@ function explainCommand(args, deps) {
4235
4765
  }
4236
4766
 
4237
4767
  // src/commands/policy.ts
4238
- import { existsSync as existsSync6, writeFileSync as writeFileSync5 } from "node:fs";
4239
- import { join as join9 } from "node:path";
4768
+ import { existsSync as existsSync9, writeFileSync as writeFileSync5 } from "node:fs";
4769
+ import { join as join14 } from "node:path";
4240
4770
  function policyCommand(args, deps) {
4241
4771
  const sub = args.positionals[0];
4242
4772
  if (sub === "init") {
4243
- const path = join9(deps.cwd, "calllint.policy.json");
4244
- if (existsSync6(path) && !flagBool(args.flags, "force")) {
4773
+ const path = join14(deps.cwd, "calllint.policy.json");
4774
+ if (existsSync9(path) && !flagBool(args.flags, "force")) {
4245
4775
  return {
4246
4776
  stdout: "",
4247
4777
  stderr: `${path} already exists. Use --force to overwrite.`,
@@ -4271,7 +4801,7 @@ function policyCommand(args, deps) {
4271
4801
  }
4272
4802
 
4273
4803
  // src/commands/verify.ts
4274
- import { join as join10 } from "node:path";
4804
+ import { join as join15 } from "node:path";
4275
4805
  function loadPolicy(args) {
4276
4806
  try {
4277
4807
  return loadPolicyOrDefault(flagStr(args.flags, "policy"));
@@ -4280,7 +4810,7 @@ function loadPolicy(args) {
4280
4810
  }
4281
4811
  }
4282
4812
  function baselinePathFor(args, cwd) {
4283
- return flagStr(args.flags, "baseline") ?? join10(cwd, ".calllint", "baseline.json");
4813
+ return flagStr(args.flags, "baseline") ?? join15(cwd, ".calllint", "baseline.json");
4284
4814
  }
4285
4815
  function baselineCommand(args, deps) {
4286
4816
  const policy = loadPolicy(args);
@@ -4406,8 +4936,11 @@ function approveCommand(args, deps) {
4406
4936
  }
4407
4937
 
4408
4938
  // src/commands/receipt.ts
4409
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
4410
- import { resolve as resolve4 } from "node:path";
4939
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "node:fs";
4940
+ import { resolve as resolve5 } from "node:path";
4941
+
4942
+ // ../../packages/signature/src/ed25519.ts
4943
+ import { createHash as createHash2 } from "node:crypto";
4411
4944
 
4412
4945
  // ../../node_modules/.pnpm/@noble+ed25519@2.3.0/node_modules/@noble/ed25519/index.js
4413
4946
  var ed25519_CURVE = {
@@ -4759,8 +5292,8 @@ var hash2extK = (hashed) => {
4759
5292
  };
4760
5293
  var getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
4761
5294
  var getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
4762
- var getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then((p) => p.pointBytes);
4763
- var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
5295
+ var getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
5296
+ var hashFinishS = (res) => res.finish(sha512s(res.hashable));
4764
5297
  var _sign = (e, rBytes, msg) => {
4765
5298
  const { pointBytes: P2, scalar: s } = e;
4766
5299
  const r = modL_LE(rBytes);
@@ -4772,11 +5305,11 @@ var _sign = (e, rBytes, msg) => {
4772
5305
  };
4773
5306
  return { hashable, finish };
4774
5307
  };
4775
- var signAsync = async (msg, privKey) => {
5308
+ var sign = (msg, privKey) => {
4776
5309
  const m = toU8(msg);
4777
- const e = await getExtendedPublicKeyAsync(privKey);
4778
- const rBytes = await sha512a(e.prefix, m);
4779
- return hashFinishA(_sign(e, rBytes, m));
5310
+ const e = getExtendedPublicKey(privKey);
5311
+ const rBytes = sha512s(e.prefix, m);
5312
+ return hashFinishS(_sign(e, rBytes, m));
4780
5313
  };
4781
5314
  var veriOpts = { zip215: true };
4782
5315
  var _verify = (sig, msg, pub, opts = veriOpts) => {
@@ -4808,7 +5341,7 @@ var _verify = (sig, msg, pub, opts = veriOpts) => {
4808
5341
  };
4809
5342
  return { hashable, finish };
4810
5343
  };
4811
- var verifyAsync = async (s, m, p, opts = veriOpts) => hashFinishA(_verify(s, m, p, opts));
5344
+ var verify = (s, m, p, opts = veriOpts) => hashFinishS(_verify(s, m, p, opts));
4812
5345
  var etc = {
4813
5346
  sha512Async: async (...messages) => {
4814
5347
  const s = subtle();
@@ -4899,21 +5432,26 @@ function base64urlDecode(str) {
4899
5432
  }
4900
5433
 
4901
5434
  // ../../packages/signature/src/ed25519.ts
4902
- async function generateKeypair(keyId) {
5435
+ etc.sha512Sync = (...messages) => {
5436
+ const hash = createHash2("sha512");
5437
+ hash.update(etc.concatBytes(...messages));
5438
+ return new Uint8Array(hash.digest());
5439
+ };
5440
+ function generateKeypair(keyId) {
4903
5441
  const privateKey = utils.randomPrivateKey();
4904
- const publicKey = await getPublicKeyAsync(privateKey);
5442
+ const publicKey = getPublicKey(privateKey);
4905
5443
  return {
4906
5444
  privateKey,
4907
5445
  publicKey,
4908
5446
  keyId
4909
5447
  };
4910
5448
  }
4911
- async function signReceipt(unsignedReceipt, keypair) {
5449
+ function signReceipt(unsignedReceipt, keypair) {
4912
5450
  const canonical = stableStringify(unsignedReceipt);
4913
5451
  const hash = sha256(canonical);
4914
5452
  const hashHex = hash.slice(7);
4915
5453
  const hashBytes = Buffer.from(hashHex, "hex");
4916
- const signatureBytes = await signAsync(hashBytes, keypair.privateKey);
5454
+ const signatureBytes = sign(hashBytes, keypair.privateKey);
4917
5455
  const signatureBase64url = base64urlEncode(signatureBytes);
4918
5456
  return {
4919
5457
  algorithm: "ed25519",
@@ -4922,7 +5460,7 @@ async function signReceipt(unsignedReceipt, keypair) {
4922
5460
  signed_at: (/* @__PURE__ */ new Date()).toISOString()
4923
5461
  };
4924
5462
  }
4925
- async function verifyReceipt2(signedReceipt, publicKey) {
5463
+ function verifyReceipt2(signedReceipt, publicKey) {
4926
5464
  try {
4927
5465
  const sig = signedReceipt.signature;
4928
5466
  if (!sig) {
@@ -4944,7 +5482,7 @@ async function verifyReceipt2(signedReceipt, publicKey) {
4944
5482
  const hash = sha256(canonical);
4945
5483
  const hashHex = hash.slice(7);
4946
5484
  const hashBytes = Buffer.from(hashHex, "hex");
4947
- const valid = await verifyAsync(signatureBytes, hashBytes, publicKeyBytes);
5485
+ const valid = verify(signatureBytes, hashBytes, publicKeyBytes);
4948
5486
  return { valid, key_id: sig.key_id };
4949
5487
  } catch (error) {
4950
5488
  return {
@@ -5008,7 +5546,7 @@ function receiptVerify(args, deps) {
5008
5546
  }
5009
5547
  let raw;
5010
5548
  try {
5011
- raw = readFileSync8(resolve4(deps.cwd, file), "utf8");
5549
+ raw = readFileSync13(resolve5(deps.cwd, file), "utf8");
5012
5550
  } catch (e) {
5013
5551
  return {
5014
5552
  stdout: "",
@@ -5043,53 +5581,7 @@ function receiptVerify(args, deps) {
5043
5581
  }
5044
5582
  const receipt = parsed;
5045
5583
  if (receipt.signature) {
5046
- let cryptoResult = null;
5047
- let errorMsg = null;
5048
- try {
5049
- const promise = verifySignature(receipt, args);
5050
- let done = false;
5051
- promise.then(
5052
- (r) => {
5053
- cryptoResult = r;
5054
- done = true;
5055
- },
5056
- (e) => {
5057
- errorMsg = e instanceof Error ? e.message : String(e);
5058
- done = true;
5059
- }
5060
- );
5061
- const start = Date.now();
5062
- while (!done && Date.now() - start < 5e3) {
5063
- }
5064
- if (!done) {
5065
- return {
5066
- stdout: "",
5067
- stderr: "Signature verification timed out",
5068
- exitCode: EXIT.ERROR
5069
- };
5070
- }
5071
- if (errorMsg) {
5072
- return {
5073
- stdout: "",
5074
- stderr: `Signature verification failed: ${errorMsg}`,
5075
- exitCode: EXIT.ERROR
5076
- };
5077
- }
5078
- if (!cryptoResult) {
5079
- return {
5080
- stdout: "",
5081
- stderr: "Signature verification failed (no result)",
5082
- exitCode: EXIT.ERROR
5083
- };
5084
- }
5085
- return formatVerifyResult(receipt, cryptoResult, args);
5086
- } catch (e) {
5087
- return {
5088
- stdout: "",
5089
- stderr: `Signature verification error: ${e instanceof Error ? e.message : String(e)}`,
5090
- exitCode: EXIT.ERROR
5091
- };
5092
- }
5584
+ return verifySignature(receipt, args, deps);
5093
5585
  }
5094
5586
  if (flagBool(args.flags, "json")) {
5095
5587
  return {
@@ -5107,6 +5599,43 @@ function receiptVerify(args, deps) {
5107
5599
  exitCode: EXIT.OK
5108
5600
  };
5109
5601
  }
5602
+ if (flagBool(args.flags, "verbose")) {
5603
+ const stdout2 = [
5604
+ "\u2713 CallLint receipt: valid",
5605
+ "",
5606
+ "Receipt Information:",
5607
+ ` ID: ${receipt.receipt_id}`,
5608
+ ` Schema: ${receipt.schema_version}`,
5609
+ ` Created: ${receipt.created_at}`,
5610
+ "",
5611
+ "Tool:",
5612
+ ` Name: ${receipt.tool.name}`,
5613
+ ` Version: ${receipt.tool.version}`,
5614
+ "",
5615
+ "Subject:",
5616
+ ` Type: ${receipt.subject.type}`,
5617
+ ` Target: ${receipt.subject.target}`,
5618
+ "",
5619
+ `Verdict: ${receipt.verdict}`,
5620
+ "",
5621
+ "Hashes:",
5622
+ ` Input: ${receipt.hashes.input_hash}`,
5623
+ ` Policy: ${receipt.hashes.policy_hash}`,
5624
+ ` Report: ${receipt.hashes.report_hash}`,
5625
+ ` Ruleset: ${receipt.hashes.ruleset_hash}`,
5626
+ "",
5627
+ "Risk Summary:",
5628
+ ` BLOCK: ${receipt.risk_counts.block ?? 0}`,
5629
+ ` REVIEW: ${receipt.risk_counts.review ?? 0}`,
5630
+ ` UNKNOWN: ${receipt.risk_counts.unknown ?? 0}`,
5631
+ ` SAFE: ${receipt.risk_counts.safe ?? 0}`,
5632
+ "",
5633
+ `Network: ${receipt.trust_boundaries.network_used ? "Used" : "Not used"}`,
5634
+ "",
5635
+ "Signature: unsigned local receipt"
5636
+ ].join("\n");
5637
+ return { stdout: stdout2, exitCode: EXIT.OK };
5638
+ }
5110
5639
  const stdout = [
5111
5640
  "\u2713 CallLint receipt: valid",
5112
5641
  ` Receipt ID: ${receipt.receipt_id}`,
@@ -5116,34 +5645,39 @@ function receiptVerify(args, deps) {
5116
5645
  ].join("\n");
5117
5646
  return { stdout, exitCode: EXIT.OK };
5118
5647
  }
5119
- async function verifySignature(receipt, args) {
5648
+ function verifySignature(receipt, args, deps) {
5120
5649
  const sig = receipt.signature;
5121
5650
  const publicKeyUrl = sig.public_key_url || "https://calllint.com/.well-known/receipt-keys.json";
5122
- let publicKey;
5123
5651
  const publicKeyFlag = args.flags["public-key"];
5124
- if (publicKeyFlag && typeof publicKeyFlag === "string") {
5125
- try {
5126
- const keyJson = JSON.parse(readFileSync8(publicKeyFlag, "utf8"));
5127
- publicKey = keyJson.public_key;
5128
- } catch (e) {
5129
- throw new Error(
5130
- `Could not read public key from ${publicKeyFlag}: ${e instanceof Error ? e.message : String(e)}`
5131
- );
5652
+ if (!publicKeyFlag || typeof publicKeyFlag !== "string") {
5653
+ return {
5654
+ stdout: "",
5655
+ stderr: [
5656
+ "Receipt has a signature but no public key was provided.",
5657
+ ` Signature key_id: ${sig.key_id}`,
5658
+ ` Public key URL: ${publicKeyUrl}`,
5659
+ "",
5660
+ "Verify offline by passing the public key:",
5661
+ " calllint receipt verify <receipt.json> --public-key <keyfile>"
5662
+ ].join("\n"),
5663
+ exitCode: EXIT.ERROR
5664
+ };
5665
+ }
5666
+ let publicKey;
5667
+ try {
5668
+ const keyJson = JSON.parse(readFileSync13(resolve5(deps.cwd, publicKeyFlag), "utf8"));
5669
+ publicKey = keyJson.public_key;
5670
+ if (typeof publicKey !== "string") {
5671
+ throw new Error("key file has no string 'public_key' field");
5132
5672
  }
5133
- } else {
5134
- throw new Error(
5135
- `Receipt has signature but public key not available.
5136
- Signature key_id: ${sig.key_id}
5137
- Public key URL: ${publicKeyUrl}
5138
-
5139
- To verify locally, provide public key with --public-key <keyfile>
5140
- Example: calllint receipt verify receipt.json --public-key dev-key.json`
5141
- );
5673
+ } catch (e) {
5674
+ return {
5675
+ stdout: "",
5676
+ stderr: `Could not read public key from ${publicKeyFlag}: ${e instanceof Error ? e.message : String(e)}`,
5677
+ exitCode: EXIT.ERROR
5678
+ };
5142
5679
  }
5143
- return await verifyReceipt2(receipt, publicKey);
5144
- }
5145
- function formatVerifyResult(receipt, cryptoResult, args) {
5146
- const sig = receipt.signature;
5680
+ const cryptoResult = verifyReceipt2(receipt, publicKey);
5147
5681
  if (flagBool(args.flags, "json")) {
5148
5682
  return {
5149
5683
  stdout: JSON.stringify(
@@ -5176,6 +5710,47 @@ function formatVerifyResult(receipt, cryptoResult, args) {
5176
5710
  ];
5177
5711
  return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
5178
5712
  }
5713
+ if (flagBool(args.flags, "verbose")) {
5714
+ const stdout2 = [
5715
+ "\u2713 CallLint receipt: valid",
5716
+ "",
5717
+ "Receipt Information:",
5718
+ ` ID: ${receipt.receipt_id}`,
5719
+ ` Schema: ${receipt.schema_version}`,
5720
+ ` Created: ${receipt.created_at}`,
5721
+ "",
5722
+ "Tool:",
5723
+ ` Name: ${receipt.tool.name}`,
5724
+ ` Version: ${receipt.tool.version}`,
5725
+ "",
5726
+ "Subject:",
5727
+ ` Type: ${receipt.subject.type}`,
5728
+ ` Target: ${receipt.subject.target}`,
5729
+ "",
5730
+ `Verdict: ${receipt.verdict}`,
5731
+ "",
5732
+ "Hashes:",
5733
+ ` Input: ${receipt.hashes.input_hash}`,
5734
+ ` Policy: ${receipt.hashes.policy_hash}`,
5735
+ ` Report: ${receipt.hashes.report_hash}`,
5736
+ ` Ruleset: ${receipt.hashes.ruleset_hash}`,
5737
+ "",
5738
+ "Risk Summary:",
5739
+ ` BLOCK: ${receipt.risk_counts.block ?? 0}`,
5740
+ ` REVIEW: ${receipt.risk_counts.review ?? 0}`,
5741
+ ` UNKNOWN: ${receipt.risk_counts.unknown ?? 0}`,
5742
+ ` SAFE: ${receipt.risk_counts.safe ?? 0}`,
5743
+ "",
5744
+ `Network: ${receipt.trust_boundaries.network_used ? "Used" : "Not used"}`,
5745
+ "",
5746
+ "Signature:",
5747
+ ` Status: valid`,
5748
+ ` Key ID: ${sig.key_id}`,
5749
+ ` Algorithm: ${sig.algorithm}`,
5750
+ ` Signed at: ${sig.signed_at}`
5751
+ ].join("\n");
5752
+ return { stdout: stdout2, exitCode: EXIT.OK };
5753
+ }
5179
5754
  const stdout = [
5180
5755
  "\u2713 CallLint receipt: valid",
5181
5756
  ` Receipt ID: ${receipt.receipt_id}`,
@@ -5202,23 +5777,13 @@ function receiptSign(args, deps) {
5202
5777
  exitCode: EXIT.USAGE
5203
5778
  };
5204
5779
  }
5205
- let receiptRaw;
5206
- try {
5207
- receiptRaw = readFileSync8(resolve4(deps.cwd, receiptFile), "utf8");
5208
- } catch (e) {
5209
- return {
5210
- stdout: "",
5211
- stderr: `Could not read receipt ${receiptFile}: ${e instanceof Error ? e.message : String(e)}`,
5212
- exitCode: EXIT.ERROR
5213
- };
5214
- }
5215
5780
  let unsignedReceipt;
5216
5781
  try {
5217
- unsignedReceipt = JSON.parse(receiptRaw);
5782
+ unsignedReceipt = JSON.parse(readFileSync13(resolve5(deps.cwd, receiptFile), "utf8"));
5218
5783
  } catch (e) {
5219
5784
  return {
5220
5785
  stdout: "",
5221
- stderr: `Receipt is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,
5786
+ stderr: `Could not read receipt ${receiptFile}: ${e instanceof Error ? e.message : String(e)}`,
5222
5787
  exitCode: EXIT.ERROR
5223
5788
  };
5224
5789
  }
@@ -5229,69 +5794,21 @@ function receiptSign(args, deps) {
5229
5794
  exitCode: EXIT.ERROR
5230
5795
  };
5231
5796
  }
5232
- let keyJson;
5233
- try {
5234
- const keyRaw = readFileSync8(resolve4(deps.cwd, keyFile), "utf8");
5235
- keyJson = JSON.parse(keyRaw);
5236
- } catch (e) {
5237
- return {
5238
- stdout: "",
5239
- stderr: `Could not read key file ${keyFile}: ${e instanceof Error ? e.message : String(e)}`,
5240
- exitCode: EXIT.ERROR
5241
- };
5242
- }
5243
5797
  let keypair;
5244
5798
  try {
5799
+ const keyJson = JSON.parse(readFileSync13(resolve5(deps.cwd, keyFile), "utf8"));
5245
5800
  keypair = importKeypair(keyJson);
5246
5801
  } catch (e) {
5247
5802
  return {
5248
5803
  stdout: "",
5249
- stderr: `Invalid keypair format: ${e instanceof Error ? e.message : String(e)}`,
5250
- exitCode: EXIT.ERROR
5251
- };
5252
- }
5253
- let signature = null;
5254
- let errorMsg = null;
5255
- const promise = signReceipt(unsignedReceipt, keypair);
5256
- let done = false;
5257
- promise.then(
5258
- (sig) => {
5259
- signature = sig;
5260
- done = true;
5261
- },
5262
- (e) => {
5263
- errorMsg = e instanceof Error ? e.message : String(e);
5264
- done = true;
5265
- }
5266
- );
5267
- const start = Date.now();
5268
- while (!done && Date.now() - start < 5e3) {
5269
- }
5270
- if (!done) {
5271
- return {
5272
- stdout: "",
5273
- stderr: "Signing timed out",
5274
- exitCode: EXIT.ERROR
5275
- };
5276
- }
5277
- if (errorMsg) {
5278
- return {
5279
- stdout: "",
5280
- stderr: `Signing failed: ${errorMsg}`,
5281
- exitCode: EXIT.ERROR
5282
- };
5283
- }
5284
- if (!signature) {
5285
- return {
5286
- stdout: "",
5287
- stderr: "Signing failed (no result)",
5804
+ stderr: `Could not read key file ${keyFile}: ${e instanceof Error ? e.message : String(e)}`,
5288
5805
  exitCode: EXIT.ERROR
5289
5806
  };
5290
5807
  }
5291
5808
  try {
5809
+ const signature = signReceipt(unsignedReceipt, keypair);
5292
5810
  const signedReceipt = { ...unsignedReceipt, signature };
5293
- const outPath = resolve4(deps.cwd, outFile);
5294
- writeFileSync6(outPath, JSON.stringify(signedReceipt, null, 2) + "\n", "utf8");
5811
+ writeFileSync6(resolve5(deps.cwd, outFile), JSON.stringify(signedReceipt, null, 2) + "\n", "utf8");
5295
5812
  const stdout = [
5296
5813
  "\u2713 Receipt signed successfully",
5297
5814
  ` Key ID: ${signature.key_id}`,
@@ -5303,7 +5820,7 @@ function receiptSign(args, deps) {
5303
5820
  } catch (e) {
5304
5821
  return {
5305
5822
  stdout: "",
5306
- stderr: `Failed to write signed receipt: ${e instanceof Error ? e.message : String(e)}`,
5823
+ stderr: `Signing failed: ${e instanceof Error ? e.message : String(e)}`,
5307
5824
  exitCode: EXIT.ERROR
5308
5825
  };
5309
5826
  }
@@ -5323,48 +5840,10 @@ function receiptKeygen(args, deps) {
5323
5840
  };
5324
5841
  }
5325
5842
  const keyId = args.flags["key-id"] || "calllint-dev-2026-h2";
5326
- let keypair = null;
5327
- let errorMsg = null;
5328
- const promise = generateKeypair(keyId);
5329
- let done = false;
5330
- promise.then(
5331
- (kp) => {
5332
- keypair = kp;
5333
- done = true;
5334
- },
5335
- (e) => {
5336
- errorMsg = e instanceof Error ? e.message : String(e);
5337
- done = true;
5338
- }
5339
- );
5340
- const start = Date.now();
5341
- while (!done && Date.now() - start < 5e3) {
5342
- }
5343
- if (!done) {
5344
- return {
5345
- stdout: "",
5346
- stderr: "Keygen timed out",
5347
- exitCode: EXIT.ERROR
5348
- };
5349
- }
5350
- if (errorMsg) {
5351
- return {
5352
- stdout: "",
5353
- stderr: `Keygen failed: ${errorMsg}`,
5354
- exitCode: EXIT.ERROR
5355
- };
5356
- }
5357
- if (!keypair) {
5358
- return {
5359
- stdout: "",
5360
- stderr: "Keygen failed (no result)",
5361
- exitCode: EXIT.ERROR
5362
- };
5363
- }
5364
5843
  try {
5844
+ const keypair = generateKeypair(keyId);
5365
5845
  const exported = exportKeypair(keypair);
5366
- const outPath = resolve4(deps.cwd, outFile);
5367
- writeFileSync6(outPath, JSON.stringify(exported, null, 2) + "\n", "utf8");
5846
+ writeFileSync6(resolve5(deps.cwd, outFile), JSON.stringify(exported, null, 2) + "\n", "utf8");
5368
5847
  const stdout = [
5369
5848
  "\u2713 Keypair generated successfully",
5370
5849
  ` Key ID: ${keyId}`,
@@ -5377,15 +5856,15 @@ function receiptKeygen(args, deps) {
5377
5856
  } catch (e) {
5378
5857
  return {
5379
5858
  stdout: "",
5380
- stderr: `Failed to write keypair: ${e instanceof Error ? e.message : String(e)}`,
5859
+ stderr: `Keygen failed: ${e instanceof Error ? e.message : String(e)}`,
5381
5860
  exitCode: EXIT.ERROR
5382
5861
  };
5383
5862
  }
5384
5863
  }
5385
5864
 
5386
5865
  // src/commands/action.ts
5387
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
5388
- import { resolve as resolve5 } from "node:path";
5866
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
5867
+ import { resolve as resolve6 } from "node:path";
5389
5868
 
5390
5869
  // ../../packages/action-analyzer/src/types.ts
5391
5870
  var KIND_RISK_PROFILES = {
@@ -5859,8 +6338,8 @@ function actionInspect(args, deps) {
5859
6338
  };
5860
6339
  }
5861
6340
  try {
5862
- const absolutePath = resolve5(deps.cwd, actionFile);
5863
- const content = readFileSync9(absolutePath, "utf-8");
6341
+ const absolutePath = resolve6(deps.cwd, actionFile);
6342
+ const content = readFileSync14(absolutePath, "utf-8");
5864
6343
  const descriptor = JSON.parse(content);
5865
6344
  if (descriptor.schema_version !== "calllint.action.v0") {
5866
6345
  return {
@@ -5871,7 +6350,7 @@ Expected: calllint.action.v0`,
5871
6350
  };
5872
6351
  }
5873
6352
  const policyPath = args.flags["policy"];
5874
- const policy = loadPolicyOrDefault(policyPath ? resolve5(deps.cwd, policyPath) : void 0);
6353
+ const policy = loadPolicyOrDefault(policyPath ? resolve6(deps.cwd, policyPath) : void 0);
5875
6354
  const findings = analyzeAction(descriptor);
5876
6355
  const verdict = computeActionVerdict(findings, policy);
5877
6356
  const actionReport = {
@@ -5998,7 +6477,7 @@ function writeActionReceipt(descriptor, rawContent, actionReport, policy, args,
5998
6477
  },
5999
6478
  deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
6000
6479
  );
6001
- const outPath = resolve5(
6480
+ const outPath = resolve6(
6002
6481
  deps.cwd,
6003
6482
  args.flags["receipt-out"] ?? "calllint-action-receipt.json"
6004
6483
  );
@@ -6011,8 +6490,8 @@ function writeActionReceipt(descriptor, rawContent, actionReport, policy, args,
6011
6490
  }
6012
6491
 
6013
6492
  // src/commands/inbox.ts
6014
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
6015
- import { resolve as resolve6 } from "node:path";
6493
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
6494
+ import { resolve as resolve7 } from "node:path";
6016
6495
  function inboxCommand(args, deps) {
6017
6496
  const subcommand = args.positionals[0];
6018
6497
  if (!subcommand || subcommand === "help") {
@@ -6042,8 +6521,8 @@ function inboxInspect(args, deps) {
6042
6521
  };
6043
6522
  }
6044
6523
  try {
6045
- const absolutePath = resolve6(deps.cwd, eventFile);
6046
- const content = readFileSync10(absolutePath, "utf-8");
6524
+ const absolutePath = resolve7(deps.cwd, eventFile);
6525
+ const content = readFileSync15(absolutePath, "utf-8");
6047
6526
  const event = JSON.parse(content);
6048
6527
  if (event.schema_version !== "calllint.agent-inbox-event.v0") {
6049
6528
  return {
@@ -6061,7 +6540,7 @@ Expected: calllint.agent-inbox-event.v0`,
6061
6540
  };
6062
6541
  }
6063
6542
  const policyPath = args.flags["policy"];
6064
- const policy = loadPolicyOrDefault(policyPath ? resolve6(deps.cwd, policyPath) : void 0);
6543
+ const policy = loadPolicyOrDefault(policyPath ? resolve7(deps.cwd, policyPath) : void 0);
6065
6544
  const actionCandidate = event.action_candidate;
6066
6545
  let verdict;
6067
6546
  let findings;
@@ -6209,7 +6688,7 @@ function writeInboxReceipt(event, rawContent, verdict, findings, policy, args, d
6209
6688
  deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
6210
6689
  );
6211
6690
  const receiptOutPath = args.flags["receipt-out"] || "calllint-inbox-receipt.json";
6212
- const absoluteReceiptPath = resolve6(deps.cwd, receiptOutPath);
6691
+ const absoluteReceiptPath = resolve7(deps.cwd, receiptOutPath);
6213
6692
  writeFileSync8(absoluteReceiptPath, JSON.stringify(receipt, null, 2) + "\n", "utf-8");
6214
6693
  return null;
6215
6694
  } catch (error) {
@@ -6264,6 +6743,82 @@ SEE ALSO
6264
6743
  `;
6265
6744
  }
6266
6745
 
6746
+ // src/commands/inventory.ts
6747
+ function inventoryCommand(args, deps) {
6748
+ try {
6749
+ const result = discoverConfigs({ cwd: deps.cwd });
6750
+ if (args.flags.json) {
6751
+ return {
6752
+ exitCode: 0,
6753
+ stdout: JSON.stringify(result, null, 2) + "\n",
6754
+ stderr: ""
6755
+ };
6756
+ }
6757
+ const lines = [];
6758
+ if (result.discovered.length === 0) {
6759
+ lines.push("No agent configs discovered.");
6760
+ lines.push("");
6761
+ lines.push("Searched agents: Cursor, Claude Code, Claude Desktop");
6762
+ lines.push("To scan a specific config: calllint scan --config <path>");
6763
+ } else {
6764
+ lines.push(`Discovered ${result.discovered.length} agent config(s):`);
6765
+ lines.push("");
6766
+ const byAgent = /* @__PURE__ */ new Map();
6767
+ for (const config of result.discovered) {
6768
+ const existing = byAgent.get(config.agentType) || [];
6769
+ existing.push(config);
6770
+ byAgent.set(config.agentType, existing);
6771
+ }
6772
+ const sortedAgents = Array.from(byAgent.keys()).sort((a, b) => {
6773
+ const priorityA = result.discovered.find((c) => c.agentType === a)?.priority || "P3";
6774
+ const priorityB = result.discovered.find((c) => c.agentType === b)?.priority || "P3";
6775
+ return priorityA.localeCompare(priorityB);
6776
+ });
6777
+ for (const agentType of sortedAgents) {
6778
+ const configs = byAgent.get(agentType);
6779
+ const priority = configs[0].priority;
6780
+ const agentLabel = formatAgentType(agentType);
6781
+ lines.push(`${agentLabel} (${priority}):`);
6782
+ for (const config of configs) {
6783
+ lines.push(` ${config.configPath}`);
6784
+ }
6785
+ lines.push("");
6786
+ }
6787
+ lines.push(`To scan all: calllint scan --auto`);
6788
+ lines.push(`To scan one: calllint scan --agent ${result.discovered[0].agentType}`);
6789
+ }
6790
+ return {
6791
+ exitCode: 0,
6792
+ stdout: lines.join("\n") + "\n",
6793
+ stderr: ""
6794
+ };
6795
+ } catch (error) {
6796
+ const message = error instanceof Error ? error.message : String(error);
6797
+ return {
6798
+ exitCode: 1,
6799
+ stdout: "",
6800
+ stderr: `Error discovering configs: ${message}
6801
+ `
6802
+ };
6803
+ }
6804
+ }
6805
+ function formatAgentType(agentType) {
6806
+ switch (agentType) {
6807
+ case "cursor":
6808
+ return "Cursor";
6809
+ case "claude-code":
6810
+ return "Claude Code";
6811
+ case "claude-desktop":
6812
+ return "Claude Desktop";
6813
+ case "vscode":
6814
+ return "VS Code";
6815
+ case "windsurf":
6816
+ return "Windsurf";
6817
+ default:
6818
+ return agentType;
6819
+ }
6820
+ }
6821
+
6267
6822
  // src/run.ts
6268
6823
  function run(argv, deps) {
6269
6824
  const args = parseArgs(argv);
@@ -6336,6 +6891,8 @@ function run(argv, deps) {
6336
6891
  return actionCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
6337
6892
  case "inbox":
6338
6893
  return inboxCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
6894
+ case "inventory":
6895
+ return inventoryCommand(args, { cwd: deps.cwd });
6339
6896
  case "gen-rule":
6340
6897
  return genRuleCommand(args, { cwd: deps.cwd });
6341
6898
  case "policy":
@@ -6609,13 +7166,13 @@ async function breathe(argv, deps = {}) {
6609
7166
  }
6610
7167
 
6611
7168
  // src/version.ts
6612
- import { readFileSync as readFileSync11 } from "node:fs";
7169
+ import { readFileSync as readFileSync16 } from "node:fs";
6613
7170
  import { fileURLToPath } from "node:url";
6614
- import { dirname as dirname4, join as join11 } from "node:path";
7171
+ import { dirname as dirname4, join as join16 } from "node:path";
6615
7172
  function resolveToolVersion() {
6616
7173
  try {
6617
- const pkgPath = join11(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
6618
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
7174
+ const pkgPath = join16(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
7175
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
6619
7176
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
6620
7177
  } catch {
6621
7178
  return "unknown";
@@ -6625,7 +7182,7 @@ function resolveToolVersion() {
6625
7182
  // src/index.ts
6626
7183
  function readStdin() {
6627
7184
  try {
6628
- return readFileSync12(0, "utf8");
7185
+ return readFileSync17(0, "utf8");
6629
7186
  } catch {
6630
7187
  return "";
6631
7188
  }