@keygraph/shannon 1.1.0 → 1.3.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.mjs CHANGED
@@ -165,6 +165,7 @@ function spawnWorker(opts) {
165
165
  args.push("-v", `${path.join(workspacePath, "deliverables")}:${opts.repo.containerPath}/.shannon/deliverables`);
166
166
  args.push("-v", `${path.join(workspacePath, "scratchpad")}:${opts.repo.containerPath}/.shannon/scratchpad`);
167
167
  args.push("-v", `${path.join(workspacePath, ".playwright-cli")}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
168
+ args.push("-v", `${path.join(workspacePath, ".playwright")}:${opts.repo.containerPath}/.playwright`);
168
169
  if (opts.promptsDir) args.push("-v", `${opts.promptsDir}:/app/apps/worker/prompts:ro`);
169
170
  if (opts.config) args.push("-v", `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
170
171
  if (opts.outputDir) args.push("-v", `${opts.outputDir}:/app/output`);
@@ -421,7 +422,9 @@ async function setup() {
421
422
  ]
422
423
  });
423
424
  if (p.isCancel(provider)) return cancelAndExit();
424
- saveConfig(await setupProvider(provider));
425
+ const config = await setupProvider(provider);
426
+ await maybePromptAdaptiveThinking(config);
427
+ saveConfig(config);
425
428
  const configPath = path.join(SHANNON_HOME$1, "config.toml");
426
429
  p.log.success(`Configuration saved to ${configPath}`);
427
430
  p.outro("Run `npx @keygraph/shannon start` to begin a scan.");
@@ -450,7 +453,7 @@ async function setupAnthropic() {
450
453
  if (authMethod === "oauth") config.anthropic = { oauth_token: await promptSecret("Enter your OAuth token") };
451
454
  else config.anthropic = { api_key: await promptSecret("Enter your Anthropic API key") };
452
455
  const customizeModels = await p.confirm({
453
- message: "Do you want to change the default models?\n Small - claude-haiku-4-5-20251001\n Medium - claude-sonnet-4-6\n Large - claude-opus-4-6",
456
+ message: "Do you want to change the default models?\n Small - claude-haiku-4-5-20251001\n Medium - claude-sonnet-4-6\n Large - claude-opus-4-7",
454
457
  initialValue: false
455
458
  });
456
459
  if (p.isCancel(customizeModels)) return cancelAndExit();
@@ -469,7 +472,7 @@ async function setupAnthropic() {
469
472
  if (p.isCancel(medium)) return cancelAndExit();
470
473
  const large = await p.text({
471
474
  message: "Large model ID",
472
- initialValue: "claude-opus-4-6",
475
+ initialValue: "claude-opus-4-7",
473
476
  validate: required("Large model ID is required")
474
477
  });
475
478
  if (p.isCancel(large)) return cancelAndExit();
@@ -500,7 +503,7 @@ async function setupCustomBaseUrl() {
500
503
  auth_token: await promptSecret("Enter the auth token for the custom endpoint")
501
504
  } };
502
505
  const customizeModels = await p.confirm({
503
- message: "Do you want to change the default models?\n Small - claude-haiku-4-5-20251001\n Medium - claude-sonnet-4-6\n Large - claude-opus-4-6",
506
+ message: "Do you want to change the default models?\n Small - claude-haiku-4-5-20251001\n Medium - claude-sonnet-4-6\n Large - claude-opus-4-7",
504
507
  initialValue: false
505
508
  });
506
509
  if (p.isCancel(customizeModels)) return cancelAndExit();
@@ -519,7 +522,7 @@ async function setupCustomBaseUrl() {
519
522
  if (p.isCancel(medium)) return cancelAndExit();
520
523
  const large = await p.text({
521
524
  message: "Large model ID",
522
- initialValue: "claude-opus-4-6",
525
+ initialValue: "claude-opus-4-7",
523
526
  validate: required("Large model ID is required")
524
527
  });
525
528
  if (p.isCancel(large)) return cancelAndExit();
@@ -553,7 +556,7 @@ async function setupBedrock() {
553
556
  if (p.isCancel(medium)) return cancelAndExit();
554
557
  const large = await p.text({
555
558
  message: "Large model ID",
556
- placeholder: "us.anthropic.claude-opus-4-6",
559
+ placeholder: "us.anthropic.claude-opus-4-7",
557
560
  validate: required("Large model ID is required")
558
561
  });
559
562
  if (p.isCancel(large)) return cancelAndExit();
@@ -610,7 +613,7 @@ async function setupVertex() {
610
613
  }),
611
614
  large: () => p.text({
612
615
  message: "Large model ID",
613
- placeholder: "claude-opus-4-6",
616
+ placeholder: "claude-opus-4-7",
614
617
  validate: required("Large model ID is required")
615
618
  })
616
619
  });
@@ -629,6 +632,23 @@ async function setupVertex() {
629
632
  }
630
633
  };
631
634
  }
635
+ async function maybePromptAdaptiveThinking(config) {
636
+ const m = config.models;
637
+ if (!(!m || [
638
+ m.small,
639
+ m.medium,
640
+ m.large
641
+ ].some((v) => v && /opus-4-[67]/.test(v)))) return;
642
+ const enable = await p.confirm({
643
+ message: "Enable adaptive thinking on Opus 4.6/4.7? Claude decides when and how deeply to reason.",
644
+ initialValue: true
645
+ });
646
+ if (p.isCancel(enable)) return cancelAndExit();
647
+ config.core = {
648
+ ...config.core,
649
+ adaptive_thinking: enable
650
+ };
651
+ }
632
652
  async function promptSecret(message) {
633
653
  const value = await p.password({
634
654
  message,
@@ -661,6 +681,12 @@ const CONFIG_MAP = [
661
681
  toml: "core.max_tokens",
662
682
  type: "number"
663
683
  },
684
+ {
685
+ env: "CLAUDE_ADAPTIVE_THINKING",
686
+ toml: "core.adaptive_thinking",
687
+ type: "boolean",
688
+ boolFormat: "literal"
689
+ },
664
690
  {
665
691
  env: "ANTHROPIC_API_KEY",
666
692
  toml: "anthropic.api_key",
@@ -732,15 +758,18 @@ const CONFIG_MAP = [
732
758
  type: "string"
733
759
  }
734
760
  ];
735
- /** Read a nested TOML value by dotted path (e.g. "anthropic.api_key"). */
736
- function getTomlValue(config, path) {
737
- const [section, key] = path.split(".");
761
+ /** Read a nested TOML value for a given mapping. */
762
+ function getTomlValue(config, mapping) {
763
+ const [section, key] = mapping.toml.split(".");
738
764
  if (!section || !key) return void 0;
739
765
  const sectionObj = config[section];
740
766
  if (!sectionObj || typeof sectionObj !== "object") return void 0;
741
767
  const value = sectionObj[key];
742
768
  if (value === void 0 || value === null) return void 0;
743
- if (typeof value === "boolean") return value ? "1" : "0";
769
+ if (typeof value === "boolean") {
770
+ if (mapping.boolFormat === "literal") return value ? "true" : "false";
771
+ return value ? "1" : "0";
772
+ }
744
773
  return String(value);
745
774
  }
746
775
  /** Parse the global TOML config file, returning null if it doesn't exist. */
@@ -897,7 +926,7 @@ function resolveConfig$1() {
897
926
  }
898
927
  for (const mapping of CONFIG_MAP) {
899
928
  if (process.env[mapping.env]) continue;
900
- const value = getTomlValue(toml, mapping.toml);
929
+ const value = getTomlValue(toml, mapping);
901
930
  if (value) process.env[mapping.env] = value;
902
931
  }
903
932
  }
@@ -925,7 +954,8 @@ const FORWARD_VARS = [
925
954
  "ANTHROPIC_SMALL_MODEL",
926
955
  "ANTHROPIC_MEDIUM_MODEL",
927
956
  "ANTHROPIC_LARGE_MODEL",
928
- "CLAUDE_CODE_MAX_OUTPUT_TOKENS"
957
+ "CLAUDE_CODE_MAX_OUTPUT_TOKENS",
958
+ "CLAUDE_ADAPTIVE_THINKING"
929
959
  ];
930
960
  /**
931
961
  * Load credentials into process.env.
@@ -1162,7 +1192,8 @@ async function start(args) {
1162
1192
  for (const dir of [
1163
1193
  "deliverables",
1164
1194
  "scratchpad",
1165
- ".playwright-cli"
1195
+ ".playwright-cli",
1196
+ ".playwright"
1166
1197
  ]) {
1167
1198
  const dirPath = path.join(workspacePath, dir);
1168
1199
  fs.mkdirSync(dirPath, { recursive: true });
@@ -1174,6 +1205,7 @@ async function start(args) {
1174
1205
  "scratchpad",
1175
1206
  ".playwright-cli"
1176
1207
  ]) fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
1208
+ fs.mkdirSync(path.join(repo.hostPath, ".playwright"), { recursive: true });
1177
1209
  const credentialsPath = getCredentialsPath();
1178
1210
  const hasCredentials = fs.existsSync(credentialsPath);
1179
1211
  if (hasCredentials) process.env.GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/google-sa-key.json";
@@ -1386,6 +1418,23 @@ function workspaces(version) {
1386
1418
  * in the current working directory.
1387
1419
  */
1388
1420
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
1421
+ function blockSudo() {
1422
+ const isSudo = !!process.env.SUDO_USER;
1423
+ const isRoot = process.geteuid?.() === 0;
1424
+ if (!isSudo && !isRoot) return;
1425
+ if (isSudo) {
1426
+ console.error("ERROR: Shannon must not be run with sudo.");
1427
+ console.error("Re-run this command as your normal user.");
1428
+ } else {
1429
+ console.error("ERROR: Shannon must not be run as the root user.");
1430
+ console.error("Switch to a regular user account and re-run this command.");
1431
+ }
1432
+ if (process.platform === "linux") {
1433
+ console.error("Configure Docker to run without sudo first:");
1434
+ console.error("https://docs.docker.com/engine/install/linux-postinstall");
1435
+ }
1436
+ process.exit(1);
1437
+ }
1389
1438
  function getVersion() {
1390
1439
  try {
1391
1440
  const pkgPath = path.join(__dirname, "..", "package.json");
@@ -1506,6 +1555,7 @@ function parseStartArgs(argv) {
1506
1555
  ...output && { output }
1507
1556
  };
1508
1557
  }
1558
+ blockSudo();
1509
1559
  const args = process.argv.slice(2);
1510
1560
  const command = args[0];
1511
1561
  switch (command) {
package/infra/compose.yml CHANGED
@@ -4,7 +4,7 @@ networks:
4
4
 
5
5
  services:
6
6
  temporal:
7
- image: temporalio/temporal:latest
7
+ image: temporalio/temporal:1.7.0
8
8
  container_name: shannon-temporal
9
9
  command: ["server", "start-dev", "--db-filename", "/home/temporal/temporal.db", "--ip", "0.0.0.0"]
10
10
  ports:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keygraph/shannon",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Shannon - Autonomous white-box AI pentester for web applications and APIs by Keygraph",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",