@ezlkg/shn 1.0.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.
Files changed (51) hide show
  1. package/dist/commands/logs.d.ts +5 -0
  2. package/dist/commands/logs.d.ts.map +1 -0
  3. package/dist/commands/logs.js +53 -0
  4. package/dist/commands/logs.js.map +1 -0
  5. package/dist/commands/start.d.ts +14 -0
  6. package/dist/commands/start.d.ts.map +1 -0
  7. package/dist/commands/start.js +146 -0
  8. package/dist/commands/start.js.map +1 -0
  9. package/dist/commands/status.d.ts +5 -0
  10. package/dist/commands/status.d.ts.map +1 -0
  11. package/dist/commands/status.js +23 -0
  12. package/dist/commands/status.js.map +1 -0
  13. package/dist/commands/stop.d.ts +5 -0
  14. package/dist/commands/stop.d.ts.map +1 -0
  15. package/dist/commands/stop.js +9 -0
  16. package/dist/commands/stop.js.map +1 -0
  17. package/dist/commands/update.d.ts +5 -0
  18. package/dist/commands/update.d.ts.map +1 -0
  19. package/dist/commands/update.js +9 -0
  20. package/dist/commands/update.js.map +1 -0
  21. package/dist/commands/workspaces.d.ts +5 -0
  22. package/dist/commands/workspaces.d.ts.map +1 -0
  23. package/dist/commands/workspaces.js +19 -0
  24. package/dist/commands/workspaces.js.map +1 -0
  25. package/dist/docker.d.ts +58 -0
  26. package/dist/docker.d.ts.map +1 -0
  27. package/dist/docker.js +159 -0
  28. package/dist/docker.js.map +1 -0
  29. package/dist/env.d.ts +26 -0
  30. package/dist/env.d.ts.map +1 -0
  31. package/dist/env.js +119 -0
  32. package/dist/env.js.map +1 -0
  33. package/dist/home.d.ts +16 -0
  34. package/dist/home.d.ts.map +1 -0
  35. package/dist/home.js +52 -0
  36. package/dist/home.js.map +1 -0
  37. package/dist/index.d.ts +17 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +167 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/paths.d.ts +25 -0
  42. package/dist/paths.d.ts.map +1 -0
  43. package/dist/paths.js +57 -0
  44. package/dist/paths.js.map +1 -0
  45. package/dist/splash.d.ts +5 -0
  46. package/dist/splash.d.ts.map +1 -0
  47. package/dist/splash.js +38 -0
  48. package/dist/splash.js.map +1 -0
  49. package/infra/compose.yml +50 -0
  50. package/infra/router-config.json +33 -0
  51. package/package.json +42 -0
package/dist/env.js ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Environment variable loading and credential validation.
3
+ *
4
+ * Loads ~/.shannon/.env and validates that at least one authentication
5
+ * method is configured (API key, OAuth, Bedrock, Vertex, or router).
6
+ */
7
+ import dotenv from 'dotenv';
8
+ import { getEnvFile } from './home.js';
9
+ /** Environment variables forwarded to worker containers. */
10
+ const FORWARD_VARS = [
11
+ 'ANTHROPIC_API_KEY',
12
+ 'ANTHROPIC_BASE_URL',
13
+ 'ANTHROPIC_AUTH_TOKEN',
14
+ 'ROUTER_DEFAULT',
15
+ 'CLAUDE_CODE_OAUTH_TOKEN',
16
+ 'CLAUDE_CODE_USE_BEDROCK',
17
+ 'AWS_REGION',
18
+ 'AWS_BEARER_TOKEN_BEDROCK',
19
+ 'CLAUDE_CODE_USE_VERTEX',
20
+ 'CLOUD_ML_REGION',
21
+ 'ANTHROPIC_VERTEX_PROJECT_ID',
22
+ 'GOOGLE_APPLICATION_CREDENTIALS',
23
+ 'ANTHROPIC_SMALL_MODEL',
24
+ 'ANTHROPIC_MEDIUM_MODEL',
25
+ 'ANTHROPIC_LARGE_MODEL',
26
+ 'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
27
+ ];
28
+ /**
29
+ * Load ~/.shannon/.env into process.env.
30
+ * Existing env vars take precedence (standard dotenv behavior).
31
+ */
32
+ export function loadEnv() {
33
+ dotenv.config({ path: getEnvFile() });
34
+ }
35
+ /**
36
+ * Build `-e KEY=VALUE` flags for docker run, only for set variables.
37
+ */
38
+ export function buildEnvFlags() {
39
+ const flags = ['-e', 'TEMPORAL_ADDRESS=shannon-temporal:7233'];
40
+ for (const key of FORWARD_VARS) {
41
+ const value = process.env[key];
42
+ if (value) {
43
+ flags.push('-e', `${key}=${value}`);
44
+ }
45
+ }
46
+ return flags;
47
+ }
48
+ /**
49
+ * Validate that at least one authentication method is configured.
50
+ */
51
+ export function validateCredentials(useRouter) {
52
+ if (process.env.ANTHROPIC_API_KEY) {
53
+ return { valid: true, mode: 'api-key' };
54
+ }
55
+ if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
56
+ return { valid: true, mode: 'oauth' };
57
+ }
58
+ if (process.env.CLAUDE_CODE_USE_BEDROCK === '1') {
59
+ const missing = [];
60
+ if (!process.env.AWS_REGION)
61
+ missing.push('AWS_REGION');
62
+ if (!process.env.AWS_BEARER_TOKEN_BEDROCK)
63
+ missing.push('AWS_BEARER_TOKEN_BEDROCK');
64
+ if (!process.env.ANTHROPIC_SMALL_MODEL)
65
+ missing.push('ANTHROPIC_SMALL_MODEL');
66
+ if (!process.env.ANTHROPIC_MEDIUM_MODEL)
67
+ missing.push('ANTHROPIC_MEDIUM_MODEL');
68
+ if (!process.env.ANTHROPIC_LARGE_MODEL)
69
+ missing.push('ANTHROPIC_LARGE_MODEL');
70
+ if (missing.length > 0) {
71
+ return {
72
+ valid: false,
73
+ mode: 'bedrock',
74
+ error: `Bedrock mode requires: ${missing.join(', ')}`,
75
+ };
76
+ }
77
+ return { valid: true, mode: 'bedrock' };
78
+ }
79
+ if (process.env.CLAUDE_CODE_USE_VERTEX === '1') {
80
+ const missing = [];
81
+ if (!process.env.CLOUD_ML_REGION)
82
+ missing.push('CLOUD_ML_REGION');
83
+ if (!process.env.ANTHROPIC_VERTEX_PROJECT_ID)
84
+ missing.push('ANTHROPIC_VERTEX_PROJECT_ID');
85
+ if (!process.env.ANTHROPIC_SMALL_MODEL)
86
+ missing.push('ANTHROPIC_SMALL_MODEL');
87
+ if (!process.env.ANTHROPIC_MEDIUM_MODEL)
88
+ missing.push('ANTHROPIC_MEDIUM_MODEL');
89
+ if (!process.env.ANTHROPIC_LARGE_MODEL)
90
+ missing.push('ANTHROPIC_LARGE_MODEL');
91
+ if (missing.length > 0) {
92
+ return {
93
+ valid: false,
94
+ mode: 'vertex',
95
+ error: `Vertex AI mode requires: ${missing.join(', ')}`,
96
+ };
97
+ }
98
+ if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
99
+ return {
100
+ valid: false,
101
+ mode: 'vertex',
102
+ error: 'Vertex AI mode requires GOOGLE_APPLICATION_CREDENTIALS',
103
+ };
104
+ }
105
+ return { valid: true, mode: 'vertex' };
106
+ }
107
+ if (useRouter && (process.env.OPENAI_API_KEY || process.env.OPENROUTER_API_KEY)) {
108
+ // Set a placeholder so the worker doesn't reject the missing key
109
+ process.env.ANTHROPIC_API_KEY = 'router-mode';
110
+ return { valid: true, mode: 'router' };
111
+ }
112
+ return {
113
+ valid: false,
114
+ mode: 'api-key',
115
+ error: 'No credentials found. Set ANTHROPIC_API_KEY in ~/.shannon/.env\n' +
116
+ ' (or use CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1)',
117
+ };
118
+ }
119
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,4DAA4D;AAC5D,MAAM,YAAY,GAAG;IACnB,mBAAmB;IACnB,oBAAoB;IACpB,sBAAsB;IACtB,gBAAgB;IAChB,yBAAyB;IACzB,yBAAyB;IACzB,YAAY;IACZ,0BAA0B;IAC1B,wBAAwB;IACxB,iBAAiB;IACjB,6BAA6B;IAC7B,gCAAgC;IAChC,uBAAuB;IACvB,wBAAwB;IACxB,uBAAuB;IACvB,+BAA+B;CACvB,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAa,CAAC,IAAI,EAAE,wCAAwC,CAAC,CAAC;IAEzE,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAQD;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAkB;IACpD,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,GAAG,EAAE,CAAC;QAChD,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU;YAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB;YAAE,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB;YAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC9E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB;YAAE,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAChF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB;YAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC9E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACtD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,GAAG,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe;YAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B;YAAE,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC1F,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB;YAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC9E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB;YAAE,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAChF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB;YAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC9E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,4BAA4B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACxD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,CAAC;YAChD,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,wDAAwD;aAChE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAChF,iEAAiE;QACjE,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,aAAa,CAAC;QAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,SAAS;QACf,KAAK,EACH,kEAAkE;YAClE,yFAAyF;KAC5F,CAAC;AACJ,CAAC"}
package/dist/home.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shannon home directory management (~/.shannon/).
3
+ *
4
+ * Handles initialization, directory creation, and permission checks
5
+ * for the global state directory used by the npx CLI.
6
+ */
7
+ export declare function getHome(): string;
8
+ export declare function getWorkspacesDir(): string;
9
+ export declare function getEnvFile(): string;
10
+ export declare function getCredentialsPath(): string;
11
+ /**
12
+ * Initialize ~/.shannon/ directory structure.
13
+ * Creates directories if missing, warns on overly permissive permissions.
14
+ */
15
+ export declare function initHome(): void;
16
+ //# sourceMappingURL=home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../src/home.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,IAAI,CAO/B"}
package/dist/home.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Shannon home directory management (~/.shannon/).
3
+ *
4
+ * Handles initialization, directory creation, and permission checks
5
+ * for the global state directory used by the npx CLI.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ const SHANNON_HOME = path.join(os.homedir(), '.shannon');
11
+ const WORKSPACES_DIR = path.join(SHANNON_HOME, 'workspaces');
12
+ const ENV_FILE = path.join(SHANNON_HOME, '.env');
13
+ const CREDENTIALS_DIR = SHANNON_HOME;
14
+ export function getHome() {
15
+ return SHANNON_HOME;
16
+ }
17
+ export function getWorkspacesDir() {
18
+ return WORKSPACES_DIR;
19
+ }
20
+ export function getEnvFile() {
21
+ return ENV_FILE;
22
+ }
23
+ export function getCredentialsPath() {
24
+ return path.join(CREDENTIALS_DIR, 'google-sa-vertex-key.json');
25
+ }
26
+ /**
27
+ * Initialize ~/.shannon/ directory structure.
28
+ * Creates directories if missing, warns on overly permissive permissions.
29
+ */
30
+ export function initHome() {
31
+ // 1. Create directories
32
+ fs.mkdirSync(WORKSPACES_DIR, { recursive: true, mode: 0o700 });
33
+ // 2. Check permissions on sensitive files
34
+ checkPermissions(ENV_FILE, 0o600, '.env (contains API keys)');
35
+ checkPermissions(getCredentialsPath(), 0o600, 'GCP service account key');
36
+ }
37
+ function checkPermissions(filePath, expectedMode, label) {
38
+ try {
39
+ const stats = fs.statSync(filePath);
40
+ const actualMode = stats.mode & 0o777;
41
+ if (actualMode > expectedMode) {
42
+ const actual = '0' + actualMode.toString(8);
43
+ const expected = '0' + expectedMode.toString(8);
44
+ console.warn(`WARNING: ${label} has permissions ${actual}, expected ${expected} or stricter.`);
45
+ console.warn(` Fix with: chmod ${expected} ${filePath}`);
46
+ }
47
+ }
48
+ catch {
49
+ // File doesn't exist yet — that's fine
50
+ }
51
+ }
52
+ //# sourceMappingURL=home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../src/home.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACzD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACjD,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,MAAM,UAAU,OAAO;IACrB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,2BAA2B,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACtB,wBAAwB;IACxB,EAAE,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAE/D,0CAA0C;IAC1C,gBAAgB,CAAC,QAAQ,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC;IAC9D,gBAAgB,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,yBAAyB,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB,EAAE,YAAoB,EAAE,KAAa;IAC7E,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;QACtC,IAAI,UAAU,GAAG,YAAY,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,QAAQ,GAAG,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAChD,OAAO,CAAC,IAAI,CACV,YAAY,KAAK,oBAAoB,MAAM,cAAc,QAAQ,eAAe,CACjF,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,qBAAqB,QAAQ,IAAI,QAAQ,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shannon CLI — AI Penetration Testing Framework
4
+ *
5
+ * Zero-install CLI that orchestrates Docker containers for security scans.
6
+ * Uses pre-built images from Docker Hub and stores state in ~/.shannon/.
7
+ *
8
+ * Usage:
9
+ * npx @ezlkg/shn start --url <url> --repo <path> [options]
10
+ * npx @ezlkg/shn stop [--clean]
11
+ * npx @ezlkg/shn workspaces
12
+ * npx @ezlkg/shn logs <workspace>
13
+ * npx @ezlkg/shn status
14
+ * npx @ezlkg/shn update
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;GAaG"}
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shannon CLI — AI Penetration Testing Framework
4
+ *
5
+ * Zero-install CLI that orchestrates Docker containers for security scans.
6
+ * Uses pre-built images from Docker Hub and stores state in ~/.shannon/.
7
+ *
8
+ * Usage:
9
+ * npx @ezlkg/shn start --url <url> --repo <path> [options]
10
+ * npx @ezlkg/shn stop [--clean]
11
+ * npx @ezlkg/shn workspaces
12
+ * npx @ezlkg/shn logs <workspace>
13
+ * npx @ezlkg/shn status
14
+ * npx @ezlkg/shn update
15
+ */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { start } from './commands/start.js';
20
+ import { stop } from './commands/stop.js';
21
+ import { logs } from './commands/logs.js';
22
+ import { workspaces } from './commands/workspaces.js';
23
+ import { status } from './commands/status.js';
24
+ import { update } from './commands/update.js';
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+ function getVersion() {
27
+ try {
28
+ const pkgPath = path.join(__dirname, '..', 'package.json');
29
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
30
+ return pkg.version || '1.0.0';
31
+ }
32
+ catch {
33
+ return '1.0.0';
34
+ }
35
+ }
36
+ function showHelp() {
37
+ console.log(`
38
+ Shannon - AI Penetration Testing Framework
39
+
40
+ Usage:
41
+ shannon start --url <url> --repo <path> [options] Start a pentest scan
42
+ shannon stop [--clean] Stop all containers
43
+ shannon workspaces List all workspaces
44
+ shannon logs <workspace> Tail workflow log
45
+ shannon status Show running workers
46
+ shannon update Pull latest image
47
+ shannon help Show this help
48
+
49
+ Options for 'start':
50
+ -u, --url <url> Target URL (required)
51
+ -r, --repo <path> Repository path (required)
52
+ -c, --config <path> Configuration file (YAML)
53
+ -w, --workspace <name> Named workspace (auto-resumes if exists)
54
+ --pipeline-testing Use minimal prompts for fast testing
55
+ --router Route requests through claude-code-router
56
+
57
+ Examples:
58
+ npx @ezlkg/shn start -u https://example.com -r ./my-repo
59
+ npx @ezlkg/shn start -u https://example.com -r /path/to/repo -c config.yaml -w q1-audit
60
+ npx @ezlkg/shn logs q1-audit
61
+ npx @ezlkg/shn stop --clean
62
+
63
+ State directory: ~/.shannon/
64
+ Monitor workflows at http://localhost:8233
65
+ `);
66
+ }
67
+ function parseStartArgs(argv) {
68
+ let url = '';
69
+ let repo = '';
70
+ let config;
71
+ let workspace;
72
+ let pipelineTesting = false;
73
+ let router = false;
74
+ for (let i = 0; i < argv.length; i++) {
75
+ const arg = argv[i];
76
+ const next = argv[i + 1];
77
+ switch (arg) {
78
+ case '-u':
79
+ case '--url':
80
+ if (next && !next.startsWith('-')) {
81
+ url = next;
82
+ i++;
83
+ }
84
+ break;
85
+ case '-r':
86
+ case '--repo':
87
+ if (next && !next.startsWith('-')) {
88
+ repo = next;
89
+ i++;
90
+ }
91
+ break;
92
+ case '-c':
93
+ case '--config':
94
+ if (next && !next.startsWith('-')) {
95
+ config = next;
96
+ i++;
97
+ }
98
+ break;
99
+ case '-w':
100
+ case '--workspace':
101
+ if (next && !next.startsWith('-')) {
102
+ workspace = next;
103
+ i++;
104
+ }
105
+ break;
106
+ case '--pipeline-testing':
107
+ pipelineTesting = true;
108
+ break;
109
+ case '--router':
110
+ router = true;
111
+ break;
112
+ default:
113
+ console.error(`Unknown option: ${arg}`);
114
+ console.error('Run "shannon help" for usage');
115
+ process.exit(1);
116
+ }
117
+ }
118
+ if (!url || !repo) {
119
+ console.error('ERROR: --url and --repo are required');
120
+ console.error('Usage: shannon start -u <url> -r <path>');
121
+ process.exit(1);
122
+ }
123
+ return { url, repo, pipelineTesting, router, ...(config && { config }), ...(workspace && { workspace }) };
124
+ }
125
+ // === Main Dispatch ===
126
+ const args = process.argv.slice(2);
127
+ const command = args[0];
128
+ switch (command) {
129
+ case 'start': {
130
+ const parsed = parseStartArgs(args.slice(1));
131
+ start({ ...parsed, version: getVersion() });
132
+ break;
133
+ }
134
+ case 'stop':
135
+ stop(args.includes('--clean'));
136
+ break;
137
+ case 'logs': {
138
+ const workspaceId = args[1];
139
+ if (!workspaceId) {
140
+ console.error('ERROR: Workspace ID is required');
141
+ console.error('Usage: shannon logs <workspace>');
142
+ process.exit(1);
143
+ }
144
+ logs(workspaceId);
145
+ break;
146
+ }
147
+ case 'workspaces':
148
+ workspaces();
149
+ break;
150
+ case 'status':
151
+ status();
152
+ break;
153
+ case 'update':
154
+ update();
155
+ break;
156
+ case 'help':
157
+ case '--help':
158
+ case '-h':
159
+ case undefined:
160
+ showHelp();
161
+ break;
162
+ default:
163
+ console.error(`Unknown command: ${command}`);
164
+ showHelp();
165
+ process.exit(1);
166
+ }
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Path resolution for --repo and --config arguments.
3
+ *
4
+ * Resolves user-provided paths to absolute host paths and generates
5
+ * corresponding container mount paths.
6
+ */
7
+ export interface MountPair {
8
+ hostPath: string;
9
+ containerPath: string;
10
+ }
11
+ /**
12
+ * Resolve --repo to absolute path and container mount.
13
+ * Unlike the bash CLI, there is no ./repos/ convention — all paths
14
+ * are resolved against CWD.
15
+ */
16
+ export declare function resolveRepo(repoArg: string): MountPair;
17
+ /**
18
+ * Resolve --config to absolute path and container mount.
19
+ */
20
+ export declare function resolveConfig(configArg: string): MountPair;
21
+ /**
22
+ * Ensure the deliverables directory exists and is writable by the container user.
23
+ */
24
+ export declare function ensureDeliverables(repoHostPath: string): void;
25
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAkBtD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAkB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAI7D"}
package/dist/paths.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Path resolution for --repo and --config arguments.
3
+ *
4
+ * Resolves user-provided paths to absolute host paths and generates
5
+ * corresponding container mount paths.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ /**
10
+ * Resolve --repo to absolute path and container mount.
11
+ * Unlike the bash CLI, there is no ./repos/ convention — all paths
12
+ * are resolved against CWD.
13
+ */
14
+ export function resolveRepo(repoArg) {
15
+ const hostPath = path.resolve(repoArg);
16
+ if (!fs.existsSync(hostPath)) {
17
+ console.error(`ERROR: Repository not found: ${hostPath}`);
18
+ process.exit(1);
19
+ }
20
+ if (!fs.statSync(hostPath).isDirectory()) {
21
+ console.error(`ERROR: Not a directory: ${hostPath}`);
22
+ process.exit(1);
23
+ }
24
+ const basename = path.basename(hostPath);
25
+ return {
26
+ hostPath,
27
+ containerPath: `/repos/${basename}`,
28
+ };
29
+ }
30
+ /**
31
+ * Resolve --config to absolute path and container mount.
32
+ */
33
+ export function resolveConfig(configArg) {
34
+ const hostPath = path.resolve(configArg);
35
+ if (!fs.existsSync(hostPath)) {
36
+ console.error(`ERROR: Config file not found: ${hostPath}`);
37
+ process.exit(1);
38
+ }
39
+ if (!fs.statSync(hostPath).isFile()) {
40
+ console.error(`ERROR: Not a file: ${hostPath}`);
41
+ process.exit(1);
42
+ }
43
+ const basename = path.basename(hostPath);
44
+ return {
45
+ hostPath,
46
+ containerPath: `/app/configs/${basename}`,
47
+ };
48
+ }
49
+ /**
50
+ * Ensure the deliverables directory exists and is writable by the container user.
51
+ */
52
+ export function ensureDeliverables(repoHostPath) {
53
+ const deliverables = path.join(repoHostPath, 'deliverables');
54
+ fs.mkdirSync(deliverables, { recursive: true });
55
+ fs.chmodSync(deliverables, 0o777);
56
+ }
57
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO;QACL,QAAQ;QACR,aAAa,EAAE,UAAU,QAAQ,EAAE;KACpC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,iCAAiC,QAAQ,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO;QACL,QAAQ;QACR,aAAa,EAAE,gBAAgB,QAAQ,EAAE;KAC1C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,YAAoB;IACrD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IAC7D,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Splash screen display — pure terminal output, no npm dependencies.
3
+ */
4
+ export declare function displaySplash(version: string): void;
5
+ //# sourceMappingURL=splash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splash.d.ts","sourceRoot":"","sources":["../src/splash.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA4CnD"}
package/dist/splash.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Splash screen display — pure terminal output, no npm dependencies.
3
+ */
4
+ export function displaySplash(version) {
5
+ const GOLD = '\x1b[38;2;244;197;66m';
6
+ const CYAN = '\x1b[36;1m';
7
+ const WHITE = '\x1b[1;37m';
8
+ const GRAY = '\x1b[0;37m';
9
+ const YELLOW = '\x1b[1;33m';
10
+ const RESET = '\x1b[0m';
11
+ const B = `${CYAN}\u2551${RESET}`;
12
+ const S67 = ' '.repeat(67);
13
+ const HR = '\u2550'.repeat(67);
14
+ const lines = [
15
+ '',
16
+ ` ${CYAN}\u2554${HR}\u2557${RESET}`,
17
+ ` ${B}${S67}${B}`,
18
+ ` ${B} ${GOLD}\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557${RESET} ${B}`,
19
+ ` ${B} ${GOLD}\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551${RESET} ${B}`,
20
+ ` ${B} ${GOLD}\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551${RESET} ${B}`,
21
+ ` ${B} ${GOLD}\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551${RESET} ${B}`,
22
+ ` ${B} ${GOLD}\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551${RESET} ${B}`,
23
+ ` ${B} ${GOLD}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D${RESET} ${B}`,
24
+ ` ${B}${S67}${B}`,
25
+ ` ${B} ${CYAN}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${RESET} ${B}`,
26
+ ` ${B} ${CYAN}\u2551${RESET} ${WHITE}AI Penetration Testing Framework${RESET} ${CYAN}\u2551${RESET} ${B}`,
27
+ ` ${B} ${CYAN}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${RESET} ${B}`,
28
+ ` ${B}${S67}${B}`,
29
+ ];
30
+ // Center the version string
31
+ const verStr = `v${version}`;
32
+ const verPadLeft = Math.floor((67 - verStr.length) / 2);
33
+ const verPadRight = 67 - verStr.length - verPadLeft;
34
+ lines.push(` ${B}${' '.repeat(verPadLeft)}${GRAY}${verStr}${RESET}${' '.repeat(verPadRight)}${B}`);
35
+ lines.push(` ${B}${S67}${B}`, ` ${B} ${YELLOW}\uD83D\uDD10 DEFENSIVE SECURITY ONLY \uD83D\uDD10${RESET} ${B}`, ` ${B}${S67}${B}`, ` ${CYAN}\u255A${HR}\u255D${RESET}`, '');
36
+ console.log(lines.join('\n'));
37
+ }
38
+ //# sourceMappingURL=splash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splash.js","sourceRoot":"","sources":["../src/splash.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,uBAAuB,CAAC;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,MAAM,KAAK,GAAG,YAAY,CAAC;IAC3B,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,MAAM,MAAM,GAAG,YAAY,CAAC;IAC5B,MAAM,KAAK,GAAG,SAAS,CAAC;IAExB,MAAM,CAAC,GAAG,GAAG,IAAI,SAAS,KAAK,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAE/B,MAAM,KAAK,GAAG;QACZ,EAAE;QACF,KAAK,IAAI,SAAS,EAAE,SAAS,KAAK,EAAE;QACpC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;QAClB,KAAK,CAAC,KAAK,IAAI,kTAAkT,KAAK,KAAK,CAAC,EAAE;QAC9U,KAAK,CAAC,KAAK,IAAI,qVAAqV,KAAK,KAAK,CAAC,EAAE;QACjX,KAAK,CAAC,KAAK,IAAI,+VAA+V,KAAK,KAAK,CAAC,EAAE;QAC3X,KAAK,CAAC,KAAK,IAAI,8WAA8W,KAAK,KAAK,CAAC,EAAE;QAC1Y,KAAK,CAAC,KAAK,IAAI,0VAA0V,KAAK,KAAK,CAAC,EAAE;QACtX,KAAK,CAAC,KAAK,IAAI,iUAAiU,KAAK,KAAK,CAAC,EAAE;QAC7V,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;QAClB,KAAK,CAAC,iBAAiB,IAAI,uOAAuO,KAAK,kBAAkB,CAAC,EAAE;QAC5R,KAAK,CAAC,iBAAiB,IAAI,SAAS,KAAK,KAAK,KAAK,mCAAmC,KAAK,KAAK,IAAI,SAAS,KAAK,kBAAkB,CAAC,EAAE;QACvI,KAAK,CAAC,iBAAiB,IAAI,uOAAuO,KAAK,kBAAkB,CAAC,EAAE;QAC5R,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;KACnB,CAAC;IAEF,4BAA4B;IAC5B,MAAM,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEpG,KAAK,CAAC,IAAI,CACR,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,EAClB,KAAK,CAAC,uBAAuB,MAAM,oDAAoD,KAAK,qBAAqB,CAAC,EAAE,EACpH,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,EAClB,KAAK,IAAI,SAAS,EAAE,SAAS,KAAK,EAAE,EACpC,EAAE,CACH,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,50 @@
1
+ networks:
2
+ default:
3
+ name: shannon-net
4
+
5
+ services:
6
+ temporal:
7
+ image: temporalio/temporal:latest
8
+ container_name: shannon-temporal
9
+ command: ["server", "start-dev", "--db-filename", "/home/temporal/temporal.db", "--ip", "0.0.0.0"]
10
+ ports:
11
+ - "7233:7233"
12
+ - "8233:8233"
13
+ volumes:
14
+ - temporal-data:/home/temporal
15
+ healthcheck:
16
+ test: ["CMD", "temporal", "operator", "cluster", "health", "--address", "localhost:7233"]
17
+ interval: 10s
18
+ timeout: 5s
19
+ retries: 10
20
+ start_period: 30s
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
+ - "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
+ volumes:
50
+ temporal-data:
@@ -0,0 +1,33 @@
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": [
23
+ "google/gemini-3-flash-preview"
24
+ ],
25
+ "transformer": {
26
+ "use": ["openrouter"]
27
+ }
28
+ }
29
+ ],
30
+ "Router": {
31
+ "default": "$ROUTER_DEFAULT"
32
+ }
33
+ }