ai-trust 0.2.0 → 0.2.3

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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Contribution Opt-In Prompt
3
+ *
4
+ * Handles the user's consent to share anonymized scan findings
5
+ * with the OpenA2A Registry.
6
+ *
7
+ * Config/counting is delegated to @opena2a/shared (the canonical
8
+ * source for ~/.opena2a/config.json). If @opena2a/shared is not
9
+ * available at runtime, falls back to a local implementation.
10
+ */
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ import { join } from "path";
13
+ /** Resolved backend -- lazy-initialized on first call. */
14
+ let _backend;
15
+ function resolveBackend() {
16
+ if (_backend)
17
+ return _backend;
18
+ try {
19
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
20
+ const shared = require("@opena2a/shared");
21
+ if (typeof shared.isContributeEnabled === "function" &&
22
+ typeof shared.setContributeEnabled === "function" &&
23
+ typeof shared.incrementScanCount === "function" &&
24
+ typeof shared.shouldPromptContribute === "function" &&
25
+ typeof shared.dismissContributePrompt === "function") {
26
+ _backend = {
27
+ // Shared returns boolean (false when not configured).
28
+ // ai-trust callers expect undefined for "not yet configured",
29
+ // but shouldPromptContribute() handles that distinction via
30
+ // scan-count thresholds, so returning false here is acceptable.
31
+ isContributeEnabled: shared.isContributeEnabled,
32
+ setContributeEnabled: shared.setContributeEnabled,
33
+ incrementScanCount: shared.incrementScanCount,
34
+ shouldPromptContribute: shared.shouldPromptContribute,
35
+ dismissContributePrompt: shared.dismissContributePrompt,
36
+ };
37
+ return _backend;
38
+ }
39
+ }
40
+ catch {
41
+ // @opena2a/shared not installed -- fall through to local backend
42
+ }
43
+ _backend = createLocalBackend();
44
+ return _backend;
45
+ }
46
+ function getConfigPath() {
47
+ const home = process.env.OPENA2A_HOME || join(require("os").homedir(), ".opena2a");
48
+ return join(home, "config.json");
49
+ }
50
+ function readConfig() {
51
+ const configPath = getConfigPath();
52
+ try {
53
+ if (existsSync(configPath)) {
54
+ return JSON.parse(readFileSync(configPath, "utf-8"));
55
+ }
56
+ }
57
+ catch {
58
+ // Corrupt config -- treat as empty
59
+ }
60
+ return {};
61
+ }
62
+ function writeConfig(config) {
63
+ const configPath = getConfigPath();
64
+ const dir = require("path").dirname(configPath);
65
+ mkdirSync(dir, { recursive: true });
66
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", {
67
+ mode: 0o600,
68
+ });
69
+ }
70
+ function createLocalBackend() {
71
+ return {
72
+ isContributeEnabled() {
73
+ const config = readConfig();
74
+ if (config.contribute?.enabled === true)
75
+ return true;
76
+ if (config.contribute?.enabled === false)
77
+ return false;
78
+ return undefined;
79
+ },
80
+ setContributeEnabled(enabled) {
81
+ const config = readConfig();
82
+ if (!config.contribute)
83
+ config.contribute = {};
84
+ config.contribute.enabled = enabled;
85
+ const scanCount = config.contribute.scanCount ?? 0;
86
+ if (scanCount >= 9)
87
+ config.contribute.promptedAtTen = true;
88
+ writeConfig(config);
89
+ },
90
+ incrementScanCount() {
91
+ const config = readConfig();
92
+ if (!config.contribute)
93
+ config.contribute = {};
94
+ config.contribute.scanCount = (config.contribute.scanCount ?? 0) + 1;
95
+ writeConfig(config);
96
+ return config.contribute.scanCount;
97
+ },
98
+ shouldPromptContribute() {
99
+ const config = readConfig();
100
+ if (config.contribute?.enabled === true ||
101
+ config.contribute?.enabled === false) {
102
+ return false;
103
+ }
104
+ const scanCount = config.contribute?.scanCount ?? 0;
105
+ if (scanCount === 0)
106
+ return true;
107
+ if (scanCount >= 9 && !config.contribute?.promptedAtTen)
108
+ return true;
109
+ return false;
110
+ },
111
+ dismissContributePrompt() {
112
+ const config = readConfig();
113
+ if (!config.contribute)
114
+ config.contribute = {};
115
+ config.contribute.promptedAtTen = true;
116
+ writeConfig(config);
117
+ },
118
+ };
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Public API (signatures preserved for backward compatibility)
122
+ // ---------------------------------------------------------------------------
123
+ /**
124
+ * Check whether the contribution setting is enabled.
125
+ *
126
+ * Returns:
127
+ * true - user explicitly opted in
128
+ * false - user explicitly opted out (or default in shared backend)
129
+ * undefined - not yet configured (local fallback only; shared backend
130
+ * defaults to false, so callers should rely on
131
+ * shouldPromptContribute() for prompt logic)
132
+ */
133
+ export function isContributeEnabled() {
134
+ return resolveBackend().isContributeEnabled();
135
+ }
136
+ /**
137
+ * Check whether we should show the contribution prompt.
138
+ *
139
+ * ai-trust-specific: also checks for TTY (non-interactive environments
140
+ * should never prompt). The backend handles scan-count thresholds
141
+ * and cooldown/dismiss logic.
142
+ */
143
+ export function shouldPromptContribute() {
144
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
145
+ return false;
146
+ return resolveBackend().shouldPromptContribute();
147
+ }
148
+ /**
149
+ * Increment the scan count. Called after each scan completes,
150
+ * regardless of contribution setting.
151
+ */
152
+ export function incrementScanCount() {
153
+ resolveBackend().incrementScanCount();
154
+ }
155
+ /**
156
+ * Save the user's contribution choice to the config file.
157
+ */
158
+ export function saveContributeChoice(enabled) {
159
+ resolveBackend().setContributeEnabled(enabled);
160
+ if (!enabled) {
161
+ resolveBackend().dismissContributePrompt();
162
+ }
163
+ }
164
+ /**
165
+ * Display the contribution opt-in prompt and return the user's choice.
166
+ *
167
+ * Uses raw stdin to read a single keypress (Y/N).
168
+ * Returns true if the user opted in, false otherwise.
169
+ */
170
+ export async function showContributePrompt() {
171
+ const lines = [
172
+ "",
173
+ "Help improve security for the AI agent community.",
174
+ "",
175
+ "Share anonymized scan findings with the OpenA2A Registry?",
176
+ "No personal data. No source code. Only check pass/fail results.",
177
+ "You can opt out anytime: opena2a config set contribute false",
178
+ "",
179
+ "[Y] Yes, contribute [N] No thanks",
180
+ ];
181
+ for (const line of lines) {
182
+ process.stderr.write(line + "\n");
183
+ }
184
+ const answer = await readSingleKey();
185
+ const enabled = answer.toLowerCase() === "y";
186
+ saveContributeChoice(enabled);
187
+ if (enabled) {
188
+ process.stderr.write("\nContribution enabled. Thank you.\n");
189
+ }
190
+ else {
191
+ process.stderr.write("\nContribution disabled. You can enable it later: opena2a config set contribute true\n");
192
+ }
193
+ return enabled;
194
+ }
195
+ /**
196
+ * Read a single keypress from stdin.
197
+ * Falls back to 'n' after a 30-second timeout.
198
+ */
199
+ function readSingleKey() {
200
+ return new Promise((resolve) => {
201
+ const stdin = process.stdin;
202
+ const wasRaw = stdin.isRaw;
203
+ // Timeout after 30 seconds -- default to 'n'
204
+ const timer = setTimeout(() => {
205
+ cleanup();
206
+ resolve("n");
207
+ }, 30_000);
208
+ function cleanup() {
209
+ clearTimeout(timer);
210
+ stdin.removeListener("data", onData);
211
+ if (stdin.isRaw !== wasRaw) {
212
+ stdin.setRawMode(wasRaw ?? false);
213
+ }
214
+ stdin.pause();
215
+ }
216
+ function onData(data) {
217
+ const char = data.toString().trim().toLowerCase();
218
+ cleanup();
219
+ resolve(char || "n");
220
+ }
221
+ stdin.setRawMode(true);
222
+ stdin.resume();
223
+ stdin.once("data", onData);
224
+ });
225
+ }
226
+ /**
227
+ * Reset the backend (for testing).
228
+ * When forceLocal is true, skips @opena2a/shared resolution and uses the
229
+ * local file-based backend. This allows tests to control config via
230
+ * OPENA2A_HOME without the shared backend ignoring that env var.
231
+ */
232
+ export function _resetBackend(forceLocal = false) {
233
+ _backend = undefined;
234
+ if (forceLocal) {
235
+ _backend = createLocalBackend();
236
+ }
237
+ }
238
+ //# sourceMappingURL=opt-in.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opt-in.js","sourceRoot":"","sources":["../../src/telemetry/opt-in.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "ai-trust",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Trust verification CLI for AI packages — check MCP servers, A2A agents, and AI tools before you install",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/opena2a-org/ai-trust.git"
8
+ },
5
9
  "type": "module",
6
10
  "main": "dist/index.js",
7
11
  "bin": {
@@ -33,6 +37,7 @@
33
37
  "author": "OpenA2A",
34
38
  "license": "Apache-2.0",
35
39
  "dependencies": {
40
+ "@opena2a/shared": "^0.1.0",
36
41
  "chalk": "^5.3.0",
37
42
  "commander": "^12.1.0"
38
43
  },