@strongkeep/watchdog 0.1.1

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 (67) hide show
  1. package/assets/PINNED_PUBLIC_KEY.txt +13 -0
  2. package/assets/healthcheck-skill/SKILL.md +28 -0
  3. package/assets/skill/SKILL.md +30 -0
  4. package/dist/appliers/add-hook.d.ts +19 -0
  5. package/dist/appliers/add-hook.js +81 -0
  6. package/dist/appliers/add-hook.js.map +1 -0
  7. package/dist/appliers/applier.d.ts +86 -0
  8. package/dist/appliers/applier.js +60 -0
  9. package/dist/appliers/applier.js.map +1 -0
  10. package/dist/appliers/block-domain.d.ts +20 -0
  11. package/dist/appliers/block-domain.js +77 -0
  12. package/dist/appliers/block-domain.js.map +1 -0
  13. package/dist/appliers/fs-guard.d.ts +36 -0
  14. package/dist/appliers/fs-guard.js +109 -0
  15. package/dist/appliers/fs-guard.js.map +1 -0
  16. package/dist/appliers/min-version.d.ts +15 -0
  17. package/dist/appliers/min-version.js +47 -0
  18. package/dist/appliers/min-version.js.map +1 -0
  19. package/dist/appliers/pin-mcp.d.ts +17 -0
  20. package/dist/appliers/pin-mcp.js +99 -0
  21. package/dist/appliers/pin-mcp.js.map +1 -0
  22. package/dist/appliers/set-config.d.ts +12 -0
  23. package/dist/appliers/set-config.js +67 -0
  24. package/dist/appliers/set-config.js.map +1 -0
  25. package/dist/appliers/update-rule-file.d.ts +15 -0
  26. package/dist/appliers/update-rule-file.js +69 -0
  27. package/dist/appliers/update-rule-file.js.map +1 -0
  28. package/dist/autonomy.d.ts +24 -0
  29. package/dist/autonomy.js +35 -0
  30. package/dist/autonomy.js.map +1 -0
  31. package/dist/cli/guard-check.d.ts +16 -0
  32. package/dist/cli/guard-check.js +108 -0
  33. package/dist/cli/guard-check.js.map +1 -0
  34. package/dist/cli/init.d.ts +37 -0
  35. package/dist/cli/init.js +226 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/cli/login.d.ts +32 -0
  38. package/dist/cli/login.js +144 -0
  39. package/dist/cli/login.js.map +1 -0
  40. package/dist/cli/mcp.d.ts +23 -0
  41. package/dist/cli/mcp.js +84 -0
  42. package/dist/cli/mcp.js.map +1 -0
  43. package/dist/cli/poll.d.ts +20 -0
  44. package/dist/cli/poll.js +111 -0
  45. package/dist/cli/poll.js.map +1 -0
  46. package/dist/cli/revert.d.ts +31 -0
  47. package/dist/cli/revert.js +129 -0
  48. package/dist/cli/revert.js.map +1 -0
  49. package/dist/feed-client.d.ts +110 -0
  50. package/dist/feed-client.js +178 -0
  51. package/dist/feed-client.js.map +1 -0
  52. package/dist/fires.d.ts +28 -0
  53. package/dist/fires.js +36 -0
  54. package/dist/fires.js.map +1 -0
  55. package/dist/guard-check.d.ts +39 -0
  56. package/dist/guard-check.js +94 -0
  57. package/dist/guard-check.js.map +1 -0
  58. package/dist/init.d.ts +105 -0
  59. package/dist/init.js +283 -0
  60. package/dist/init.js.map +1 -0
  61. package/dist/pipeline.d.ts +50 -0
  62. package/dist/pipeline.js +110 -0
  63. package/dist/pipeline.js.map +1 -0
  64. package/dist/undo.d.ts +98 -0
  65. package/dist/undo.js +67 -0
  66. package/dist/undo.js.map +1 -0
  67. package/package.json +40 -0
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-init CLI — write the config pack into the subscriber's harness (D6).
4
+ *
5
+ * Usage:
6
+ * strongkeep-init [--harness <dir>] [--kind claude-code|cursor]
7
+ * [--tier advise|propose|auto] [--feed-url <url>] [--dry-run]
8
+ * strongkeep-init --help
9
+ *
10
+ * Token: read from STRONGKEEP_TOKEN or the local login state (~/.strongkeep/token, written by
11
+ * strongkeep-login). NEVER accepted on argv (would leak into shell history) and NEVER printed.
12
+ *
13
+ * Design: arg-parse + orchestration + exit only. All work is in ../init.ts (runInit).
14
+ */
15
+ import process from 'node:process';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { realpathSync } from 'node:fs';
18
+ import { readFile } from 'node:fs/promises';
19
+ import { homedir } from 'node:os';
20
+ import { join } from 'node:path';
21
+ import { AUTONOMY_TIERS } from '@strongkeep/core';
22
+ import { runInit } from '../init.js';
23
+ // ponytail(beta): beta Worker default; revert to 'https://feed.strongkeep.com/v1' at GA.
24
+ const DEFAULT_FEED_URL = 'https://sk-feed-beta.gaurav-016.workers.dev';
25
+ const HELP_TEXT = `strongkeep-init — write the StrongKeep config pack into your OWN agent harness
26
+
27
+ Usage:
28
+ strongkeep-init [--harness <dir>] [--kind claude-code|cursor]
29
+ [--tier advise|propose|auto] [--feed-url <url>] [--dry-run]
30
+ [--no-poll] [--no-healthcheck]
31
+
32
+ Defaults: --harness . | --kind claude-code | --tier propose
33
+ Token: from $STRONGKEEP_TOKEN or ~/.strongkeep/token (run strongkeep-login) — never on argv.
34
+ Optional during the beta: the beta feed is public-read; feed authenticity is still verified
35
+ against the pinned public key.
36
+ After writing the config pack, init runs a first feed poll (skip with --no-poll).
37
+ Idempotent + non-destructive: existing hooks/servers/settings are preserved; edited files are
38
+ backed up. Re-run any time (e.g. to change tier).`;
39
+ /**
40
+ * Parse argv → options. Total; never throws. Token is NOT an argv field (read from env/login state).
41
+ */
42
+ export function parseInitArgs(argv, cwd = process.cwd()) {
43
+ const opts = { harnessRoot: cwd, kind: 'claude-code', tier: 'propose', dryRun: false, showHelp: false, noHealthcheck: false, noPoll: false };
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const arg = argv[i];
46
+ if (arg === '--help' || arg === '-h')
47
+ opts.showHelp = true;
48
+ else if (arg === '--dry-run')
49
+ opts.dryRun = true;
50
+ else if (arg === '--no-healthcheck')
51
+ opts.noHealthcheck = true;
52
+ else if (arg === '--no-poll')
53
+ opts.noPoll = true;
54
+ else if (arg === '--ingest-url') {
55
+ const val = argv[++i];
56
+ if (val === undefined)
57
+ return { ...opts, showHelp: true, error: '--ingest-url requires a URL' };
58
+ opts.ingestUrl = val;
59
+ }
60
+ else if (arg === '--staff') {
61
+ const val = argv[++i];
62
+ if (val === undefined)
63
+ return { ...opts, showHelp: true, error: '--staff requires a label' };
64
+ opts.staffLabel = val;
65
+ }
66
+ else if (arg === '--harness') {
67
+ const val = argv[++i];
68
+ if (val === undefined)
69
+ return { ...opts, showHelp: true, error: '--harness requires a dir' };
70
+ opts.harnessRoot = val;
71
+ }
72
+ else if (arg === '--kind') {
73
+ const val = argv[++i];
74
+ if (val !== 'claude-code' && val !== 'cursor')
75
+ return { ...opts, showHelp: true, error: '--kind must be claude-code or cursor' };
76
+ opts.kind = val;
77
+ }
78
+ else if (arg === '--tier') {
79
+ const val = argv[++i];
80
+ if (val === undefined || !AUTONOMY_TIERS.includes(val)) {
81
+ return { ...opts, showHelp: true, error: `--tier must be one of ${AUTONOMY_TIERS.join('|')}` };
82
+ }
83
+ opts.tier = val;
84
+ }
85
+ else if (arg === '--feed-url') {
86
+ const val = argv[++i];
87
+ if (val === undefined)
88
+ return { ...opts, showHelp: true, error: '--feed-url requires a URL' };
89
+ opts.feedUrl = val;
90
+ }
91
+ else {
92
+ return { ...opts, showHelp: true, error: `unknown argument "${arg}" (the token is never passed on argv)` };
93
+ }
94
+ }
95
+ return opts;
96
+ }
97
+ /** Resolve the per-tenant token from env or login state. Never from argv, never logged. */
98
+ async function resolveToken() {
99
+ const fromEnv = process.env['STRONGKEEP_TOKEN'];
100
+ if (fromEnv !== undefined && fromEnv !== '')
101
+ return fromEnv;
102
+ try {
103
+ return (await readFile(join(homedir(), '.strongkeep', 'token'), 'utf8')).trim();
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ }
109
+ async function main() {
110
+ const opts = parseInitArgs(process.argv.slice(2));
111
+ if (opts.error)
112
+ process.stderr.write(`strongkeep-init: ${opts.error}\n\n`);
113
+ if (opts.showHelp) {
114
+ process.stdout.write(HELP_TEXT + '\n');
115
+ process.exit(opts.error ? 2 : 0);
116
+ }
117
+ const token = await resolveToken();
118
+ // ponytail(beta): token not required while the beta feed is public-read; UNCOMMENT this
119
+ // fail-closed block when beta ends.
120
+ // if (token === undefined) {
121
+ // process.stderr.write(
122
+ // 'strongkeep-init: no token found. Run strongkeep-login first (or set STRONGKEEP_TOKEN).\n',
123
+ // );
124
+ // process.exit(2);
125
+ // }
126
+ if (token === undefined) {
127
+ process.stderr.write('strongkeep-init: no feed token found; continuing (beta feed is public-read).\n');
128
+ }
129
+ try {
130
+ const result = await runInit({
131
+ harnessRoot: opts.harnessRoot,
132
+ kind: opts.kind,
133
+ feedUrl: opts.feedUrl ?? DEFAULT_FEED_URL,
134
+ ...(token !== undefined ? { token } : {}),
135
+ tier: opts.tier,
136
+ dryRun: opts.dryRun,
137
+ ...(opts.ingestUrl !== undefined ? { ingestUrl: opts.ingestUrl } : {}),
138
+ ...(opts.staffLabel !== undefined ? { staffLabel: opts.staffLabel } : {}),
139
+ });
140
+ const lines = result.changes.map((c) => ` ${c.action.padEnd(7)} ${c.path} — ${c.note}`);
141
+ if (result.backups.length > 0)
142
+ lines.push(` backups: ${result.backups.join(', ')}`);
143
+ lines.push(opts.dryRun ? '(dry-run — nothing was written)' : 'protection configured.');
144
+ lines.push('next: run strongkeep-scan for your baseline security report.');
145
+ process.stdout.write(lines.join('\n') + '\n'); // summary never includes the token
146
+ // Beta 2-command flow: run the first feed poll right here (best-effort; init already
147
+ // succeeded, so a poll failure warns and continues — exit stays 0).
148
+ if (!opts.dryRun && !opts.noPoll) {
149
+ await autoFirstPoll(opts.harnessRoot);
150
+ }
151
+ // Cadence Q1: auto first-run of the health check (best-effort; never fails init).
152
+ if (!opts.dryRun && !opts.noHealthcheck) {
153
+ await autoFirstHealthcheck(opts.harnessRoot);
154
+ }
155
+ process.exit(0);
156
+ }
157
+ catch (err) {
158
+ process.stderr.write(`strongkeep-init: ${err instanceof Error ? err.message : String(err)}\n`);
159
+ process.exit(2);
160
+ }
161
+ }
162
+ /**
163
+ * Run the first feed poll after init (beta 2-command flow: init IS the setup, the poll
164
+ * happens inline). Best-effort: spawns the sibling poll bin (same dist/cli directory,
165
+ * realpath-safe URL resolution — the Phase 1 bin-path pattern) with inherited stdio so
166
+ * the user sees the freshness badge. A non-zero poll exit warns but never fails init:
167
+ * the config pack is already written and the hooks will poll again on next tool use.
168
+ */
169
+ async function autoFirstPoll(harnessRoot) {
170
+ const { spawn } = await import('node:child_process');
171
+ const pollBin = fileURLToPath(new URL('./poll.js', import.meta.url));
172
+ await new Promise((resolveDone) => {
173
+ try {
174
+ const child = spawn(process.execPath, [pollBin, '--harness', harnessRoot], { stdio: 'inherit' });
175
+ child.on('error', () => {
176
+ process.stderr.write('strongkeep-init: first feed poll could not start; run strongkeep-poll manually.\n');
177
+ resolveDone();
178
+ });
179
+ child.on('close', (code) => {
180
+ if (code !== 0) {
181
+ process.stderr.write('strongkeep-init: first feed poll did not succeed (init itself is complete; protection hooks will retry automatically).\n');
182
+ }
183
+ resolveDone();
184
+ });
185
+ }
186
+ catch {
187
+ resolveDone();
188
+ }
189
+ });
190
+ }
191
+ /**
192
+ * Run the first health check after init (cadence Q1). Best-effort: shells out to the
193
+ * healthcheck bin via npx so the watchdog does not take a runtime dependency on it. Any
194
+ * failure (bin absent, no network) is swallowed — init has already succeeded.
195
+ */
196
+ async function autoFirstHealthcheck(harnessRoot) {
197
+ const { spawn } = await import('node:child_process');
198
+ await new Promise((resolveDone) => {
199
+ try {
200
+ // -p <pkg> <bin>: the multi-arg form npx can actually resolve (bare `npx <pkg> <bin>`
201
+ // passes the bin name as an ARGUMENT to the package's default bin and breaks).
202
+ const child = spawn('npx', ['--no-install', '-p', '@strongkeep/healthcheck', 'strongkeep-healthcheck', '--send', '--root', harnessRoot], { stdio: 'ignore' });
203
+ child.on('error', () => resolveDone());
204
+ child.on('close', () => resolveDone());
205
+ }
206
+ catch {
207
+ resolveDone();
208
+ }
209
+ });
210
+ }
211
+ // ponytail: realpath main-guard, Phase 1 pattern (macOS /var vs /private/var symlink).
212
+ if (isMainModule()) {
213
+ void main();
214
+ }
215
+ function isMainModule() {
216
+ const entry = process.argv[1];
217
+ if (entry === undefined)
218
+ return false;
219
+ try {
220
+ return fileURLToPath(import.meta.url) === realpathSync(entry);
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ }
226
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-login CLI — store the per-tenant feed token in the local login state
4
+ * (~/.strongkeep/token) that strongkeep-init reads.
5
+ *
6
+ * Usage:
7
+ * strongkeep-login Prompt for the token (input hidden) and save it.
8
+ * strongkeep-login --help
9
+ *
10
+ * SECURITY:
11
+ * - The token is NEVER accepted on argv (shell-history leak) and NEVER printed or echoed —
12
+ * the interactive prompt disables terminal echo; only a confirmation line is printed.
13
+ * - When stdin is not a TTY (scripted use), one trimmed line is read from stdin instead:
14
+ * `strongkeep-login < token-file` or `some-secret-store | strongkeep-login`.
15
+ * - The token file is written mode 0600 (parent dir 0700), matching what init expects.
16
+ * - Overwriting an existing token is fine (re-login) and is confirmed in the output.
17
+ * - The token value never appears in any error message, either.
18
+ */
19
+ /** Where the token lives — the exact path strongkeep-init's resolveToken reads. */
20
+ export declare function defaultTokenPath(): string;
21
+ /**
22
+ * Validate a candidate token's SHAPE (never its authenticity — the feed does that) and return
23
+ * the trimmed token. Throws on empty/whitespace, implausible length, or non-base64url chars.
24
+ * SECURITY: error messages describe the problem but NEVER include the token value.
25
+ */
26
+ export declare function validateTokenShape(raw: string): string;
27
+ /**
28
+ * Validate + persist the token: parent dir 0700 (created if missing), file 0600. The explicit
29
+ * chmod covers the overwrite case (writeFile's mode only applies on create; re-login must not
30
+ * leave a pre-existing looser mode in place). Nothing is written if validation fails.
31
+ */
32
+ export declare function writeToken(path: string, rawToken: string): Promise<void>;
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-login CLI — store the per-tenant feed token in the local login state
4
+ * (~/.strongkeep/token) that strongkeep-init reads.
5
+ *
6
+ * Usage:
7
+ * strongkeep-login Prompt for the token (input hidden) and save it.
8
+ * strongkeep-login --help
9
+ *
10
+ * SECURITY:
11
+ * - The token is NEVER accepted on argv (shell-history leak) and NEVER printed or echoed —
12
+ * the interactive prompt disables terminal echo; only a confirmation line is printed.
13
+ * - When stdin is not a TTY (scripted use), one trimmed line is read from stdin instead:
14
+ * `strongkeep-login < token-file` or `some-secret-store | strongkeep-login`.
15
+ * - The token file is written mode 0600 (parent dir 0700), matching what init expects.
16
+ * - Overwriting an existing token is fine (re-login) and is confirmed in the output.
17
+ * - The token value never appears in any error message, either.
18
+ */
19
+ import process from 'node:process';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { realpathSync } from 'node:fs';
22
+ import { chmod, mkdir, writeFile } from 'node:fs/promises';
23
+ import { homedir } from 'node:os';
24
+ import { dirname, join } from 'node:path';
25
+ import { createInterface } from 'node:readline';
26
+ import { Writable } from 'node:stream';
27
+ /** Where the token lives — the exact path strongkeep-init's resolveToken reads. */
28
+ export function defaultTokenPath() {
29
+ return join(homedir(), '.strongkeep', 'token');
30
+ }
31
+ /** Plausible token bounds: long enough to be a real credential, short enough to be one line. */
32
+ const MIN_TOKEN_LENGTH = 16;
33
+ const MAX_TOKEN_LENGTH = 512;
34
+ /** base64url charset — the shape StrongKeep tenant tokens are issued in. */
35
+ const TOKEN_CHARSET = /^[A-Za-z0-9_-]+$/;
36
+ /**
37
+ * Validate a candidate token's SHAPE (never its authenticity — the feed does that) and return
38
+ * the trimmed token. Throws on empty/whitespace, implausible length, or non-base64url chars.
39
+ * SECURITY: error messages describe the problem but NEVER include the token value.
40
+ */
41
+ export function validateTokenShape(raw) {
42
+ const token = raw.trim();
43
+ if (token === '')
44
+ throw new Error('no token entered');
45
+ if (token.length < MIN_TOKEN_LENGTH) {
46
+ throw new Error(`token looks too short (${token.length} chars; expected at least ${MIN_TOKEN_LENGTH})`);
47
+ }
48
+ if (token.length > MAX_TOKEN_LENGTH) {
49
+ throw new Error(`token looks too long (${token.length} chars; expected at most ${MAX_TOKEN_LENGTH})`);
50
+ }
51
+ if (!TOKEN_CHARSET.test(token)) {
52
+ throw new Error('token contains characters outside the expected base64url set (A-Z a-z 0-9 - _)');
53
+ }
54
+ return token;
55
+ }
56
+ /**
57
+ * Validate + persist the token: parent dir 0700 (created if missing), file 0600. The explicit
58
+ * chmod covers the overwrite case (writeFile's mode only applies on create; re-login must not
59
+ * leave a pre-existing looser mode in place). Nothing is written if validation fails.
60
+ */
61
+ export async function writeToken(path, rawToken) {
62
+ const token = validateTokenShape(rawToken);
63
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
64
+ await writeFile(path, token + '\n', { encoding: 'utf8', mode: 0o600 });
65
+ await chmod(path, 0o600);
66
+ }
67
+ const HELP_TEXT = `strongkeep-login — save your per-tenant StrongKeep feed token to local login state
68
+
69
+ Usage:
70
+ strongkeep-login Prompt for the token (input hidden; piped stdin works too).
71
+
72
+ Writes ~/.strongkeep/token (mode 0600) — the file strongkeep-init reads. Re-running replaces
73
+ the stored token (re-login). The token is never accepted on argv and never printed.`;
74
+ /** Prompt on the TTY with echo DISABLED (readline output muted; prompt goes to stderr). */
75
+ async function promptHidden(promptText) {
76
+ const muted = new Writable({
77
+ write(_chunk, _encoding, callback) {
78
+ callback(); // swallow all echo — the typed token must never appear on screen
79
+ },
80
+ });
81
+ const rl = createInterface({ input: process.stdin, output: muted, terminal: true });
82
+ process.stderr.write(promptText);
83
+ try {
84
+ return await new Promise((resolveAnswer) => rl.question('', resolveAnswer));
85
+ }
86
+ finally {
87
+ rl.close();
88
+ process.stderr.write('\n');
89
+ }
90
+ }
91
+ /** Scripted path: read exactly one line from (non-TTY) stdin. */
92
+ async function readLineFromStdin() {
93
+ const rl = createInterface({ input: process.stdin, terminal: false });
94
+ try {
95
+ for await (const line of rl)
96
+ return line;
97
+ return '';
98
+ }
99
+ finally {
100
+ rl.close();
101
+ }
102
+ }
103
+ async function main() {
104
+ const argv = process.argv.slice(2);
105
+ if (argv.includes('--help') || argv.includes('-h')) {
106
+ process.stdout.write(HELP_TEXT + '\n');
107
+ process.exit(0);
108
+ }
109
+ if (argv.length > 0) {
110
+ // Refuse ALL positional/unknown args: the token must never travel via argv/shell history.
111
+ process.stderr.write('strongkeep-login: unexpected argument (the token is never passed on argv)\n\n' + HELP_TEXT + '\n');
112
+ process.exit(2);
113
+ }
114
+ try {
115
+ const raw = process.stdin.isTTY
116
+ ? await promptHidden('Paste your StrongKeep feed token (input hidden): ')
117
+ : await readLineFromStdin();
118
+ const path = defaultTokenPath();
119
+ await writeToken(path, raw);
120
+ // Confirmation ONLY — never the token value.
121
+ process.stdout.write(`token saved to ${path} (mode 0600). Next: strongkeep-init --tier propose\n`);
122
+ process.exit(0);
123
+ }
124
+ catch (err) {
125
+ process.stderr.write(`strongkeep-login: ${err instanceof Error ? err.message : String(err)}\n`);
126
+ process.exit(2);
127
+ }
128
+ }
129
+ // realpath main-guard, Phase 1 pattern (macOS /var vs /private/var symlink).
130
+ if (isMainModule()) {
131
+ void main();
132
+ }
133
+ function isMainModule() {
134
+ const entry = process.argv[1];
135
+ if (entry === undefined)
136
+ return false;
137
+ try {
138
+ return fileURLToPath(import.meta.url) === realpathSync(entry);
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-mcp CLI — the read-only MCP server the harness wires in (Journey B step 1c) so
4
+ * the subscriber's agent can query the live feed (list_threats / get_playbook / check_my_setup).
5
+ *
6
+ * This is the CONSUMER-side entry point. It reuses the read-only MCP implementation from
7
+ * @strongkeep/core's serve.ts (serveMcp) but points it at the LOCAL, verified last-good
8
+ * feed cache (not a build directory), so it works offline and never serves unverified content
9
+ * (fail-safe D2 applies here too — the snapshot is re-verified against the pinned key on load).
10
+ *
11
+ * Usage:
12
+ * strongkeep-mcp [--harness <dir>] Run the stdio MCP server against the local verified feed cache.
13
+ * strongkeep-mcp --help
14
+ *
15
+ * READ-ONLY: no tool here applies an action. Apply is exclusively the pipeline's job.
16
+ */
17
+ export interface McpCliOptions {
18
+ harnessRoot: string;
19
+ showHelp: boolean;
20
+ error?: string;
21
+ }
22
+ /** Parse argv → options. Total; never throws. */
23
+ export declare function parseMcpArgs(argv: string[], cwd?: string): McpCliOptions;
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-mcp CLI — the read-only MCP server the harness wires in (Journey B step 1c) so
4
+ * the subscriber's agent can query the live feed (list_threats / get_playbook / check_my_setup).
5
+ *
6
+ * This is the CONSUMER-side entry point. It reuses the read-only MCP implementation from
7
+ * @strongkeep/core's serve.ts (serveMcp) but points it at the LOCAL, verified last-good
8
+ * feed cache (not a build directory), so it works offline and never serves unverified content
9
+ * (fail-safe D2 applies here too — the snapshot is re-verified against the pinned key on load).
10
+ *
11
+ * Usage:
12
+ * strongkeep-mcp [--harness <dir>] Run the stdio MCP server against the local verified feed cache.
13
+ * strongkeep-mcp --help
14
+ *
15
+ * READ-ONLY: no tool here applies an action. Apply is exclusively the pipeline's job.
16
+ */
17
+ import process from 'node:process';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { realpathSync } from 'node:fs';
20
+ import { join, resolve } from 'node:path';
21
+ import { serveMcp } from '@strongkeep/core';
22
+ import { lastGoodDir } from '../feed-client.js';
23
+ import { loadLocalConfig, pinnedKeysFromConfig } from '../init.js';
24
+ const HELP_TEXT = `strongkeep-mcp — read-only MCP server over the LOCAL verified feed cache
25
+
26
+ Usage:
27
+ strongkeep-mcp [--harness <dir>]
28
+
29
+ Tools: list_threats / get_playbook / check_my_setup. Read-only; works offline; serves nothing
30
+ that has not passed signature + hash verification against the pinned StrongKeep key.`;
31
+ /** Parse argv → options. Total; never throws. */
32
+ export function parseMcpArgs(argv, cwd = process.cwd()) {
33
+ const opts = { harnessRoot: cwd, showHelp: false };
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const arg = argv[i];
36
+ if (arg === '--help' || arg === '-h')
37
+ opts.showHelp = true;
38
+ else if (arg === '--harness') {
39
+ const val = argv[++i];
40
+ if (val === undefined)
41
+ return { ...opts, showHelp: true, error: '--harness requires a dir' };
42
+ opts.harnessRoot = val;
43
+ }
44
+ else
45
+ return { ...opts, showHelp: true, error: `unknown argument "${arg}"` };
46
+ }
47
+ return opts;
48
+ }
49
+ async function main() {
50
+ const opts = parseMcpArgs(process.argv.slice(2));
51
+ if (opts.error)
52
+ process.stderr.write(`strongkeep-mcp: ${opts.error}\n\n`);
53
+ if (opts.showHelp) {
54
+ process.stdout.write(HELP_TEXT + '\n');
55
+ process.exit(opts.error ? 2 : 0);
56
+ }
57
+ try {
58
+ const harnessRoot = resolve(opts.harnessRoot);
59
+ const config = await loadLocalConfig(harnessRoot);
60
+ // Serve the verified LOCAL snapshot, pinned to the bundled/configured key SET (never
61
+ // self-declared; legacy single-key configs yield a 1-element set).
62
+ await serveMcp(lastGoodDir(join(harnessRoot, config.cacheDir)), pinnedKeysFromConfig(config));
63
+ process.exit(0);
64
+ }
65
+ catch (err) {
66
+ process.stderr.write(`strongkeep-mcp: ${err instanceof Error ? err.message : String(err)}\n`);
67
+ process.exit(2);
68
+ }
69
+ }
70
+ if (isMainModule()) {
71
+ void main();
72
+ }
73
+ function isMainModule() {
74
+ const entry = process.argv[1];
75
+ if (entry === undefined)
76
+ return false;
77
+ try {
78
+ return fileURLToPath(import.meta.url) === realpathSync(entry);
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/cli/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * strongkeep-poll CLI — the invocable the init-written guard hooks call: poll the signed feed
4
+ * (verify → parse, D2 fail-safe) and run the apply pipeline under the configured autonomy tier.
5
+ *
6
+ * Usage:
7
+ * strongkeep-poll [--harness <dir>]
8
+ * strongkeep-poll --help
9
+ *
10
+ * This is the "thin stable mechanism" runtime (D6): reading it end-to-end shows everything the
11
+ * watchdog can do to this machine — verify, then route closed-vocabulary actions to the
12
+ * allowlisted appliers, with undo records. No shell, no body execution, no telemetry.
13
+ */
14
+ export interface PollCliOptions {
15
+ harnessRoot: string;
16
+ showHelp: boolean;
17
+ error?: string;
18
+ }
19
+ /** Parse argv → options. Total; never throws. */
20
+ export declare function parsePollArgs(argv: string[], cwd?: string): PollCliOptions;