slaunt 0.1.16 → 0.1.18

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 (164) hide show
  1. package/README.md +17 -106
  2. package/bin/slaunt-hooks.js +96 -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 +88 -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 +43 -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 +44 -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 -16
  79. package/dist/audit/run.d.ts.map +0 -1
  80. package/dist/audit/run.js +0 -66
  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 -220
  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 -667
  121. package/dist/explanation/local.js.map +0 -1
  122. package/dist/mcp/introspect.d.ts +0 -14
  123. package/dist/mcp/introspect.d.ts.map +0 -1
  124. package/dist/mcp/introspect.js +0 -186
  125. package/dist/mcp/introspect.js.map +0 -1
  126. package/dist/presentation/plain-language.d.ts +0 -12
  127. package/dist/presentation/plain-language.d.ts.map +0 -1
  128. package/dist/presentation/plain-language.js +0 -120
  129. package/dist/presentation/plain-language.js.map +0 -1
  130. package/dist/privacy/redaction.d.ts +0 -2
  131. package/dist/privacy/redaction.d.ts.map +0 -1
  132. package/dist/privacy/redaction.js +0 -50
  133. package/dist/privacy/redaction.js.map +0 -1
  134. package/dist/report/html.d.ts +0 -4
  135. package/dist/report/html.d.ts.map +0 -1
  136. package/dist/report/html.js +0 -101
  137. package/dist/report/html.js.map +0 -1
  138. package/dist/tui/analysis-path.d.ts +0 -17
  139. package/dist/tui/analysis-path.d.ts.map +0 -1
  140. package/dist/tui/analysis-path.js +0 -184
  141. package/dist/tui/analysis-path.js.map +0 -1
  142. package/dist/tui/render.d.ts +0 -4
  143. package/dist/tui/render.d.ts.map +0 -1
  144. package/dist/tui/render.js +0 -273
  145. package/dist/tui/render.js.map +0 -1
  146. package/dist/tui/splash.d.ts +0 -15
  147. package/dist/tui/splash.d.ts.map +0 -1
  148. package/dist/tui/splash.js +0 -130
  149. package/dist/tui/splash.js.map +0 -1
  150. package/dist/tui/stream.d.ts +0 -6
  151. package/dist/tui/stream.d.ts.map +0 -1
  152. package/dist/tui/stream.js +0 -56
  153. package/dist/tui/stream.js.map +0 -1
  154. package/dist/types.d.ts +0 -131
  155. package/dist/types.d.ts.map +0 -1
  156. package/dist/types.js +0 -2
  157. package/dist/types.js.map +0 -1
  158. package/dist/version.d.ts +0 -2
  159. package/dist/version.d.ts.map +0 -1
  160. package/dist/version.js +0 -8
  161. package/dist/version.js.map +0 -1
  162. package/models/APACHE-2.0.txt +0 -202
  163. package/models/MODEL.json +0 -29
  164. package/models/NOTICE.md +0 -23
@@ -0,0 +1,43 @@
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
+ rejectOverall(error);
26
+ };
27
+ timer = setTimeout(() => fail('timeout'), timeoutMs);
28
+ const authorize = ({client, authorizationUrl}) => new Promise((resolve, reject) => {
29
+ if (failed || !selected.has(client) || pending.has(client) || typeof authorizationUrl !== 'string') { reject(new Error('cohort-invalid-request')); fail('authorization'); return; }
30
+ pending.set(client, { authorizationUrl, resolve, reject });
31
+ if (pending.size !== clients.length || started) return;
32
+ started = true;
33
+ const requests = clients.map(name => ({client:name, authorizationUrl:pending.get(name).authorizationUrl}));
34
+ Promise.resolve().then(() => approve(requests)).then(callbacks => {
35
+ if (failed) return;
36
+ 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');
37
+ for (const name of clients) pending.get(name).resolve(callbacks[name]);
38
+ }).catch(error => fail('authorization', undefined, error?.setupDetail));
39
+ });
40
+ 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); throw error; }));
41
+ try { await Promise.race([Promise.all(runs), failedPromise]); } finally { clearTimeout(timer); }
42
+ }
43
+ 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
+ }