happy-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/api/client.d.ts +39 -0
- package/dist/api/client.js +49 -0
- package/dist/auth/credentials.d.ts +22 -0
- package/dist/auth/credentials.js +80 -0
- package/dist/auth/crypto.d.ts +118 -0
- package/dist/auth/crypto.js +249 -0
- package/dist/auth/pairing.d.ts +16 -0
- package/dist/auth/pairing.js +90 -0
- package/dist/auth/refresh.d.ts +11 -0
- package/dist/auth/refresh.js +50 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +13 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +306 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +22 -0
- package/dist/relay/client.d.ts +34 -0
- package/dist/relay/client.js +242 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +89 -0
- package/dist/session/keys.d.ts +25 -0
- package/dist/session/keys.js +41 -0
- package/dist/session/manager.d.ts +27 -0
- package/dist/session/manager.js +187 -0
- package/dist/session/types.d.ts +101 -0
- package/dist/session/types.js +1 -0
- package/dist/tools/answer_question.d.ts +5 -0
- package/dist/tools/answer_question.js +52 -0
- package/dist/tools/approve_permission.d.ts +4 -0
- package/dist/tools/approve_permission.js +54 -0
- package/dist/tools/deny_permission.d.ts +4 -0
- package/dist/tools/deny_permission.js +31 -0
- package/dist/tools/get_session.d.ts +4 -0
- package/dist/tools/get_session.js +106 -0
- package/dist/tools/list_computers.d.ts +4 -0
- package/dist/tools/list_computers.js +36 -0
- package/dist/tools/list_sessions.d.ts +4 -0
- package/dist/tools/list_sessions.js +46 -0
- package/dist/tools/send_message.d.ts +4 -0
- package/dist/tools/send_message.js +54 -0
- package/dist/tools/start_session.d.ts +5 -0
- package/dist/tools/start_session.js +49 -0
- package/dist/tools/watch_session.d.ts +4 -0
- package/dist/tools/watch_session.js +91 -0
- package/dist/types/wire.d.ts +148 -0
- package/dist/types/wire.js +9 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jared Spencer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# happy-mcp-server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that lets AI assistants observe and control active Happy Coder sessions. It connects to the Happy relay server via end-to-end encrypted channels, enabling remote session monitoring, message sending, permission management, and session control.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g happy-mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Authenticate
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
happy-mcp auth
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Scan the QR code with the Happy mobile app to pair your account.
|
|
20
|
+
|
|
21
|
+
### 3. Configure Your MCP Client
|
|
22
|
+
|
|
23
|
+
Add `happy-mcp` to your MCP client configuration. See [MCP Configuration](#mcp-configuration) below.
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Environment variables customize server behavior:
|
|
28
|
+
|
|
29
|
+
| Variable | Default | Description |
|
|
30
|
+
|----------|---------|-------------|
|
|
31
|
+
| `HAPPY_SERVER_URL` | `https://api.cluster-fluster.com` | Happy relay server URL. |
|
|
32
|
+
| `HAPPY_MCP_COMPUTERS` | `os.hostname()` | Comma-separated list of computer hostnames to filter sessions and machines. Use `*` to show all computers. |
|
|
33
|
+
| `HAPPY_MCP_PROJECT_PATHS` | `process.cwd()` | Comma-separated list of project path prefixes to filter sessions. Use `*` to show all paths. |
|
|
34
|
+
| `HAPPY_MCP_LOG_LEVEL` | `warn` | Log level: `debug`, `info`, `warn`, or `error`. Logs are written to stderr. |
|
|
35
|
+
| `HAPPY_MCP_ENABLE_START` | `true` | Set to `false` to disable the `start_session` tool. |
|
|
36
|
+
|
|
37
|
+
## MCP Configuration
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary><b>Claude Code</b></summary>
|
|
41
|
+
|
|
42
|
+
Add to `.mcp.json` in your project root or configure via CLI:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add --transport stdio happy -- happy-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or manually add to `.mcp.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"happy": {
|
|
54
|
+
"type": "stdio",
|
|
55
|
+
"command": "happy-mcp"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**With custom environment variables:**
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"happy": {
|
|
67
|
+
"type": "stdio",
|
|
68
|
+
"command": "happy-mcp",
|
|
69
|
+
"env": {
|
|
70
|
+
"HAPPY_MCP_COMPUTERS": "*",
|
|
71
|
+
"HAPPY_MCP_PROJECT_PATHS": "*"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
</details>
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary><b>Claude Desktop</b></summary>
|
|
82
|
+
|
|
83
|
+
Add to your Claude Desktop config:
|
|
84
|
+
|
|
85
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
86
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"happy": {
|
|
92
|
+
"type": "stdio",
|
|
93
|
+
"command": "happy-mcp"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
<details>
|
|
102
|
+
<summary><b>Cursor</b></summary>
|
|
103
|
+
|
|
104
|
+
Add to `.cursor/mcp.json` in your project or global config:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"happy": {
|
|
110
|
+
"command": "happy-mcp"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
</details>
|
|
117
|
+
|
|
118
|
+
<details>
|
|
119
|
+
<summary><b>Windsurf</b></summary>
|
|
120
|
+
|
|
121
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"happy": {
|
|
127
|
+
"command": "happy-mcp"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary><b>VS Code with Continue</b></summary>
|
|
137
|
+
|
|
138
|
+
Add to `~/.continue/config.json`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"experimental": {
|
|
143
|
+
"modelContextProtocolServers": [
|
|
144
|
+
{
|
|
145
|
+
"transport": {
|
|
146
|
+
"type": "stdio",
|
|
147
|
+
"command": "happy-mcp"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
</details>
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Credentials } from '../auth/crypto.js';
|
|
2
|
+
import type { Config } from '../config.js';
|
|
3
|
+
import type { RawSession, RawMachine } from '../session/types.js';
|
|
4
|
+
export declare class ApiClient {
|
|
5
|
+
private http;
|
|
6
|
+
private credentials;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(credentials: Credentials, config: Config);
|
|
9
|
+
get token(): string;
|
|
10
|
+
listActiveSessions(): Promise<RawSession[]>;
|
|
11
|
+
listMachines(): Promise<RawMachine[]>;
|
|
12
|
+
getSessionMessages(sessionId: string, afterSeq?: number, limit?: number): Promise<{
|
|
13
|
+
messages: Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
seq: number;
|
|
16
|
+
content: {
|
|
17
|
+
t: string;
|
|
18
|
+
c: string;
|
|
19
|
+
};
|
|
20
|
+
localId?: string;
|
|
21
|
+
createdAt: number;
|
|
22
|
+
updatedAt: number;
|
|
23
|
+
}>;
|
|
24
|
+
hasMore: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
sendMessages(sessionId: string, messages: Array<{
|
|
27
|
+
content: string;
|
|
28
|
+
localId: string;
|
|
29
|
+
}>): Promise<{
|
|
30
|
+
messages: Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
seq: number;
|
|
33
|
+
localId: string;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
updatedAt: number;
|
|
36
|
+
}>;
|
|
37
|
+
}>;
|
|
38
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { refreshToken } from '../auth/refresh.js';
|
|
3
|
+
export class ApiClient {
|
|
4
|
+
http;
|
|
5
|
+
credentials;
|
|
6
|
+
config;
|
|
7
|
+
constructor(credentials, config) {
|
|
8
|
+
this.credentials = credentials;
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.http = axios.create({
|
|
11
|
+
baseURL: config.serverUrl,
|
|
12
|
+
headers: { Authorization: `Bearer ${credentials.token}` },
|
|
13
|
+
timeout: 30_000,
|
|
14
|
+
});
|
|
15
|
+
// 401 interceptor for automatic token refresh
|
|
16
|
+
this.http.interceptors.response.use(undefined, async (error) => {
|
|
17
|
+
if (error.response?.status === 401 && !error.config._retried) {
|
|
18
|
+
error.config._retried = true;
|
|
19
|
+
const newToken = await refreshToken(this.credentials, this.config);
|
|
20
|
+
this.http.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
|
|
21
|
+
error.config.headers['Authorization'] = `Bearer ${newToken}`;
|
|
22
|
+
return this.http.request(error.config);
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
get token() {
|
|
28
|
+
return this.credentials.token;
|
|
29
|
+
}
|
|
30
|
+
async listActiveSessions() {
|
|
31
|
+
const res = await this.http.get('/v2/sessions/active');
|
|
32
|
+
return res.data.sessions ?? res.data ?? [];
|
|
33
|
+
}
|
|
34
|
+
async listMachines() {
|
|
35
|
+
const res = await this.http.get('/v1/machines');
|
|
36
|
+
return res.data ?? [];
|
|
37
|
+
}
|
|
38
|
+
async getSessionMessages(sessionId, afterSeq = 0, limit = 100) {
|
|
39
|
+
const res = await this.http.get(`/v3/sessions/${encodeURIComponent(sessionId)}/messages`, { params: { after_seq: afterSeq, limit } });
|
|
40
|
+
return res.data;
|
|
41
|
+
}
|
|
42
|
+
async sendMessages(sessionId, messages) {
|
|
43
|
+
const res = await this.http.post(`/v3/sessions/${encodeURIComponent(sessionId)}/messages`, { messages }, { timeout: 60_000 });
|
|
44
|
+
return res.data;
|
|
45
|
+
}
|
|
46
|
+
async deleteSession(sessionId) {
|
|
47
|
+
await this.http.delete(`/v1/sessions/${encodeURIComponent(sessionId)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Credentials } from './crypto.js';
|
|
2
|
+
export type { Credentials } from './crypto.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read credentials from disk.
|
|
5
|
+
* Returns null if file doesn't exist or is invalid.
|
|
6
|
+
* Derives contentKeyPair from secret on load.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readCredentials(path: string): Credentials | null;
|
|
9
|
+
/**
|
|
10
|
+
* Write credentials to disk with 0600 permissions.
|
|
11
|
+
* Creates parent directory with 0700 if needed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeCredentials(path: string, token: string, secret: Uint8Array, serverUrl?: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Validate that credentials file has safe permissions (0600).
|
|
16
|
+
* Throws if group or world readable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateFilePermissions(path: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Remove credentials file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function clearCredentials(path: string): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, unlinkSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import { encodeBase64, decodeBase64, deriveContentKeyPair } from './crypto.js';
|
|
4
|
+
import { logger } from '../logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Read credentials from disk.
|
|
7
|
+
* Returns null if file doesn't exist or is invalid.
|
|
8
|
+
* Derives contentKeyPair from secret on load.
|
|
9
|
+
*/
|
|
10
|
+
export function readCredentials(path) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = readFileSync(path, 'utf-8');
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
if (!parsed.token || !parsed.secret) {
|
|
15
|
+
logger.warn('Credentials file missing token or secret');
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const secret = decodeBase64(parsed.secret);
|
|
19
|
+
if (secret.length !== 32) {
|
|
20
|
+
logger.warn('Credentials secret is not 32 bytes');
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const contentKeyPair = deriveContentKeyPair(secret);
|
|
24
|
+
return { token: parsed.token, secret, contentKeyPair };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err.code === 'ENOENT') {
|
|
28
|
+
return null; // File doesn't exist, first run
|
|
29
|
+
}
|
|
30
|
+
logger.warn('Failed to read credentials:', err.message);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Write credentials to disk with 0600 permissions.
|
|
36
|
+
* Creates parent directory with 0700 if needed.
|
|
37
|
+
*/
|
|
38
|
+
export function writeCredentials(path, token, secret, serverUrl) {
|
|
39
|
+
const dir = dirname(path);
|
|
40
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
41
|
+
const data = JSON.stringify({
|
|
42
|
+
token,
|
|
43
|
+
secret: encodeBase64(secret),
|
|
44
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
45
|
+
pairedAt: new Date().toISOString(),
|
|
46
|
+
}, null, 2);
|
|
47
|
+
writeFileSync(path, data, { mode: 0o600 });
|
|
48
|
+
logger.info('Credentials written to', path);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate that credentials file has safe permissions (0600).
|
|
52
|
+
* Throws if group or world readable.
|
|
53
|
+
*/
|
|
54
|
+
export function validateFilePermissions(path) {
|
|
55
|
+
try {
|
|
56
|
+
const stat = statSync(path);
|
|
57
|
+
const mode = stat.mode & 0o777;
|
|
58
|
+
if (mode & 0o077) {
|
|
59
|
+
throw new Error(`Credentials file ${path} has unsafe permissions ${mode.toString(8)}. ` +
|
|
60
|
+
`Expected 0600. Fix with: chmod 600 ${path}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err.code === 'ENOENT')
|
|
65
|
+
return;
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove credentials file.
|
|
71
|
+
*/
|
|
72
|
+
export function clearCredentials(path) {
|
|
73
|
+
try {
|
|
74
|
+
unlinkSync(path);
|
|
75
|
+
logger.info('Credentials cleared');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// ignore if doesn't exist
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import nacl from 'tweetnacl';
|
|
2
|
+
export declare function encodeBase64(buf: Uint8Array): string;
|
|
3
|
+
export declare function decodeBase64(base64: string): Uint8Array;
|
|
4
|
+
export declare function encodeBase64Url(buf: Uint8Array): string;
|
|
5
|
+
export declare function decodeBase64Url(base64url: string): Uint8Array;
|
|
6
|
+
export declare function hmacSha512(key: Uint8Array, data: Uint8Array): Uint8Array;
|
|
7
|
+
interface KeyTreeState {
|
|
8
|
+
key: Uint8Array;
|
|
9
|
+
chainCode: Uint8Array;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Derive root of key tree.
|
|
13
|
+
* CRITICAL: key = encode(usage + ' Master Seed'), data = seed
|
|
14
|
+
* Verified against upstream: packages/happy-agent/src/encryption.ts
|
|
15
|
+
*/
|
|
16
|
+
export declare function deriveSecretKeyTreeRoot(seed: Uint8Array, usage: string): KeyTreeState;
|
|
17
|
+
/**
|
|
18
|
+
* Derive child key from chain code.
|
|
19
|
+
* data = [0x00, ...encode(index)]
|
|
20
|
+
*/
|
|
21
|
+
export declare function deriveSecretKeyTreeChild(chainCode: Uint8Array, index: string): KeyTreeState;
|
|
22
|
+
/**
|
|
23
|
+
* Derive key at path. Root + iterate children.
|
|
24
|
+
* Test vectors:
|
|
25
|
+
* deriveKey(encode("test seed"), "test usage", [])
|
|
26
|
+
* => E6E55652456F9FE47D6FF46CA3614E85B499F77E7B340FBBB1553307CEDC1E74
|
|
27
|
+
* deriveKey(encode("test seed"), "test usage", ["child1", "child2"])
|
|
28
|
+
* => 1011C097D2105D27362B987A631496BBF68B836124D1D072E9D1613C6028CF75
|
|
29
|
+
*/
|
|
30
|
+
export declare function deriveKey(seed: Uint8Array, usage: string, path: string[]): Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* CRITICAL: Has a SHA-512 step to match libsodium's crypto_box_seed_keypair behavior.
|
|
33
|
+
* 1. Derive seed via HMAC tree
|
|
34
|
+
* 2. SHA-512(seed)[0:32] = box secret key
|
|
35
|
+
* 3. nacl.box.keyPair.fromSecretKey(boxSecretKey)
|
|
36
|
+
*
|
|
37
|
+
* Source: packages/happy-agent/src/encryption.ts:deriveContentKeyPair
|
|
38
|
+
*/
|
|
39
|
+
export declare function deriveContentKeyPair(secret: Uint8Array): nacl.BoxKeyPair;
|
|
40
|
+
/**
|
|
41
|
+
* Generate auth challenge for token refresh.
|
|
42
|
+
* CRITICAL: Uses nacl.sign.keyPair.fromSeed(secret) with the RAW account secret.
|
|
43
|
+
* NOT the derived content keypair (which is Curve25519, not Ed25519).
|
|
44
|
+
*
|
|
45
|
+
* Source: packages/happy-agent/src/encryption.ts:authChallenge
|
|
46
|
+
*/
|
|
47
|
+
export declare function authChallenge(secret: Uint8Array): {
|
|
48
|
+
challenge: Uint8Array;
|
|
49
|
+
publicKey: Uint8Array;
|
|
50
|
+
signature: Uint8Array;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Decrypt a NaCl box bundle: [ephemeralPubKey(32)][nonce(24)][ciphertext]
|
|
54
|
+
* Returns decrypted Uint8Array or null on failure.
|
|
55
|
+
*
|
|
56
|
+
* Source: packages/happy-agent/src/encryption.ts
|
|
57
|
+
*/
|
|
58
|
+
export declare function decryptBoxBundle(bundle: Uint8Array, recipientSecretKey: Uint8Array): Uint8Array | null;
|
|
59
|
+
/**
|
|
60
|
+
* Encrypt data for a public key using NaCl box.
|
|
61
|
+
* Returns: [ephemeralPubKey(32)][nonce(24)][ciphertext]
|
|
62
|
+
*/
|
|
63
|
+
export declare function encryptForPublicKey(data: Uint8Array, recipientPublicKey: Uint8Array): Uint8Array;
|
|
64
|
+
/**
|
|
65
|
+
* Encrypt with AES-256-GCM.
|
|
66
|
+
* Bundle format: [version(1)=0x00][nonce(12)][ciphertext(N)][authTag(16)]
|
|
67
|
+
*/
|
|
68
|
+
export declare function encryptAesGcm(key: Uint8Array, data: unknown): Uint8Array;
|
|
69
|
+
/**
|
|
70
|
+
* Decrypt AES-256-GCM bundle.
|
|
71
|
+
* Returns parsed JSON or null on failure.
|
|
72
|
+
*/
|
|
73
|
+
export declare function decryptAesGcm(key: Uint8Array, bundle: Uint8Array): unknown | null;
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt with NaCl SecretBox.
|
|
76
|
+
* Bundle format: [nonce(24)][ciphertext]
|
|
77
|
+
*/
|
|
78
|
+
export declare function encryptSecretBox(key: Uint8Array, data: unknown): Uint8Array;
|
|
79
|
+
/**
|
|
80
|
+
* Decrypt NaCl SecretBox bundle.
|
|
81
|
+
* Returns parsed JSON or null on failure.
|
|
82
|
+
*/
|
|
83
|
+
export declare function decryptSecretBox(key: Uint8Array, bundle: Uint8Array): unknown | null;
|
|
84
|
+
export type EncryptionVariant = 'dataKey' | 'legacy';
|
|
85
|
+
export interface SessionEncryption {
|
|
86
|
+
key: Uint8Array;
|
|
87
|
+
variant: EncryptionVariant;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Encrypt data using the appropriate variant.
|
|
91
|
+
*/
|
|
92
|
+
export declare function encrypt(key: Uint8Array, variant: EncryptionVariant, data: unknown): Uint8Array;
|
|
93
|
+
/**
|
|
94
|
+
* Decrypt data using the appropriate variant.
|
|
95
|
+
*/
|
|
96
|
+
export declare function decrypt(key: Uint8Array, variant: EncryptionVariant, bundle: Uint8Array): unknown | null;
|
|
97
|
+
/**
|
|
98
|
+
* Encrypt data and return base64 string.
|
|
99
|
+
*/
|
|
100
|
+
export declare function encryptToBase64(encryption: SessionEncryption, data: unknown): string;
|
|
101
|
+
/**
|
|
102
|
+
* Decrypt base64-encoded data.
|
|
103
|
+
*/
|
|
104
|
+
export declare function decryptFromBase64(encryption: SessionEncryption, base64: string): unknown | null;
|
|
105
|
+
export interface Credentials {
|
|
106
|
+
token: string;
|
|
107
|
+
secret: Uint8Array;
|
|
108
|
+
contentKeyPair: nacl.BoxKeyPair;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve session encryption key.
|
|
112
|
+
* CRITICAL: Strip version byte (first byte) before NaCl box decryption.
|
|
113
|
+
* Pass contentKeyPair.secretKey, NOT the full keypair.
|
|
114
|
+
*
|
|
115
|
+
* Source: packages/happy-agent/src/api.ts:resolveSessionEncryption
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveSessionEncryption(dataEncryptionKey: string | null | undefined, credentials: Credentials): SessionEncryption;
|
|
118
|
+
export {};
|