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.
- package/dist/index.js +786 -229
- 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
|
|
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>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
4239
|
-
import { join as
|
|
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 =
|
|
4244
|
-
if (
|
|
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
|
|
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") ??
|
|
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
|
|
4410
|
-
import { resolve as
|
|
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
|
|
4763
|
-
var
|
|
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
|
|
5308
|
+
var sign = (msg, privKey) => {
|
|
4776
5309
|
const m = toU8(msg);
|
|
4777
|
-
const e =
|
|
4778
|
-
const rBytes =
|
|
4779
|
-
return
|
|
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
|
|
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
|
-
|
|
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 =
|
|
5442
|
+
const publicKey = getPublicKey(privateKey);
|
|
4905
5443
|
return {
|
|
4906
5444
|
privateKey,
|
|
4907
5445
|
publicKey,
|
|
4908
5446
|
keyId
|
|
4909
5447
|
};
|
|
4910
5448
|
}
|
|
4911
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
`
|
|
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
|
-
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
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
|
-
|
|
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(
|
|
5782
|
+
unsignedReceipt = JSON.parse(readFileSync13(resolve5(deps.cwd, receiptFile), "utf8"));
|
|
5218
5783
|
} catch (e) {
|
|
5219
5784
|
return {
|
|
5220
5785
|
stdout: "",
|
|
5221
|
-
stderr: `
|
|
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: `
|
|
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
|
-
|
|
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: `
|
|
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
|
-
|
|
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: `
|
|
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
|
|
5388
|
-
import { resolve as
|
|
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 =
|
|
5863
|
-
const content =
|
|
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 ?
|
|
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 =
|
|
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
|
|
6015
|
-
import { resolve as
|
|
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 =
|
|
6046
|
-
const content =
|
|
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 ?
|
|
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 =
|
|
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
|
|
7169
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
6613
7170
|
import { fileURLToPath } from "node:url";
|
|
6614
|
-
import { dirname as dirname4, join as
|
|
7171
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
6615
7172
|
function resolveToolVersion() {
|
|
6616
7173
|
try {
|
|
6617
|
-
const pkgPath =
|
|
6618
|
-
const pkg = JSON.parse(
|
|
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
|
|
7185
|
+
return readFileSync17(0, "utf8");
|
|
6629
7186
|
} catch {
|
|
6630
7187
|
return "";
|
|
6631
7188
|
}
|