slaunt 0.1.17 → 0.1.19

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 (168) hide show
  1. package/README.md +17 -122
  2. package/bin/slaunt-hooks.js +99 -0
  3. package/package.json +9 -59
  4. package/release/SlauntKillBar.zip +0 -0
  5. package/release/manifest.json +7 -0
  6. package/scripts/killbar-portability-check.js +67 -0
  7. package/scripts/package-killbar-macos.sh +38 -0
  8. package/src/approval-manager.js +53 -0
  9. package/src/cli-auth.js +230 -0
  10. package/src/compact-wordmark.js +13 -0
  11. package/src/desktop/broker-daemon.js +18 -0
  12. package/src/desktop/broker-grants.js +37 -0
  13. package/src/desktop/broker-hook-command.js +23 -0
  14. package/src/desktop/broker-keychain.js +29 -0
  15. package/src/desktop/broker-launch-agent.js +34 -0
  16. package/src/desktop/broker-service.js +40 -0
  17. package/src/desktop/chat-capture-command.js +7 -0
  18. package/src/desktop/chat-hook-capture.js +445 -0
  19. package/src/desktop/claude-lifecycle.js +143 -0
  20. package/src/desktop/client-identity.js +19 -0
  21. package/src/desktop/native-claude-broker.js +34 -0
  22. package/src/desktop/native-claude-connection.js +42 -0
  23. package/src/desktop/tool-guard-command.js +13 -0
  24. package/src/desktop/tool-guard.js +225 -0
  25. package/src/discovery/catalog.js +734 -0
  26. package/src/full-screen.js +86 -0
  27. package/src/helper-release.js +27 -0
  28. package/src/hidden-oauth.js +70 -0
  29. package/src/independent-grants.js +38 -0
  30. package/src/install-flow.js +71 -0
  31. package/src/killbar-background-install.js +47 -0
  32. package/src/killbar-enrollment.js +74 -0
  33. package/src/killbar-launch-agent.js +10 -0
  34. package/src/mcp-identity.js +6 -0
  35. package/src/native-config.js +150 -0
  36. package/src/native-oauth.js +33 -0
  37. package/src/oauth-cohort.js +44 -0
  38. package/src/pty-wrapper.py +57 -0
  39. package/src/read-json.js +8 -0
  40. package/src/setup/client-installer.js +93 -0
  41. package/src/setup/codex-guard-supervisor.js +34 -0
  42. package/src/setup/hook-installer.js +496 -0
  43. package/src/setup/managed-policy.js +26 -0
  44. package/src/setup/mcp-config-installer.js +204 -0
  45. package/src/setup-flow.js +66 -0
  46. package/src/tui.js +205 -0
  47. package/test/approval-manager.test.js +27 -0
  48. package/test/broker-grants.test.js +14 -0
  49. package/test/broker-keychain.test.js +14 -0
  50. package/test/broker-launch-agent.test.js +15 -0
  51. package/test/broker-service.test.js +42 -0
  52. package/test/cli-auth-recovery.test.js +15 -0
  53. package/test/codex-guard-supervisor.test.js +25 -0
  54. package/test/full-screen.test.js +49 -0
  55. package/test/helper-release.test.js +22 -0
  56. package/test/hidden-oauth.test.js +56 -0
  57. package/test/hook-installer.test.js +232 -0
  58. package/test/independent-grants.test.js +16 -0
  59. package/test/install-flow.test.js +24 -0
  60. package/test/killbar-background-install.test.js +15 -0
  61. package/test/killbar-enrollment.test.js +115 -0
  62. package/test/killbar-launch-agent.test.js +12 -0
  63. package/test/killbar-portability.test.js +17 -0
  64. package/test/managed-policy.test.js +18 -0
  65. package/test/native-claude-broker.test.js +19 -0
  66. package/test/native-claude-connection.test.js +17 -0
  67. package/test/native-claude-hooks.test.js +9 -0
  68. package/test/native-config.test.js +3 -0
  69. package/test/native-integration.cjs +29 -0
  70. package/test/native-oauth.test.js +23 -0
  71. package/test/oauth-cohort.test.js +26 -0
  72. package/test/tui.test.js +100 -0
  73. package/LICENSE +0 -201
  74. package/dist/audit/risk-engine.d.ts +0 -4
  75. package/dist/audit/risk-engine.d.ts.map +0 -1
  76. package/dist/audit/risk-engine.js +0 -174
  77. package/dist/audit/risk-engine.js.map +0 -1
  78. package/dist/audit/run.d.ts +0 -18
  79. package/dist/audit/run.d.ts.map +0 -1
  80. package/dist/audit/run.js +0 -68
  81. package/dist/audit/run.js.map +0 -1
  82. package/dist/classification/builtin-catalog.d.ts +0 -3
  83. package/dist/classification/builtin-catalog.d.ts.map +0 -1
  84. package/dist/classification/builtin-catalog.js +0 -283
  85. package/dist/classification/builtin-catalog.js.map +0 -1
  86. package/dist/classification/catalog.d.ts +0 -10
  87. package/dist/classification/catalog.d.ts.map +0 -1
  88. package/dist/classification/catalog.js +0 -194
  89. package/dist/classification/catalog.js.map +0 -1
  90. package/dist/classification/classifier.d.ts +0 -3
  91. package/dist/classification/classifier.d.ts.map +0 -1
  92. package/dist/classification/classifier.js +0 -64
  93. package/dist/classification/classifier.js.map +0 -1
  94. package/dist/classification/public-catalog-config.d.ts +0 -3
  95. package/dist/classification/public-catalog-config.d.ts.map +0 -1
  96. package/dist/classification/public-catalog-config.js +0 -5
  97. package/dist/classification/public-catalog-config.js.map +0 -1
  98. package/dist/cli.d.ts +0 -3
  99. package/dist/cli.d.ts.map +0 -1
  100. package/dist/cli.js +0 -239
  101. package/dist/cli.js.map +0 -1
  102. package/dist/config/discover.d.ts +0 -8
  103. package/dist/config/discover.d.ts.map +0 -1
  104. package/dist/config/discover.js +0 -163
  105. package/dist/config/discover.js.map +0 -1
  106. package/dist/config/install.d.ts +0 -10
  107. package/dist/config/install.d.ts.map +0 -1
  108. package/dist/config/install.js +0 -143
  109. package/dist/config/install.js.map +0 -1
  110. package/dist/config/paths.d.ts +0 -24
  111. package/dist/config/paths.d.ts.map +0 -1
  112. package/dist/config/paths.js +0 -90
  113. package/dist/config/paths.js.map +0 -1
  114. package/dist/demo.d.ts +0 -4
  115. package/dist/demo.d.ts.map +0 -1
  116. package/dist/demo.js +0 -99
  117. package/dist/demo.js.map +0 -1
  118. package/dist/explanation/local.d.ts +0 -24
  119. package/dist/explanation/local.d.ts.map +0 -1
  120. package/dist/explanation/local.js +0 -696
  121. package/dist/explanation/local.js.map +0 -1
  122. package/dist/mcp/introspect.d.ts +0 -18
  123. package/dist/mcp/introspect.d.ts.map +0 -1
  124. package/dist/mcp/introspect.js +0 -236
  125. package/dist/mcp/introspect.js.map +0 -1
  126. package/dist/mcp/oauth.d.ts +0 -14
  127. package/dist/mcp/oauth.d.ts.map +0 -1
  128. package/dist/mcp/oauth.js +0 -257
  129. package/dist/mcp/oauth.js.map +0 -1
  130. package/dist/presentation/plain-language.d.ts +0 -12
  131. package/dist/presentation/plain-language.d.ts.map +0 -1
  132. package/dist/presentation/plain-language.js +0 -120
  133. package/dist/presentation/plain-language.js.map +0 -1
  134. package/dist/privacy/redaction.d.ts +0 -2
  135. package/dist/privacy/redaction.d.ts.map +0 -1
  136. package/dist/privacy/redaction.js +0 -50
  137. package/dist/privacy/redaction.js.map +0 -1
  138. package/dist/report/html.d.ts +0 -4
  139. package/dist/report/html.d.ts.map +0 -1
  140. package/dist/report/html.js +0 -101
  141. package/dist/report/html.js.map +0 -1
  142. package/dist/tui/analysis-path.d.ts +0 -17
  143. package/dist/tui/analysis-path.d.ts.map +0 -1
  144. package/dist/tui/analysis-path.js +0 -112
  145. package/dist/tui/analysis-path.js.map +0 -1
  146. package/dist/tui/render.d.ts +0 -4
  147. package/dist/tui/render.d.ts.map +0 -1
  148. package/dist/tui/render.js +0 -262
  149. package/dist/tui/render.js.map +0 -1
  150. package/dist/tui/splash.d.ts +0 -15
  151. package/dist/tui/splash.d.ts.map +0 -1
  152. package/dist/tui/splash.js +0 -130
  153. package/dist/tui/splash.js.map +0 -1
  154. package/dist/tui/stream.d.ts +0 -6
  155. package/dist/tui/stream.d.ts.map +0 -1
  156. package/dist/tui/stream.js +0 -56
  157. package/dist/tui/stream.js.map +0 -1
  158. package/dist/types.d.ts +0 -132
  159. package/dist/types.d.ts.map +0 -1
  160. package/dist/types.js +0 -2
  161. package/dist/types.js.map +0 -1
  162. package/dist/version.d.ts +0 -2
  163. package/dist/version.d.ts.map +0 -1
  164. package/dist/version.js +0 -8
  165. package/dist/version.js.map +0 -1
  166. package/models/APACHE-2.0.txt +0 -202
  167. package/models/MODEL.json +0 -29
  168. package/models/NOTICE.md +0 -23
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+ const { runHiddenLogin } = require('./hidden-oauth');
3
+ // The authority must show the user the selected clients and approve the exact
4
+ // requests. It is injected because the Slaunt endpoint is not yet available.
5
+ async function authorizeCohort({ clients, issuer, approve, login = runHiddenLogin, executablePaths = {}, timeoutMs = 120000 }) {
6
+ if (!Array.isArray(clients) || clients.length < 1 || clients.length > 2 || new Set(clients).size !== clients.length || clients.some(c => !['codex','claude'].includes(c)) || typeof approve !== 'function') throw new Error('invalid-cohort');
7
+ const selected = new Set(clients);
8
+ const pending = new Map();
9
+ let started = false;
10
+ let failed = false;
11
+ const controller = new AbortController();
12
+ let timer;
13
+ let rejectOverall;
14
+ const failedPromise = new Promise((_, reject) => { rejectOverall = reject; });
15
+ const fail = (stage, client, detail) => {
16
+ if (failed) return;
17
+ failed = true;
18
+ clearTimeout(timer);
19
+ controller.abort();
20
+ for (const entry of pending.values()) entry.reject(new Error('cohort-failed'));
21
+ const error = new Error('cohort-failed');
22
+ error.setupStage = stage;
23
+ if (['sign-in','workspace','enrollment','client-grants'].includes(detail)) error.setupDetail = detail;
24
+ if (client === 'codex' || client === 'claude') error.setupClient = client;
25
+ if (stage === 'native-login' && ['launch','request','waiting-approval','callback','client-exit'].includes(detail)) error.nativeStage = detail;
26
+ rejectOverall(error);
27
+ };
28
+ timer = setTimeout(() => fail('timeout'), timeoutMs);
29
+ const authorize = ({client, authorizationUrl}) => new Promise((resolve, reject) => {
30
+ if (failed || !selected.has(client) || pending.has(client) || typeof authorizationUrl !== 'string') { reject(new Error('cohort-invalid-request')); fail('authorization'); return; }
31
+ pending.set(client, { authorizationUrl, resolve, reject });
32
+ if (pending.size !== clients.length || started) return;
33
+ started = true;
34
+ const requests = clients.map(name => ({client:name, authorizationUrl:pending.get(name).authorizationUrl}));
35
+ Promise.resolve().then(() => approve(requests)).then(callbacks => {
36
+ if (failed) return;
37
+ if (!callbacks || typeof callbacks !== 'object' || Array.isArray(callbacks) || Object.keys(callbacks).length !== clients.length || clients.some(name => typeof callbacks[name] !== 'string')) throw new Error('cohort-invalid-response');
38
+ for (const name of clients) pending.get(name).resolve(callbacks[name]);
39
+ }).catch(error => fail('authorization', undefined, error?.setupDetail));
40
+ });
41
+ const runs = clients.map(executable => Promise.resolve().then(() => login({executable,commandPath:executablePaths[executable] || executable,issuer,authorize,timeoutMs,signal:controller.signal})).catch(error => { fail('native-login', executable, error?.nativeStage); throw error; }));
42
+ try { await Promise.race([Promise.all(runs), failedPromise]); } finally { clearTimeout(timer); }
43
+ }
44
+ module.exports = { authorizeCohort };
@@ -0,0 +1,57 @@
1
+ import os
2
+ import pty
3
+ import select
4
+ import signal
5
+ import subprocess
6
+ import sys
7
+ import termios
8
+
9
+ master, slave = pty.openpty()
10
+ attrs = termios.tcgetattr(slave)
11
+ attrs[3] &= ~termios.ECHO
12
+ termios.tcsetattr(slave, termios.TCSANOW, attrs)
13
+ process = subprocess.Popen(sys.argv[1:], stdin=slave, stdout=slave, stderr=slave, start_new_session=True)
14
+ os.close(slave)
15
+
16
+ def stop(_signal, _frame):
17
+ raise KeyboardInterrupt()
18
+
19
+ signal.signal(signal.SIGTERM, stop)
20
+ signal.signal(signal.SIGINT, stop)
21
+ stdin_open = True
22
+ interrupted = False
23
+ try:
24
+ while True:
25
+ sources = [master] + ([sys.stdin.buffer] if stdin_open else [])
26
+ ready, _, _ = select.select(sources, [], [], 0.2)
27
+ if master in ready:
28
+ try:
29
+ data = os.read(master, 4096)
30
+ except OSError:
31
+ data = b''
32
+ if not data:
33
+ break
34
+ os.write(sys.stdout.fileno(), data)
35
+ if stdin_open and sys.stdin.buffer in ready:
36
+ data = os.read(sys.stdin.fileno(), 4096)
37
+ if data:
38
+ os.write(master, data)
39
+ else:
40
+ stdin_open = False
41
+ if process.poll() is not None and not ready:
42
+ break
43
+ except KeyboardInterrupt:
44
+ interrupted = True
45
+ finally:
46
+ if process.poll() is None:
47
+ try:
48
+ process.wait(timeout=2 if not interrupted else 0.1)
49
+ except subprocess.TimeoutExpired:
50
+ process.terminate()
51
+ try:
52
+ process.wait(timeout=2)
53
+ except subprocess.TimeoutExpired:
54
+ process.kill()
55
+ process.wait()
56
+ os.close(master)
57
+ sys.exit(1 if interrupted else (process.returncode or 0))
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+ async function readJson(response,maxBytes=131072){
3
+ const declared=Number(response.headers.get('content-length')||0);if(declared>maxBytes)throw new Error('response-too-large');
4
+ const reader=response.body?.getReader();if(!reader)throw new Error('response-unavailable');let size=0;const chunks=[];
5
+ for(;;){const {value,done}=await reader.read();if(done)break;size+=value.byteLength;if(size>maxBytes){await reader.cancel();throw new Error('response-too-large');}chunks.push(Buffer.from(value));}
6
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
7
+ }
8
+ module.exports={readJson};
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const { brokerArgsForClient } = require('../desktop/client-identity');
4
+ const { SLAUNT_SERVER_NAME } = require('../mcp-identity');
5
+ const { catalog } = require('../discovery/catalog');
6
+ const { installMcpConfigForClient } = require('./mcp-config-installer');
7
+ const { installHooksForDetectedClients } = require('./hook-installer');
8
+
9
+ const SUPPORTED_CLIENTS = new Set(['claude-code', 'codex-cli']);
10
+ const SUCCESSFUL_MCP_STATES = new Set(['installed', 'unchanged']);
11
+
12
+ function installSelectedClients({
13
+ clients = [],
14
+ selectedClientIds = [],
15
+ homeDir,
16
+ broker,
17
+ fileSystem
18
+ } = {}) {
19
+ if (!Array.isArray(selectedClientIds) || selectedClientIds.length > SUPPORTED_CLIENTS.size) {
20
+ return { results: [], summary: { failed: 1, installed: 0, unchanged: 0 } };
21
+ }
22
+
23
+ const uniqueIds = [...new Set(selectedClientIds)];
24
+ if (uniqueIds.some((id) => typeof id !== 'string' || !SUPPORTED_CLIENTS.has(id))) {
25
+ return { results: [], summary: { failed: 1, installed: 0, unchanged: 0 } };
26
+ }
27
+
28
+ const clientById = new Map();
29
+ for (const client of clients) {
30
+ if (SUPPORTED_CLIENTS.has(client?.id) && !clientById.has(client.id)) clientById.set(client.id, client);
31
+ }
32
+ const resultById = new Map();
33
+ const hookClients = [];
34
+
35
+ for (const id of uniqueIds) {
36
+ const client = clientById.get(id);
37
+ if (!client) {
38
+ resultById.set(id, {
39
+ id,
40
+ name: catalog.find((entry) => entry.id === id)?.name || id,
41
+ status: 'not-detected',
42
+ mcp: { status: 'not-installed', reason: 'client-not-detected' },
43
+ hooks: { status: 'not-installed', reason: 'client-not-detected' }
44
+ });
45
+ continue;
46
+ }
47
+
48
+ const clientBroker = { ...broker, args: brokerArgsForClient(broker, id) };
49
+ const mcp = installMcpConfigForClient({ clientId: id, homeDir, broker: clientBroker });
50
+ const result = {
51
+ id,
52
+ name: client.name,
53
+ status: SUCCESSFUL_MCP_STATES.has(mcp.status) ? 'installed' : mcp.status,
54
+ mcp,
55
+ hooks: { status: 'not-installed', reason: 'mcp-configuration-required' }
56
+ };
57
+ resultById.set(id, result);
58
+ if (SUCCESSFUL_MCP_STATES.has(mcp.status)) hookClients.push(client);
59
+ }
60
+
61
+ if (hookClients.length > 0) {
62
+ const hooks = installHooksForDetectedClients({
63
+ clients: hookClients,
64
+ selectedClientIds: hookClients.map((client) => client.id),
65
+ homeDir,
66
+ broker,
67
+ ...(fileSystem ? { fileSystem } : {})
68
+ });
69
+ for (const result of hooks.results) {
70
+ const combined = resultById.get(result.id);
71
+ if (!combined) continue;
72
+ combined.hooks = { status: result.status, reason: result.reason };
73
+ if (result.status === 'error') combined.status = 'partial';
74
+ if (result.status === 'partial' && combined.status === 'installed') combined.status = 'partial';
75
+ }
76
+ }
77
+
78
+ const results = uniqueIds.map((id) => resultById.get(id));
79
+ return {
80
+ serverName: SLAUNT_SERVER_NAME,
81
+ results,
82
+ summary: {
83
+ selected: results.length,
84
+ installed: results.filter((result) => result.status === 'installed').length,
85
+ partial: results.filter((result) => result.status === 'partial').length,
86
+ conflicts: results.filter((result) => result.status === 'conflict').length,
87
+ notDetected: results.filter((result) => result.status === 'not-detected').length,
88
+ failed: results.filter((result) => result.status === 'error').length
89
+ }
90
+ };
91
+ }
92
+
93
+ module.exports = { SUPPORTED_CLIENTS, installSelectedClients };
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Converts guard process failures into a Codex PreToolUse denial. This program
4
+ // cannot control Codex behavior if the supervisor itself fails to start.
5
+ const {spawn}=require('node:child_process');
6
+ const path=require('node:path');
7
+ const MAX_INPUT=1024*1024, MAX_OUTPUT=64*1024, DEADLINE_MS=8000;
8
+ const denial=JSON.stringify({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'deny',permissionDecisionReason:'Slaunt tool authorization is unavailable.'}})+'\n';
9
+ let finished=false, child, input=[], inputBytes=0, output=[], outputBytes=0;
10
+ function finish(value=denial){if(finished)return;finished=true;clearTimeout(timer);if(child&&!child.killed)child.kill('SIGKILL');process.stdin.pause();process.stdout.write(value,()=>{process.exitCode=0;});}
11
+ const timer=setTimeout(()=>finish(),DEADLINE_MS);
12
+ const executable=process.argv[2];
13
+ if(process.argv.length!==3 || !executable || !path.isAbsolute(executable) || /[\x00-\x1f\x7f]/.test(executable)) finish();
14
+ else {
15
+ process.stdin.on('error',()=>finish());
16
+ process.stdin.on('data',chunk=>{inputBytes+=chunk.length;if(inputBytes>MAX_INPUT)return finish();input.push(chunk);});
17
+ process.stdin.on('end',()=>{
18
+ if(finished)return;
19
+ try {child=spawn(executable,[],{stdio:['pipe','pipe','ignore'],env:{...process.env,NODE_OPTIONS:'',NODE_PATH:''}});}catch{return finish();}
20
+ child.on('error',()=>finish());child.stdin.on('error',()=>finish());
21
+ child.stdout.on('data',chunk=>{outputBytes+=chunk.length;if(outputBytes>MAX_OUTPUT)return finish();output.push(chunk);});
22
+ child.stdout.on('error',()=>finish());
23
+ child.on('close',code=>{
24
+ if(finished)return;if(code!==0)return finish();
25
+ try {
26
+ const value=JSON.parse(Buffer.concat(output).toString('utf8'));
27
+ const hook=value?.hookSpecificOutput;
28
+ if(!hook||hook.hookEventName!=='PreToolUse'||!['deny',undefined].includes(hook.permissionDecision)||Object.keys(value).some(k=>k!=='hookSpecificOutput')||Object.keys(hook).some(k=>!['hookEventName','permissionDecision','permissionDecisionReason'].includes(k))|| (hook.permissionDecision==='deny'&&typeof hook.permissionDecisionReason!=='string')) return finish();
29
+ finish(JSON.stringify(value)+'\n');
30
+ }catch{finish();}
31
+ });
32
+ child.stdin.end(Buffer.concat(input));
33
+ });
34
+ }