@keygraph/shannon 1.0.0-beta.2 → 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.mjs CHANGED
@@ -76,59 +76,30 @@ function isTemporalReady() {
76
76
  "localhost:7233"
77
77
  ]).includes("SERVING");
78
78
  }
79
- /** Check if the router container is running and healthy. */
80
- function isRouterReady() {
81
- return runOutput("docker", [
82
- "inspect",
83
- "--format",
84
- "{{.State.Health.Status}}",
85
- "shannon-router"
86
- ]) === "healthy";
87
- }
88
79
  /**
89
- * Ensure Temporal (and optionally router) are running via compose.
90
- * If Temporal is already up but router is needed and missing, starts router only.
80
+ * Ensure Temporal is running via compose.
91
81
  */
92
- async function ensureInfra(useRouter) {
93
- const temporalReady = isTemporalReady();
94
- const routerNeeded = useRouter && !isRouterReady();
95
- if (temporalReady && !routerNeeded) return;
96
- const composeArgs = [
82
+ async function ensureInfra() {
83
+ if (isTemporalReady()) return;
84
+ const composeFile = getComposeFile();
85
+ console.log("Starting Shannon infrastructure...");
86
+ execFileSync("docker", [
97
87
  "compose",
98
88
  "-f",
99
- getComposeFile()
100
- ];
101
- if (useRouter) composeArgs.push("--profile", "router");
102
- composeArgs.push("up", "-d");
103
- if (temporalReady && routerNeeded) console.log("Starting router...");
104
- else console.log("Starting Shannon infrastructure...");
105
- execFileSync("docker", composeArgs, { stdio: "inherit" });
106
- if (!temporalReady) {
107
- console.log("Waiting for Temporal to be ready...");
108
- for (let i = 0; i < 30; i++) {
109
- if (isTemporalReady()) {
110
- console.log("Temporal is ready!");
111
- break;
112
- }
113
- if (i === 29) {
114
- console.error("Timeout waiting for Temporal");
115
- process.exit(1);
116
- }
117
- await setTimeout$1(2e3);
118
- }
119
- }
120
- if (routerNeeded) {
121
- console.log("Waiting for router to be ready...");
122
- for (let i = 0; i < 15; i++) {
123
- if (isRouterReady()) {
124
- console.log("Router is ready!");
125
- return;
126
- }
127
- await setTimeout$1(2e3);
89
+ composeFile,
90
+ "up",
91
+ "-d"
92
+ ], { stdio: "inherit" });
93
+ console.log("Waiting for Temporal to be ready...");
94
+ for (let i = 0; i < 30; i++) {
95
+ if (isTemporalReady()) {
96
+ console.log("Temporal is ready!");
97
+ return;
128
98
  }
129
- console.error("Timeout waiting for router");
130
- process.exit(1);
99
+ await setTimeout$1(2e3);
131
100
  }
101
+ console.error("Timeout waiting for Temporal");
102
+ process.exit(1);
132
103
  }
133
104
  /**
134
105
  * Build the worker image locally (local mode only).
@@ -180,37 +151,39 @@ function addHostFlag() {
180
151
  }
181
152
  /**
182
153
  * Spawn the worker container in detached mode and return the process.
154
+ * When `opts.debug` is true, omits `--rm` so the container persists for log inspection.
183
155
  */
184
156
  function spawnWorker(opts) {
185
- const args = [
186
- "run",
187
- "-d",
188
- "--rm",
189
- "--name",
190
- opts.containerName,
191
- "--network",
192
- "shannon-net"
193
- ];
157
+ const args = ["run", "-d"];
158
+ if (!opts.debug) args.push("--rm");
159
+ args.push("--name", opts.containerName, "--network", "shannon-net");
194
160
  args.push(...addHostFlag());
195
161
  if (os.platform() === "linux" && process.getuid && process.getgid) args.push("-e", `SHANNON_HOST_UID=${process.getuid()}`, "-e", `SHANNON_HOST_GID=${process.getgid()}`);
196
162
  args.push("-v", `${opts.workspacesDir}:/app/workspaces`);
197
- args.push("-v", `${opts.repo.hostPath}:${opts.repo.containerPath}`);
163
+ args.push("-v", `${opts.repo.hostPath}:${opts.repo.containerPath}:ro`);
164
+ const workspacePath = path.join(opts.workspacesDir, opts.workspace);
165
+ args.push("-v", `${path.join(workspacePath, "deliverables")}:${opts.repo.containerPath}/.shannon/deliverables`);
166
+ args.push("-v", `${path.join(workspacePath, "scratchpad")}:${opts.repo.containerPath}/.shannon/scratchpad`);
167
+ args.push("-v", `${path.join(workspacePath, ".playwright-cli")}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
198
168
  if (opts.promptsDir) args.push("-v", `${opts.promptsDir}:/app/apps/worker/prompts:ro`);
199
169
  if (opts.config) args.push("-v", `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
200
170
  if (opts.outputDir) args.push("-v", `${opts.outputDir}:/app/output`);
201
- if (opts.credentialsDir) args.push("-v", `${opts.credentialsDir}:/app/credentials:ro`);
202
- else if (opts.credentials) args.push("-v", `${opts.credentials}:/app/credentials/google-sa-key.json:ro`);
171
+ if (opts.credentials) args.push("-v", `${opts.credentials}:/app/credentials/google-sa-key.json:ro`);
203
172
  args.push(...opts.envFlags);
204
- args.push("--shm-size", "2gb", "--ipc", "host", "--security-opt", "seccomp=unconfined");
173
+ args.push("--shm-size", "2gb", "--security-opt", "seccomp=unconfined");
205
174
  args.push(getWorkerImage(opts.version));
206
175
  args.push("node", "apps/worker/dist/temporal/worker.js", opts.url, opts.repo.containerPath);
207
176
  args.push("--task-queue", opts.taskQueue);
208
177
  if (opts.config) args.push("--config", opts.config.containerPath);
209
178
  if (opts.outputDir) args.push("--output", "/app/output");
210
- if (opts.workspace) args.push("--workspace", opts.workspace);
179
+ args.push("--workspace", opts.workspace);
211
180
  if (opts.pipelineTesting) args.push("--pipeline-testing");
212
181
  return spawn("docker", args, {
213
- stdio: "pipe",
182
+ stdio: [
183
+ "ignore",
184
+ "ignore",
185
+ "inherit"
186
+ ],
214
187
  ...os.platform() === "win32" && { env: {
215
188
  ...process.env,
216
189
  MSYS_NO_PATHCONV: "1"
@@ -240,8 +213,6 @@ function stopInfra(clean) {
240
213
  "compose",
241
214
  "-f",
242
215
  getComposeFile(),
243
- "--profile",
244
- "router",
245
216
  "down"
246
217
  ];
247
218
  if (clean) args.push("-v");
@@ -318,16 +289,6 @@ function getCredentialsPath() {
318
289
  return path.join(SHANNON_HOME$2, "google-sa-key.json");
319
290
  }
320
291
  /**
321
- * In dev mode, return the credentials directory if it exists and has files.
322
- * In npx mode, there is no credentials directory (single file mount instead).
323
- */
324
- function getCredentialsDir() {
325
- if (getMode() !== "local") return void 0;
326
- const dir = path.resolve("credentials");
327
- if (!fs.existsSync(dir)) return void 0;
328
- return fs.readdirSync(dir).length > 0 ? dir : void 0;
329
- }
330
- /**
331
292
  * Initialize state directories.
332
293
  * Local mode: creates ./workspaces/ and ./credentials/
333
294
  * NPX mode: creates ~/.shannon/workspaces/
@@ -456,11 +417,6 @@ async function setup() {
456
417
  {
457
418
  value: "vertex",
458
419
  label: "Claude via Google Vertex AI"
459
- },
460
- {
461
- value: "router",
462
- label: "Router",
463
- hint: "experimental"
464
420
  }
465
421
  ]
466
422
  });
@@ -476,7 +432,6 @@ async function setupProvider(provider) {
476
432
  case "custom_base_url": return setupCustomBaseUrl();
477
433
  case "bedrock": return setupBedrock();
478
434
  case "vertex": return setupVertex();
479
- case "router": return setupRouter();
480
435
  }
481
436
  }
482
437
  async function setupAnthropic() {
@@ -674,49 +629,6 @@ async function setupVertex() {
674
629
  }
675
630
  };
676
631
  }
677
- async function setupRouter() {
678
- const routerProvider = await p.select({
679
- message: "Router provider",
680
- options: [{
681
- value: "openai",
682
- label: "OpenAI"
683
- }, {
684
- value: "openrouter",
685
- label: "OpenRouter"
686
- }]
687
- });
688
- if (p.isCancel(routerProvider)) return cancelAndExit();
689
- const apiKey = await promptSecret(routerProvider === "openai" ? "Enter your OpenAI API key" : "Enter your OpenRouter API key");
690
- let defaultModel;
691
- if (routerProvider === "openai") {
692
- const model = await p.select({
693
- message: "Default model",
694
- options: [{
695
- value: "gpt-5.2",
696
- label: "GPT-5.2"
697
- }, {
698
- value: "gpt-5-mini",
699
- label: "GPT-5 Mini"
700
- }]
701
- });
702
- if (p.isCancel(model)) return cancelAndExit();
703
- defaultModel = `openai,${model}`;
704
- } else {
705
- const model = await p.select({
706
- message: "Default model",
707
- options: [{
708
- value: "google/gemini-3-flash-preview",
709
- label: "Google Gemini 3 Flash Preview"
710
- }]
711
- });
712
- if (p.isCancel(model)) return cancelAndExit();
713
- defaultModel = `openrouter,${model}`;
714
- }
715
- const router = { default: defaultModel };
716
- if (routerProvider === "openai") router.openai_key = apiKey;
717
- else router.openrouter_key = apiKey;
718
- return { router };
719
- }
720
632
  async function promptSecret(message) {
721
633
  const value = await p.password({
722
634
  message,
@@ -804,21 +716,6 @@ const CONFIG_MAP = [
804
716
  toml: "custom_base_url.auth_token",
805
717
  type: "string"
806
718
  },
807
- {
808
- env: "ROUTER_DEFAULT",
809
- toml: "router.default",
810
- type: "string"
811
- },
812
- {
813
- env: "OPENAI_API_KEY",
814
- toml: "router.openai_key",
815
- type: "string"
816
- },
817
- {
818
- env: "OPENROUTER_API_KEY",
819
- toml: "router.openrouter_key",
820
- type: "string"
821
- },
822
719
  {
823
720
  env: "ANTHROPIC_SMALL_MODEL",
824
721
  toml: "models.small",
@@ -917,13 +814,6 @@ function validateProviderFields(config, provider, errors) {
917
814
  validateModelTiers(config, "vertex", errors);
918
815
  break;
919
816
  }
920
- case "router": {
921
- if (!keys.includes("default")) errors.push("[router] missing required key: default");
922
- if (!keys.includes("openai_key") && !keys.includes("openrouter_key")) errors.push("[router] requires either openai_key or openrouter_key");
923
- const models = config.models;
924
- if (models && typeof models === "object" && Object.keys(models).length > 0) errors.push("[models] is not supported with [router]");
925
- break;
926
- }
927
817
  }
928
818
  }
929
819
  /** Bedrock and Vertex require a [models] section with all three tiers. */
@@ -976,8 +866,7 @@ function validateConfig(config) {
976
866
  "anthropic",
977
867
  "custom_base_url",
978
868
  "bedrock",
979
- "vertex",
980
- "router"
869
+ "vertex"
981
870
  ].filter((s) => {
982
871
  const section = config[s];
983
872
  return section && typeof section === "object" && Object.keys(section).length > 0;
@@ -1025,7 +914,6 @@ const FORWARD_VARS = [
1025
914
  "ANTHROPIC_API_KEY",
1026
915
  "ANTHROPIC_BASE_URL",
1027
916
  "ANTHROPIC_AUTH_TOKEN",
1028
- "ROUTER_DEFAULT",
1029
917
  "CLAUDE_CODE_OAUTH_TOKEN",
1030
918
  "CLAUDE_CODE_USE_BEDROCK",
1031
919
  "AWS_REGION",
@@ -1037,9 +925,7 @@ const FORWARD_VARS = [
1037
925
  "ANTHROPIC_SMALL_MODEL",
1038
926
  "ANTHROPIC_MEDIUM_MODEL",
1039
927
  "ANTHROPIC_LARGE_MODEL",
1040
- "CLAUDE_CODE_MAX_OUTPUT_TOKENS",
1041
- "OPENAI_API_KEY",
1042
- "OPENROUTER_API_KEY"
928
+ "CLAUDE_CODE_MAX_OUTPUT_TOKENS"
1043
929
  ];
1044
930
  /**
1045
931
  * Load credentials into process.env.
@@ -1065,10 +951,6 @@ function buildEnvFlags() {
1065
951
  }
1066
952
  return flags;
1067
953
  }
1068
- /** Check if router credentials are present in the environment. */
1069
- function isRouterConfigured() {
1070
- return !!(process.env.ROUTER_DEFAULT && (process.env.OPENAI_API_KEY || process.env.OPENROUTER_API_KEY));
1071
- }
1072
954
  /** Check if a custom Anthropic-compatible base URL is configured. */
1073
955
  function isCustomBaseUrlConfigured() {
1074
956
  return !!(process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_AUTH_TOKEN);
@@ -1081,7 +963,6 @@ function detectProviders() {
1081
963
  if (isCustomBaseUrlConfigured()) providers.push("Custom Base URL");
1082
964
  if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") providers.push("AWS Bedrock");
1083
965
  if (process.env.CLAUDE_CODE_USE_VERTEX === "1") providers.push("Google Vertex");
1084
- if (isRouterConfigured()) providers.push("Router");
1085
966
  return providers;
1086
967
  }
1087
968
  /**
@@ -1102,13 +983,10 @@ function validateCredentials() {
1102
983
  valid: true,
1103
984
  mode: "oauth"
1104
985
  };
1105
- if (isCustomBaseUrlConfigured()) {
1106
- process.env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_AUTH_TOKEN;
1107
- return {
1108
- valid: true,
1109
- mode: "custom-base-url"
1110
- };
1111
- }
986
+ if (isCustomBaseUrlConfigured()) return {
987
+ valid: true,
988
+ mode: "custom-base-url"
989
+ };
1112
990
  if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
1113
991
  const missing = [];
1114
992
  if (!process.env.AWS_REGION) missing.push("AWS_REGION");
@@ -1148,13 +1026,6 @@ function validateCredentials() {
1148
1026
  mode: "vertex"
1149
1027
  };
1150
1028
  }
1151
- if (isRouterConfigured()) {
1152
- process.env.ANTHROPIC_API_KEY = "router-mode";
1153
- return {
1154
- valid: true,
1155
- mode: "router"
1156
- };
1157
- }
1158
1029
  return {
1159
1030
  valid: false,
1160
1031
  mode: "api-key",
@@ -1218,14 +1089,6 @@ function resolveConfig(configArg) {
1218
1089
  containerPath: `/app/configs/${path.basename(hostPath)}`
1219
1090
  };
1220
1091
  }
1221
- /**
1222
- * Ensure the deliverables directory exists and is writable by the container user.
1223
- */
1224
- function ensureDeliverables(repoHostPath) {
1225
- const deliverables = path.join(repoHostPath, "deliverables");
1226
- fs.mkdirSync(deliverables, { recursive: true });
1227
- fs.chmodSync(deliverables, 511);
1228
- }
1229
1092
  //#endregion
1230
1093
  //#region src/splash.ts
1231
1094
  /**
@@ -1282,31 +1145,43 @@ async function start(args) {
1282
1145
  console.error(`ERROR: ${creds.error}`);
1283
1146
  process.exit(1);
1284
1147
  }
1285
- const useRouter = args.router || isRouterConfigured();
1286
1148
  const repo = resolveRepo(args.repo);
1287
1149
  const config = args.config ? resolveConfig(args.config) : void 0;
1288
- ensureDeliverables(repo.hostPath);
1289
1150
  const workspacesDir = getWorkspacesDir();
1290
1151
  fs.mkdirSync(workspacesDir, { recursive: true });
1291
1152
  fs.chmodSync(workspacesDir, 511);
1292
- if (useRouter) {
1293
- process.env.ANTHROPIC_BASE_URL = "http://shannon-router:3456";
1294
- process.env.ANTHROPIC_AUTH_TOKEN = "shannon-router-key";
1295
- }
1296
1153
  ensureImage(args.version);
1297
- await ensureInfra(useRouter);
1154
+ await ensureInfra();
1298
1155
  const suffix = randomSuffix();
1299
1156
  const taskQueue = `shannon-${suffix}`;
1300
1157
  const containerName = `shannon-worker-${suffix}`;
1301
1158
  const workspace = args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, "-")}_shannon-${Date.now()}`;
1302
- const credentialsDir = getCredentialsDir();
1159
+ const workspacePath = path.join(workspacesDir, workspace);
1160
+ fs.mkdirSync(workspacePath, { recursive: true });
1161
+ fs.chmodSync(workspacePath, 511);
1162
+ for (const dir of [
1163
+ "deliverables",
1164
+ "scratchpad",
1165
+ ".playwright-cli"
1166
+ ]) {
1167
+ const dirPath = path.join(workspacePath, dir);
1168
+ fs.mkdirSync(dirPath, { recursive: true });
1169
+ fs.chmodSync(dirPath, 511);
1170
+ }
1171
+ const shannonDir = path.join(repo.hostPath, ".shannon");
1172
+ for (const dir of [
1173
+ "deliverables",
1174
+ "scratchpad",
1175
+ ".playwright-cli"
1176
+ ]) fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
1303
1177
  const credentialsPath = getCredentialsPath();
1304
- const hasCredentials = !credentialsDir && fs.existsSync(credentialsPath);
1178
+ const hasCredentials = fs.existsSync(credentialsPath);
1179
+ if (hasCredentials) process.env.GOOGLE_APPLICATION_CREDENTIALS = "/app/credentials/google-sa-key.json";
1305
1180
  const outputDir = args.output ? path.resolve(args.output) : void 0;
1306
1181
  if (outputDir) fs.mkdirSync(outputDir, { recursive: true });
1307
1182
  const promptsDir = isLocal() ? path.resolve("apps/worker/prompts") : void 0;
1308
1183
  displaySplash(isLocal() ? void 0 : args.version);
1309
- spawnWorker({
1184
+ const proc = spawnWorker({
1310
1185
  version: args.version,
1311
1186
  url: args.url,
1312
1187
  repo,
@@ -1315,16 +1190,20 @@ async function start(args) {
1315
1190
  containerName,
1316
1191
  envFlags: buildEnvFlags(),
1317
1192
  ...config && { config },
1318
- ...credentialsDir && { credentialsDir },
1319
1193
  ...hasCredentials && { credentials: credentialsPath },
1320
1194
  ...promptsDir && { promptsDir },
1321
1195
  ...outputDir && { outputDir },
1322
- ...workspace && { workspace },
1323
- ...args.pipelineTesting && { pipelineTesting: true }
1324
- }).on("error", (err) => {
1325
- console.error(`Failed to start worker: ${err.message}`);
1326
- process.exit(1);
1196
+ workspace,
1197
+ ...args.pipelineTesting && { pipelineTesting: true },
1198
+ ...args.debug && { debug: true }
1327
1199
  });
1200
+ if (await new Promise((resolve) => {
1201
+ proc.once("exit", (code) => resolve(code ?? 1));
1202
+ proc.once("error", (err) => {
1203
+ console.error(`Failed to start worker: ${err.message}`);
1204
+ resolve(1);
1205
+ });
1206
+ }) !== 0) process.exit(1);
1328
1207
  const sessionJson = path.join(workspacesDir, workspace, "session.json");
1329
1208
  const isResume = fs.existsSync(sessionJson);
1330
1209
  let initialResumeCount = 0;
@@ -1351,7 +1230,7 @@ async function start(args) {
1351
1230
  started = true;
1352
1231
  workflowId = resumeAttempts.at(-1)?.workflowId ?? session.session?.originalWorkflowId ?? "";
1353
1232
  process.stdout.write("\r\x1B[K");
1354
- printInfo(args, useRouter, workspace, workflowId, repo.hostPath, workspacesDir);
1233
+ printInfo(args, workspace, workflowId, repo.hostPath, workspacesDir);
1355
1234
  return;
1356
1235
  }
1357
1236
  } catch {}
@@ -1366,6 +1245,7 @@ async function start(args) {
1366
1245
  try {
1367
1246
  execFileSync("docker", ["stop", containerName], { stdio: "pipe" });
1368
1247
  } catch {}
1248
+ if (args.debug) printDebugHint(containerName);
1369
1249
  };
1370
1250
  process.on("SIGINT", () => {
1371
1251
  cleanup();
@@ -1377,7 +1257,14 @@ async function start(args) {
1377
1257
  });
1378
1258
  process.on("exit", cleanup);
1379
1259
  }
1380
- function printInfo(args, routerActive, workspace, workflowId, repoPath, workspacesDir) {
1260
+ function printDebugHint(containerName) {
1261
+ console.log("");
1262
+ console.log(` Worker container preserved: ${containerName}`);
1263
+ console.log(` Inspect logs: docker logs ${containerName}`);
1264
+ console.log(` Remove: docker rm ${containerName}`);
1265
+ console.log("");
1266
+ }
1267
+ function printInfo(args, workspace, workflowId, repoPath, workspacesDir) {
1381
1268
  const logsCmd = isLocal() ? `./shannon logs ${workspace}` : `npx @keygraph/shannon logs ${workspace}`;
1382
1269
  const reportsPath = path.join(workspacesDir, workspace);
1383
1270
  console.log(` Target: ${args.url}`);
@@ -1385,7 +1272,6 @@ function printInfo(args, routerActive, workspace, workflowId, repoPath, workspac
1385
1272
  console.log(` Workspace: ${workspace}`);
1386
1273
  if (args.config) console.log(` Config: ${path.resolve(args.config)}`);
1387
1274
  if (args.pipelineTesting) console.log(" Mode: Pipeline Testing");
1388
- if (routerActive) console.log(" Router: Enabled");
1389
1275
  console.log("");
1390
1276
  console.log(" Monitor:");
1391
1277
  if (workflowId) console.log(` Web UI: http://localhost:8233/namespaces/default/workflows/${workflowId}`);
@@ -1533,7 +1419,7 @@ Options for 'start':
1533
1419
  -o, --output <path> Copy deliverables to this directory after run
1534
1420
  -w, --workspace <name> Named workspace (auto-resumes if exists)
1535
1421
  --pipeline-testing Use minimal prompts for fast testing
1536
- --router Route requests through claude-code-router
1422
+ --debug Preserve worker container after exit for log inspection
1537
1423
 
1538
1424
  Examples:
1539
1425
  ${prefix} start -u https://example.com -r ${mode === "local" ? "my-repo" : "./my-repo"}
@@ -1553,7 +1439,7 @@ function parseStartArgs(argv) {
1553
1439
  let workspace;
1554
1440
  let output;
1555
1441
  let pipelineTesting = false;
1556
- let router = false;
1442
+ let debug = false;
1557
1443
  for (let i = 0; i < argv.length; i++) {
1558
1444
  const arg = argv[i];
1559
1445
  const next = argv[i + 1];
@@ -1596,8 +1482,8 @@ function parseStartArgs(argv) {
1596
1482
  case "--pipeline-testing":
1597
1483
  pipelineTesting = true;
1598
1484
  break;
1599
- case "--router":
1600
- router = true;
1485
+ case "--debug":
1486
+ debug = true;
1601
1487
  break;
1602
1488
  default:
1603
1489
  console.error(`Unknown option: ${arg}`);
@@ -1614,7 +1500,7 @@ function parseStartArgs(argv) {
1614
1500
  url,
1615
1501
  repo,
1616
1502
  pipelineTesting,
1617
- router,
1503
+ debug,
1618
1504
  ...config && { config },
1619
1505
  ...workspace && { workspace },
1620
1506
  ...output && { output }
package/infra/compose.yml CHANGED
@@ -19,32 +19,5 @@ services:
19
19
  retries: 10
20
20
  start_period: 30s
21
21
 
22
- router:
23
- image: node:20-slim
24
- container_name: shannon-router
25
- profiles: ["router"]
26
- command: >
27
- sh -c "apt-get update && apt-get install -y gettext-base &&
28
- npm install -g @musistudio/claude-code-router &&
29
- mkdir -p /root/.claude-code-router &&
30
- envsubst < /config/router-config.json > /root/.claude-code-router/config.json &&
31
- ccr start"
32
- ports:
33
- - "127.0.0.1:3456:3456"
34
- volumes:
35
- - ./router-config.json:/config/router-config.json:ro
36
- environment:
37
- - HOST=0.0.0.0
38
- - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
39
- - OPENAI_API_KEY=${OPENAI_API_KEY:-}
40
- - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
41
- - ROUTER_DEFAULT=${ROUTER_DEFAULT:-openai,gpt-4o}
42
- healthcheck:
43
- test: ["CMD", "node", "-e", "require('http').get('http://localhost:3456/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
44
- interval: 10s
45
- timeout: 5s
46
- retries: 5
47
- start_period: 30s
48
-
49
22
  volumes:
50
23
  temporal-data:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keygraph/shannon",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.1.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",
@@ -15,7 +15,7 @@
15
15
  "@clack/prompts": "^1.1.0",
16
16
  "chokidar": "^5.0.0",
17
17
  "dotenv": "^17.3.1",
18
- "smol-toml": "^1.6.0"
18
+ "smol-toml": "^1.6.1"
19
19
  },
20
20
  "keywords": [
21
21
  "security",
@@ -40,7 +40,7 @@
40
40
  "node": ">=18"
41
41
  },
42
42
  "devDependencies": {
43
- "tsdown": "^0.21.2"
43
+ "tsdown": "^0.21.5"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsdown",
@@ -1,31 +0,0 @@
1
- {
2
- "HOST": "0.0.0.0",
3
- "APIKEY": "shannon-router-key",
4
- "LOG": true,
5
- "LOG_LEVEL": "info",
6
- "NON_INTERACTIVE_MODE": true,
7
- "API_TIMEOUT_MS": 600000,
8
- "Providers": [
9
- {
10
- "name": "openai",
11
- "api_base_url": "https://api.openai.com/v1/chat/completions",
12
- "api_key": "$OPENAI_API_KEY",
13
- "models": ["gpt-5.2", "gpt-5-mini"],
14
- "transformer": {
15
- "use": [["maxcompletiontokens", { "max_completion_tokens": 16384 }]]
16
- }
17
- },
18
- {
19
- "name": "openrouter",
20
- "api_base_url": "https://openrouter.ai/api/v1/chat/completions",
21
- "api_key": "$OPENROUTER_API_KEY",
22
- "models": ["google/gemini-3-flash-preview"],
23
- "transformer": {
24
- "use": ["openrouter"]
25
- }
26
- }
27
- ],
28
- "Router": {
29
- "default": "$ROUTER_DEFAULT"
30
- }
31
- }