Skip to content

Commit de519e0

Browse files
grypezclaude
andcommitted
feat(session-cli): add session subcommands to kernel-cli
Adds ocap session create/list/requests/queue/approve/reject subcommands. Refactors daemon-client to delegate socket/RPC helpers to kernel-node-runtime, and extracts session command builder into session.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1c0dbc9 commit de519e0

3 files changed

Lines changed: 372 additions & 103 deletions

File tree

packages/kernel-cli/src/app.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
stopRelay,
2323
} from './commands/relay.ts';
2424
import { getServer } from './commands/serve.ts';
25+
import { buildSessionCommands } from './commands/session.ts';
2526
import { watchDir } from './commands/watch.ts';
2627
import { defaultConfig } from './config.ts';
2728
import type { Config } from './config.ts';
@@ -426,6 +427,13 @@ const yargsInstance = yargs(hideBin(process.argv))
426427
() => {
427428
// Handled by subcommands.
428429
},
430+
)
431+
.command(
432+
'session',
433+
'Manage authorization sessions',
434+
(_yargs) => buildSessionCommands(_yargs),
435+
() => {
436+
// Handled by subcommands.
437+
},
429438
);
430-
431439
await yargsInstance.help('help').parse();

packages/kernel-cli/src/commands/daemon-client.ts

Lines changed: 5 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,9 @@
1-
import { readLine, writeLine } from '@metamask/kernel-node-runtime/daemon';
2-
import type { JsonRpcResponse } from '@metamask/utils';
3-
import { assertIsJsonRpcResponse } from '@metamask/utils';
4-
import { randomUUID } from 'node:crypto';
5-
import { createConnection } from 'node:net';
6-
import type { Socket } from 'node:net';
7-
import { join } from 'node:path';
1+
import {
2+
getSocketPath,
3+
sendCommand,
4+
} from '@metamask/kernel-node-runtime/daemon';
85

9-
import { getOcapHome } from '../ocap-home.ts';
10-
11-
/**
12-
* Get the default daemon socket path.
13-
*
14-
* @returns The socket path.
15-
*/
16-
export function getSocketPath(): string {
17-
return join(getOcapHome(), 'daemon.sock');
18-
}
19-
20-
/**
21-
* Connect to a UNIX domain socket.
22-
*
23-
* @param socketPath - The socket path to connect to.
24-
* @returns A connected socket.
25-
*/
26-
async function connectSocket(socketPath: string): Promise<Socket> {
27-
return new Promise((resolve, reject) => {
28-
const socket = createConnection(socketPath, () => {
29-
socket.removeListener('error', reject);
30-
resolve(socket);
31-
});
32-
socket.on('error', reject);
33-
});
34-
}
35-
36-
/**
37-
* Options for {@link sendCommand}.
38-
*/
39-
type SendCommandOptions = {
40-
/** The UNIX socket path. */
41-
socketPath: string;
42-
/** The RPC method name. */
43-
method: string;
44-
/** Optional method parameters (object or positional array). */
45-
params?: Record<string, unknown> | unknown[] | undefined;
46-
/** Read timeout in milliseconds (default: no timeout). */
47-
timeoutMs?: number | undefined;
48-
};
49-
50-
/**
51-
* Send a JSON-RPC request to the daemon over a UNIX socket and return the response.
52-
*
53-
* Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
54-
* response line, then closes the connection. Retries once after a short delay
55-
* if the connection is rejected (e.g. due to a probe connection race).
56-
*
57-
* @param options - Command options.
58-
* @param options.socketPath - The UNIX socket path.
59-
* @param options.method - The RPC method name.
60-
* @param options.params - Optional method parameters.
61-
* @param options.timeoutMs - Read timeout in milliseconds (default: no timeout).
62-
* @returns The parsed JSON-RPC response.
63-
*/
64-
export async function sendCommand({
65-
socketPath,
66-
method,
67-
params,
68-
timeoutMs,
69-
}: SendCommandOptions): Promise<JsonRpcResponse> {
70-
const id = randomUUID();
71-
const request = {
72-
jsonrpc: '2.0',
73-
id,
74-
method,
75-
...(params === undefined ? {} : { params }),
76-
};
77-
78-
const attempt = async (): Promise<JsonRpcResponse> => {
79-
const socket = await connectSocket(socketPath);
80-
try {
81-
await writeLine(socket, JSON.stringify(request));
82-
const responseLine = await readLine(socket, timeoutMs);
83-
const parsed: unknown = JSON.parse(responseLine);
84-
assertIsJsonRpcResponse(parsed);
85-
return parsed;
86-
} finally {
87-
socket.destroy();
88-
}
89-
};
90-
91-
try {
92-
return await attempt();
93-
} catch (error: unknown) {
94-
// Retry once on connection errors only — the daemon's socket may
95-
// still be cleaning up a previous connection.
96-
const code = (error as NodeJS.ErrnoException | undefined)?.code;
97-
if (code !== 'ECONNREFUSED' && code !== 'ECONNRESET') {
98-
throw error;
99-
}
100-
await new Promise((resolve) => setTimeout(resolve, 100));
101-
return attempt();
102-
}
103-
}
6+
export { getSocketPath, sendCommand };
1047

1058
/**
1069
* Check whether the daemon is running by sending a lightweight `getStatus`

0 commit comments

Comments
 (0)