@coworker-jp/aidr 0.1.11 → 0.1.75
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/README.md +66 -14
- package/package.json +1 -1
- package/src/agents/standalone.mjs +3 -3
- package/src/binary-fetcher.mjs +6 -58
- package/src/cli.mjs +198 -160
- package/src/env-gate.mjs +208 -0
- package/src/red-team.mjs +169 -0
- package/src/scheduled.mjs +83 -222
- package/src/sentinel.mjs +143 -266
- package/src/templates.mjs +7 -6
- package/src/verify.mjs +3 -4
- package/src/browser-extension.mjs +0 -83
- package/src/invoker-resolve.mjs +0 -63
package/src/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { AGENT_ORDER, AGENT_REGISTRY, loadAgent } from "./agents/index.mjs";
|
|
|
12
12
|
import { verifyKey, maskKey } from "./verify.mjs";
|
|
13
13
|
import { fetchBinary, fetchOpengrep } from "./binary-fetcher.mjs";
|
|
14
14
|
import { detectInstalled } from "./detect.mjs";
|
|
15
|
-
import {
|
|
15
|
+
import { evaluateInstallGate, formatLocalPcGuidance } from "./env-gate.mjs";
|
|
16
16
|
import { resolveCallerHome, isRunningUnderSudo, chownToCaller } from "./sudo-user.mjs";
|
|
17
17
|
|
|
18
18
|
// Package identity — read at module load from the bundled package.json.
|
|
@@ -84,47 +84,37 @@ async function cmdInstall(opts) {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Install-environment gate (issue #243): an interactive terminal with zero
|
|
88
|
+
// cloud/CI/remote indicators is almost certainly someone's own PC — the
|
|
89
|
+
// machine that should run coworker-sentinel, not the ai-scanner hooks.
|
|
90
|
+
// Stop and point at the sentinel installer unless --local overrides.
|
|
91
|
+
// Non-interactive runs (CI, cloud sandboxes, agent-executed shells) pass
|
|
92
|
+
// silently: they are ai-scanner's intended environments.
|
|
93
|
+
if (!opts.local) {
|
|
94
|
+
const gate = evaluateInstallGate();
|
|
95
|
+
if (gate.block) {
|
|
96
|
+
console.error(formatLocalPcGuidance({
|
|
97
|
+
sentinelPlatform: gate.sentinelPlatform,
|
|
98
|
+
env: opts.env || DEFAULT_ENV,
|
|
99
|
+
}));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
const home = resolveCallerHome();
|
|
88
105
|
const scope = opts.scope || "user";
|
|
89
106
|
const base = scope === "user" ? home : process.cwd();
|
|
90
107
|
validateKey(opts.key);
|
|
91
108
|
|
|
92
|
-
let verifyInfo = null;
|
|
93
109
|
if (opts.verify !== false) {
|
|
94
110
|
try {
|
|
95
|
-
|
|
111
|
+
await verifyKey(opts.key, opts.env || DEFAULT_ENV);
|
|
96
112
|
} catch (e) {
|
|
97
113
|
console.error(`verify failed: ${e.message}`);
|
|
98
114
|
process.exit(1);
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
// --with-sentinel requires pro or trial plan
|
|
103
|
-
if (opts.withSentinel && verifyInfo && !verifyInfo.edr_enabled) {
|
|
104
|
-
console.error("Error: --with-sentinel requires a Pro or Trial plan.");
|
|
105
|
-
console.error(`Current plan: ${verifyInfo.plan}`);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// --with-sentinel requires root (same as --scheduled)
|
|
110
|
-
if (opts.withSentinel && !opts.dryRun
|
|
111
|
-
&& process.getuid && process.getuid() !== 0
|
|
112
|
-
&& process.env.AI_SCANNER_FAKE_ROOT !== "1") {
|
|
113
|
-
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.platform !== "win32";
|
|
114
|
-
if (interactive) {
|
|
115
|
-
console.error("--with-sentinel requires root. Re-running via sudo (you may be prompted for your password)...");
|
|
116
|
-
const result = spawnSync(
|
|
117
|
-
"sudo",
|
|
118
|
-
["-E", process.execPath, ...process.argv.slice(1)],
|
|
119
|
-
{ stdio: "inherit" }
|
|
120
|
-
);
|
|
121
|
-
process.exit(result.status ?? 1);
|
|
122
|
-
}
|
|
123
|
-
console.error("Error: --with-sentinel requires root privileges.");
|
|
124
|
-
console.error(` sudo -E npx @coworker-jp/aidr install --with-sentinel --key <ak_xxx>`);
|
|
125
|
-
process.exit(2);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
118
|
let agents;
|
|
129
119
|
if (opts.all) {
|
|
130
120
|
agents = detectInstalled(home);
|
|
@@ -146,62 +136,6 @@ async function cmdInstall(opts) {
|
|
|
146
136
|
}
|
|
147
137
|
}
|
|
148
138
|
|
|
149
|
-
// Standalone has no hook scripts and no settings.json — scheduled
|
|
150
|
-
// endpoint-info collection is the whole reason to pick it, so turn
|
|
151
|
-
// --scheduled on by default when standalone is selected. --binary-only
|
|
152
|
-
// still short-circuits later.
|
|
153
|
-
if (agents.includes("standalone")) {
|
|
154
|
-
opts.scheduled = true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// On Windows there is no ai-scanner binary and no cron/systemd/launchd, so
|
|
158
|
-
// the scheduled endpoint-info scan cannot run — the standalone install is
|
|
159
|
-
// coworker-sentinel-only there (`--with-sentinel` installs the daemon below).
|
|
160
|
-
// Force scheduled off so it doesn't abort the install before sentinel.
|
|
161
|
-
if (process.platform === "win32" && opts.scheduled) {
|
|
162
|
-
console.error("Note: scheduled scanning is not available on Windows; installing coworker-sentinel only.");
|
|
163
|
-
opts.scheduled = false;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// --scheduled installs a system-wide cron / systemd / launchd entry that
|
|
167
|
-
// runs `ai-scanner scan endpoint-info` periodically as root, against an
|
|
168
|
-
// agent-independent binary at /opt/coworker/aidr/bin/ai-scanner. Auto-add
|
|
169
|
-
// the standalone install so the binary fetch loop drops a copy there
|
|
170
|
-
// even when the user only listed AI agents (claude / cursor / ...) on
|
|
171
|
-
// the command line.
|
|
172
|
-
if (opts.scheduled && !agents.includes("standalone")) {
|
|
173
|
-
agents.push("standalone");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const intervalHours = parseInt(opts.interval ?? "4", 10);
|
|
177
|
-
if (opts.scheduled && (isNaN(intervalHours) || intervalHours < 1)) {
|
|
178
|
-
console.error("--interval must be an integer >= 1");
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// --scheduled の system path 書き込みは root 必須。
|
|
183
|
-
// - TTY あり (対話 shell): `sudo -E` で自分自身を re-exec、password prompt が出る
|
|
184
|
-
// - TTY なし (CI / pipe): sudo は対話的に password を聞けないので fail-fast 案内
|
|
185
|
-
// smoke / unit test では `AI_SCANNER_FAKE_ROOT=1` を設定してこのチェックを skip 可能。
|
|
186
|
-
if (opts.scheduled && !opts.dryRun
|
|
187
|
-
&& process.getuid && process.getuid() !== 0
|
|
188
|
-
&& process.env.AI_SCANNER_FAKE_ROOT !== "1") {
|
|
189
|
-
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.platform !== "win32";
|
|
190
|
-
if (interactive) {
|
|
191
|
-
console.error("--scheduled requires root. Re-running via sudo (you may be prompted for your password)...");
|
|
192
|
-
const result = spawnSync(
|
|
193
|
-
"sudo",
|
|
194
|
-
["-E", process.execPath, ...process.argv.slice(1)],
|
|
195
|
-
{ stdio: "inherit" }
|
|
196
|
-
);
|
|
197
|
-
process.exit(result.status ?? 1);
|
|
198
|
-
}
|
|
199
|
-
console.error("Error: --scheduled requires root privileges (writes /opt/coworker/aidr, /etc/systemd/system, /etc/aidr).");
|
|
200
|
-
console.error("Re-run with sudo (non-interactive shells cannot prompt for password):");
|
|
201
|
-
console.error(` sudo -E npx @coworker-jp/aidr install --scheduled --key <ak_xxx>`);
|
|
202
|
-
process.exit(2);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
139
|
// Confirmation for existing shared settings.json (claude / gemini).
|
|
206
140
|
// --yes bypasses; --dry-run doesn't write so skip; otherwise prompt on TTY,
|
|
207
141
|
// error if non-interactive (safer than silently merging in CI).
|
|
@@ -231,16 +165,9 @@ async function cmdInstall(opts) {
|
|
|
231
165
|
}
|
|
232
166
|
|
|
233
167
|
if (!opts.skipBinary && !opts.dryRun) {
|
|
234
|
-
const { SYSTEM_BIN_DIR } = await import("./scheduled.mjs");
|
|
235
168
|
const agentBinDirs = [];
|
|
236
169
|
for (const name of agents) {
|
|
237
170
|
const mod = await loadAgent(name);
|
|
238
|
-
// --scheduled 時の standalone は system path (/opt/coworker/aidr/bin)
|
|
239
|
-
// に置く。それ以外は従来通り per-user の ~/.<agent>/bin。
|
|
240
|
-
if (name === "standalone" && opts.scheduled) {
|
|
241
|
-
agentBinDirs.push(SYSTEM_BIN_DIR);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
171
|
const agentDir = mod.meta.agentDir;
|
|
245
172
|
if (!agentDir) continue;
|
|
246
173
|
agentBinDirs.push(path.join(home, agentDir, "bin"));
|
|
@@ -272,10 +199,27 @@ async function cmdInstall(opts) {
|
|
|
272
199
|
const src = path.join(primaryDir, file);
|
|
273
200
|
const dest = path.join(restDir, file);
|
|
274
201
|
await fs.mkdir(restDir, { recursive: true });
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
202
|
+
// Temp + atomic rename, NEVER copyFile onto the live path — same
|
|
203
|
+
// rule (and same reason) as binary-fetcher.mjs::fetchAsset. On a
|
|
204
|
+
// re-install this path already holds a binary the user's agent has
|
|
205
|
+
// exec'd, and on macOS rewriting an exec'd file in place makes the
|
|
206
|
+
// kernel mark THAT VNODE's code signature invalid: every later exec
|
|
207
|
+
// of the inode is SIGKILLed until reboot, even though the bytes on
|
|
208
|
+
// disk are valid and re-running the installer cannot repair it
|
|
209
|
+
// (the inode never changes). See issue #171 / ADR-0005 §52.
|
|
210
|
+
// The temp file is in the destination dir, so the rename is on one
|
|
211
|
+
// filesystem = atomic, and mode/xattr are set BEFORE it goes live.
|
|
212
|
+
const tmp = `${dest}.tmp-${process.pid}`;
|
|
213
|
+
try {
|
|
214
|
+
await fs.copyFile(src, tmp);
|
|
215
|
+
await fs.chmod(tmp, 0o755);
|
|
216
|
+
if (process.platform === "darwin") {
|
|
217
|
+
try { await execFileP("xattr", ["-d", "com.apple.quarantine", tmp]); } catch {}
|
|
218
|
+
}
|
|
219
|
+
await fs.rename(tmp, dest);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
await fs.rm(tmp, { force: true }).catch(() => {});
|
|
222
|
+
throw e;
|
|
279
223
|
}
|
|
280
224
|
console.log(`binary: ${dest} (copied)`);
|
|
281
225
|
})
|
|
@@ -315,54 +259,29 @@ async function cmdInstall(opts) {
|
|
|
315
259
|
.filter((r) => r.name !== "standalone")
|
|
316
260
|
.map((r) => r.displayName);
|
|
317
261
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const presignedUrl = verifyInfo?.sentinel_download_urls?.[platform] || undefined;
|
|
339
|
-
// Windows additionally needs the sibling WinDivert.dll presigned URL.
|
|
340
|
-
const dllPresignedUrl =
|
|
341
|
-
verifyInfo?.sentinel_download_urls?.[`${platform}-windivert-dll`] || undefined;
|
|
342
|
-
await installSentinel({
|
|
343
|
-
accessKey: opts.key,
|
|
344
|
-
dryRun: opts.dryRun,
|
|
345
|
-
skipBinary: opts.skipBinary,
|
|
346
|
-
presignedUrl,
|
|
347
|
-
dllPresignedUrl,
|
|
348
|
-
env: opts.env || DEFAULT_ENV,
|
|
349
|
-
noActivate: process.env.AI_SCANNER_NO_SCHEDULED_ACTIVATE === "1",
|
|
350
|
-
});
|
|
351
|
-
} catch (e) {
|
|
352
|
-
console.error(`sentinel install failed: ${e.message}`);
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (opts.browserExtension && !opts.dryRun) {
|
|
358
|
-
const firstAgentName = agents[0];
|
|
359
|
-
const firstAgent = await loadAgent(firstAgentName);
|
|
360
|
-
const binaryPath = path.join(home, firstAgent.meta.agentDir, "bin", "ai-scanner");
|
|
361
|
-
try {
|
|
362
|
-
await installBrowserExtension(home, binaryPath);
|
|
363
|
-
} catch (e) {
|
|
364
|
-
console.error(`browser extension install warning: ${e.message}`);
|
|
262
|
+
// Legacy `--scheduled` artifacts (cron.d / launchd plist / wrapper /
|
|
263
|
+
// scheduled.log) are retired — endpoint-info collection now runs from the
|
|
264
|
+
// coworker-sentinel daemon's internal timer. Remove leftovers from older
|
|
265
|
+
// installs when we have root; otherwise just point the user at sudo.
|
|
266
|
+
// /etc/aidr/aidr.env is intentionally preserved (sentinel reads it).
|
|
267
|
+
try {
|
|
268
|
+
const { isScheduledInstalled, cleanupLegacyScheduled } = await import("./scheduled.mjs");
|
|
269
|
+
if (await isScheduledInstalled()) {
|
|
270
|
+
const isRoot = process.env.AI_SCANNER_FAKE_ROOT === "1"
|
|
271
|
+
|| (process.getuid && process.getuid() === 0);
|
|
272
|
+
if (opts.dryRun) {
|
|
273
|
+
console.error("[dry-run] legacy scheduled endpoint-info entries detected; a real install would remove them (root required).");
|
|
274
|
+
} else if (isRoot) {
|
|
275
|
+
await cleanupLegacyScheduled();
|
|
276
|
+
console.error("Removed legacy scheduled endpoint-info entries (cron/launchd) — collection is now handled by the coworker-sentinel daemon.");
|
|
277
|
+
} else {
|
|
278
|
+
console.error("Note: legacy scheduled endpoint-info entries detected (cron/launchd). They are obsolete —");
|
|
279
|
+
console.error("collection is now handled by the coworker-sentinel daemon. Re-run with sudo to remove them:");
|
|
280
|
+
console.error(" sudo -E npx @coworker-jp/aidr install ...");
|
|
281
|
+
}
|
|
365
282
|
}
|
|
283
|
+
} catch (e) {
|
|
284
|
+
console.error(`legacy scheduled cleanup warning: ${e.message}`);
|
|
366
285
|
}
|
|
367
286
|
|
|
368
287
|
// Auto-sudo re-exec wrote per-agent files as root into the original user's
|
|
@@ -505,18 +424,20 @@ async function cmdUninstall(opts) {
|
|
|
505
424
|
}
|
|
506
425
|
}));
|
|
507
426
|
|
|
508
|
-
//
|
|
509
|
-
// and need root to remove. Mirror cmdInstall's
|
|
510
|
-
// re-exec、non-TTY は guidance を出して
|
|
427
|
+
// Legacy `--scheduled` entries live in system paths (/etc/cron.d,
|
|
428
|
+
// /Library/LaunchDaemons) and need root to remove. Mirror cmdInstall's
|
|
429
|
+
// auto-sudo: TTY なら sudo に re-exec、non-TTY は guidance を出して
|
|
430
|
+
// per-user 部分だけ撤去で完了。/etc/aidr/aidr.env は sentinel が読むので
|
|
431
|
+
// 残す (cleanupLegacyScheduled は触らない)。
|
|
511
432
|
try {
|
|
512
|
-
const {
|
|
433
|
+
const { cleanupLegacyScheduled, isScheduledInstalled } = await import("./scheduled.mjs");
|
|
513
434
|
if (await isScheduledInstalled()) {
|
|
514
435
|
const isRoot = process.env.AI_SCANNER_FAKE_ROOT === "1"
|
|
515
436
|
|| (process.getuid && process.getuid() === 0);
|
|
516
437
|
if (!isRoot) {
|
|
517
438
|
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.platform !== "win32";
|
|
518
439
|
if (interactive) {
|
|
519
|
-
console.error("
|
|
440
|
+
console.error("Legacy scheduled entry detected. Re-running via sudo to remove (you may be prompted for your password)...");
|
|
520
441
|
const result = spawnSync(
|
|
521
442
|
"sudo",
|
|
522
443
|
["-E", process.execPath, ...process.argv.slice(1)],
|
|
@@ -524,19 +445,23 @@ async function cmdUninstall(opts) {
|
|
|
524
445
|
);
|
|
525
446
|
process.exit(result.status ?? 0);
|
|
526
447
|
}
|
|
527
|
-
console.error("Note: scheduled entry detected but uninstall needs root. Re-run:");
|
|
448
|
+
console.error("Note: legacy scheduled entry detected but uninstall needs root. Re-run:");
|
|
528
449
|
console.error(` sudo -E npx @coworker-jp/aidr uninstall`);
|
|
529
450
|
} else {
|
|
530
|
-
await
|
|
451
|
+
await cleanupLegacyScheduled({ dryRun: opts.dryRun });
|
|
531
452
|
}
|
|
532
453
|
}
|
|
533
454
|
} catch (e) {
|
|
534
|
-
console.error(`scheduled
|
|
455
|
+
console.error(`scheduled cleanup warning: ${e.message}`);
|
|
535
456
|
}
|
|
536
457
|
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
458
|
+
// LEGACY npx-installed sentinel daemon (Linux systemd unit under
|
|
459
|
+
// /etc/systemd/system + /opt binaries) — same auto-sudo pattern.
|
|
460
|
+
// isSentinelInstalled matches only that legacy layout: deb installs
|
|
461
|
+
// (unit under /lib/systemd/system), the Windows setup.exe, and the macOS
|
|
462
|
+
// DMG are owned by their own uninstallers and never touched here.
|
|
463
|
+
// Independent try-block so a scheduled-uninstall hiccup doesn't skip
|
|
464
|
+
// sentinel cleanup (and vice-versa).
|
|
540
465
|
try {
|
|
541
466
|
const { uninstallSentinel, isSentinelInstalled } = await import("./sentinel.mjs");
|
|
542
467
|
if (await isSentinelInstalled()) {
|
|
@@ -545,7 +470,7 @@ async function cmdUninstall(opts) {
|
|
|
545
470
|
if (!isRoot) {
|
|
546
471
|
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.platform !== "win32";
|
|
547
472
|
if (interactive) {
|
|
548
|
-
console.error("
|
|
473
|
+
console.error("Legacy npx-installed sentinel daemon detected. Re-running via sudo to remove (you may be prompted for your password)...");
|
|
549
474
|
const result = spawnSync(
|
|
550
475
|
"sudo",
|
|
551
476
|
["-E", process.execPath, ...process.argv.slice(1)],
|
|
@@ -553,7 +478,7 @@ async function cmdUninstall(opts) {
|
|
|
553
478
|
);
|
|
554
479
|
process.exit(result.status ?? 0);
|
|
555
480
|
}
|
|
556
|
-
console.error("Note: sentinel daemon detected but uninstall needs root. Re-run:");
|
|
481
|
+
console.error("Note: legacy npx-installed sentinel daemon detected but uninstall needs root. Re-run:");
|
|
557
482
|
console.error(` sudo -E npx @coworker-jp/aidr uninstall`);
|
|
558
483
|
} else {
|
|
559
484
|
await uninstallSentinel({ dryRun: opts.dryRun });
|
|
@@ -597,7 +522,101 @@ async function cmdListAgents() {
|
|
|
597
522
|
}
|
|
598
523
|
}
|
|
599
524
|
|
|
525
|
+
// `--scheduled` / `--interval` were retired when endpoint-info collection
|
|
526
|
+
// moved into the coworker-sentinel daemon (internal 6h timer), and
|
|
527
|
+
// `--with-sentinel` was retired when sentinel distribution moved to the
|
|
528
|
+
// OS-native installers (setup.exe / DMG / deb). Commander no longer knows
|
|
529
|
+
// the flags, so scan argv ourselves to fail with a migration message
|
|
530
|
+
// instead of a generic "unknown option" error.
|
|
531
|
+
const SENTINEL_OS_INSTALLERS_HINT = `coworker-sentinel is installed with the OS-native installer (get it from the user portal's Download page):
|
|
532
|
+
Windows: coworker-sentinel-setup.exe
|
|
533
|
+
macOS: CoworkerSentinel.dmg (drag to Applications, launch, enter your access key)
|
|
534
|
+
Linux: sudo apt install ./coworker-sentinel.deb && sudo coworker-sentinel activate --key <ak_xxx>`;
|
|
535
|
+
|
|
536
|
+
const REMOVED_SCHEDULED_FLAGS_MESSAGE = `Error: --scheduled / --interval are no longer supported.
|
|
537
|
+
Endpoint-info collection is now scheduled by the coworker-sentinel daemon (every 6 hours).
|
|
538
|
+
${SENTINEL_OS_INSTALLERS_HINT}
|
|
539
|
+
Toggle collection from the macOS menu-bar app, or edit /etc/aidr/sentinel-settings.json
|
|
540
|
+
(Windows: %ProgramData%\\coworker\\sentinel-settings.json).`;
|
|
541
|
+
|
|
542
|
+
const REMOVED_WITH_SENTINEL_MESSAGE = `Error: --with-sentinel is no longer supported.
|
|
543
|
+
${SENTINEL_OS_INSTALLERS_HINT}
|
|
544
|
+
aidr now installs agent hooks only:
|
|
545
|
+
npx @coworker-jp/aidr install --agent <list> --key <ak_xxx>`;
|
|
546
|
+
|
|
547
|
+
// Red Team は hook 方式のエージェント統合とは別物 — hook を撒くのではなく、
|
|
548
|
+
// ネットワーク脆弱性スキャナを MCP サーバーとして 1 つ登録するだけ。そのため
|
|
549
|
+
// install/uninstall の --agent 複数指定・--all の枠組みには乗せず、独立した
|
|
550
|
+
// サブコマンドにしている。実装は red-team.mjs に分離 (scheduled/sentinel と
|
|
551
|
+
// 同じ遅延 import パターン)。
|
|
552
|
+
async function cmdRedTeamInstall(opts) {
|
|
553
|
+
const agent = opts.agent || "claude";
|
|
554
|
+
if (agent !== "claude") {
|
|
555
|
+
console.error(`red-team install: unsupported agent '${agent}' (only 'claude' is supported)`);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const home = resolveCallerHome();
|
|
560
|
+
const scope = opts.scope || "user";
|
|
561
|
+
validateKey(opts.key);
|
|
562
|
+
|
|
563
|
+
const env = opts.env || DEFAULT_ENV;
|
|
564
|
+
if (opts.verify !== false && !opts.dryRun) {
|
|
565
|
+
try {
|
|
566
|
+
await verifyKey(opts.key, env);
|
|
567
|
+
} catch (e) {
|
|
568
|
+
console.error(`verify failed: ${e.message}`);
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const { installRedTeam } = await import("./red-team.mjs");
|
|
574
|
+
try {
|
|
575
|
+
const res = await installRedTeam({
|
|
576
|
+
accessKey: opts.key,
|
|
577
|
+
env,
|
|
578
|
+
scope,
|
|
579
|
+
home: scope === "user" ? home : process.cwd(),
|
|
580
|
+
dryRun: opts.dryRun,
|
|
581
|
+
});
|
|
582
|
+
if (res.installed) {
|
|
583
|
+
console.log("");
|
|
584
|
+
console.log("Next: restart Claude Code so the MCP server is picked up.");
|
|
585
|
+
}
|
|
586
|
+
} catch (e) {
|
|
587
|
+
// 403 red_team_requires_10_seats は「Pro/Trial かつ契約シート 10 以上」の
|
|
588
|
+
// エンタイトルメント不足。サーバー側が唯一の判断者なので、ここでは
|
|
589
|
+
// メッセージをそのまま見せる。
|
|
590
|
+
console.error(`red-team install failed: ${e.message}`);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async function cmdRedTeamUninstall(opts) {
|
|
596
|
+
const home = resolveCallerHome();
|
|
597
|
+
const scope = opts.scope || "user";
|
|
598
|
+
const { uninstallRedTeam } = await import("./red-team.mjs");
|
|
599
|
+
await uninstallRedTeam({
|
|
600
|
+
scope,
|
|
601
|
+
home: scope === "user" ? home : process.cwd(),
|
|
602
|
+
dryRun: opts.dryRun,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function rejectRemovedFlags(argv) {
|
|
607
|
+
const args = argv.slice(2);
|
|
608
|
+
if (args.some((a) => a === "--scheduled" || a === "--interval" || a.startsWith("--interval="))) {
|
|
609
|
+
console.error(REMOVED_SCHEDULED_FLAGS_MESSAGE);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
if (args.includes("--with-sentinel")) {
|
|
613
|
+
console.error(REMOVED_WITH_SENTINEL_MESSAGE);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
600
618
|
export async function run(argv) {
|
|
619
|
+
rejectRemovedFlags(argv);
|
|
601
620
|
const program = new Command();
|
|
602
621
|
program
|
|
603
622
|
.name("aidr")
|
|
@@ -617,12 +636,9 @@ export async function run(argv) {
|
|
|
617
636
|
.option("--no-verify", "skip /verify call")
|
|
618
637
|
.option("--skip-binary", "skip binary download", false)
|
|
619
638
|
.option("--scope <project|user>", "installation scope: user ($HOME) or project (cwd)", "user")
|
|
639
|
+
.option("--local", "install on this local PC anyway (ai-scanner targets cloud/CI agent environments; a personal PC should run coworker-sentinel)", false)
|
|
620
640
|
.option("-y, --yes", "proceed without confirmation when existing settings.json is detected", false)
|
|
621
641
|
.option("--uninstall", "remove all ai-scanner hooks/scripts/binaries for the selected agents (see 'uninstall' command)", false)
|
|
622
|
-
.option("--scheduled", "enable scheduled endpoint-info collection (creates a cron/systemd/launchd entry; without this flag endpoint-info is never collected)", false)
|
|
623
|
-
.option("--interval <hours>", "scheduled scan interval in hours (min 1, default 4; requires --scheduled)", "4")
|
|
624
|
-
.option("--with-sentinel", "install coworker-sentinel host-level EDR daemon (Pro/Trial plan; requires sudo)", false)
|
|
625
|
-
.option("--browser-extension", "install browser extension native messaging host for Shadow IT network monitoring (default: disabled)", false)
|
|
626
642
|
.action(cmdInstall);
|
|
627
643
|
|
|
628
644
|
program
|
|
@@ -635,6 +651,28 @@ export async function run(argv) {
|
|
|
635
651
|
.option("--dry-run", "print what would be removed without touching disk", false)
|
|
636
652
|
.action(cmdUninstall);
|
|
637
653
|
|
|
654
|
+
const redTeam = program
|
|
655
|
+
.command("red-team")
|
|
656
|
+
.description("Red Team network vulnerability scanner (MCP server for Claude Code)");
|
|
657
|
+
|
|
658
|
+
redTeam
|
|
659
|
+
.command("install")
|
|
660
|
+
.description("Download the scanner and register it as an MCP server")
|
|
661
|
+
.option("--agent <name>", "target agent (only 'claude' is supported)", "claude")
|
|
662
|
+
.requiredOption("--key <ak_xxx>", "access key (ak_<43-char>)")
|
|
663
|
+
.option("--env <env>", "target environment: dev|prod (default: auto-detected from package name)")
|
|
664
|
+
.option("--scope <project|user>", "installation scope: user ($HOME) or project (cwd)", "user")
|
|
665
|
+
.option("--no-verify", "skip /verify call")
|
|
666
|
+
.option("--dry-run", "print planned actions, do not touch disk", false)
|
|
667
|
+
.action(cmdRedTeamInstall);
|
|
668
|
+
|
|
669
|
+
redTeam
|
|
670
|
+
.command("uninstall")
|
|
671
|
+
.description("Remove the MCP server registration and the downloaded scanner")
|
|
672
|
+
.option("--scope <project|user>", "scope: user ($HOME) or project (cwd)", "user")
|
|
673
|
+
.option("--dry-run", "print what would be removed without touching disk", false)
|
|
674
|
+
.action(cmdRedTeamUninstall);
|
|
675
|
+
|
|
638
676
|
program.command("doctor").action(cmdDoctor);
|
|
639
677
|
program.command("list-agents").action(cmdListAgents);
|
|
640
678
|
|