@ccmsg/cli 0.1.0 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccmsg/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "The ccmsg daemon, CLI and agent plugins for one instance (= one config home)",
5
5
  "license": "MIT",
6
6
  "author": "kawaz",
@@ -20,7 +20,7 @@
20
20
  "test": "bun test"
21
21
  },
22
22
  "dependencies": {
23
- "@ccmsg/protocol": "1.1.0"
23
+ "@ccmsg/protocol": "1.4.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/bun": "^1.3.0",
@@ -0,0 +1,64 @@
1
+ import type { Endpoint, Subject } from "@ccmsg/protocol";
2
+ import { failure, OpError, reply, type DispatchResult } from "../dispatch/index.ts";
3
+ import type { Auth } from "./auth.ts";
4
+
5
+ /** The three things a person does to passkeys from the machine the instance
6
+ * runs on (DR-0001 §2.2).
7
+ *
8
+ * They are not ops of the contract, and deliberately: the contract is what
9
+ * reaches an instance over a network, and registration is the one thing that
10
+ * must not. These travel on the unix socket alone, where reaching the address
11
+ * is the permission — the same footing the supervisor's own control requests
12
+ * stand on.
13
+ *
14
+ * They arrive as frames that are not ops, which is the shape the mesh handshake
15
+ * already uses for traffic the op vocabulary has no name for. */
16
+ export type AdminRequest =
17
+ | {
18
+ readonly admin: "passkey_add";
19
+ readonly request_id: string;
20
+ readonly endpoint?: Endpoint;
21
+ readonly rp_id?: string;
22
+ readonly name?: string;
23
+ readonly sub?: Subject;
24
+ }
25
+ | { readonly admin: "passkey_list"; readonly request_id: string }
26
+ | { readonly admin: "passkey_remove"; readonly request_id: string; readonly sub: Subject };
27
+
28
+ /** Whether a frame is one of these, without deciding anything about it. */
29
+ export function adminRequestOf(frame: unknown): AdminRequest | undefined {
30
+ if (typeof frame !== "object" || frame === null) return undefined;
31
+ const fields = frame as Record<string, unknown>;
32
+ const name = fields["admin"];
33
+ if (name !== "passkey_add" && name !== "passkey_list" && name !== "passkey_remove") {
34
+ return undefined;
35
+ }
36
+ return typeof fields["request_id"] === "string" ? (frame as AdminRequest) : undefined;
37
+ }
38
+
39
+ /** Run one administrative request. */
40
+ export function handleAdmin(auth: Auth, request: AdminRequest): DispatchResult {
41
+ try {
42
+ switch (request.admin) {
43
+ case "passkey_add":
44
+ return reply(
45
+ request.request_id,
46
+ auth.issue({
47
+ ...(request.endpoint === undefined ? {} : { endpoint: request.endpoint }),
48
+ ...(request.rp_id === undefined ? {} : { rpId: request.rp_id }),
49
+ ...(request.name === undefined ? {} : { label: request.name }),
50
+ ...(request.sub === undefined ? {} : { sub: request.sub }),
51
+ }),
52
+ );
53
+ case "passkey_list":
54
+ return reply(request.request_id, { credentials: auth.list() });
55
+ case "passkey_remove": {
56
+ const { closed } = auth.remove(request.sub);
57
+ return reply(request.request_id, { sub: request.sub, closed });
58
+ }
59
+ }
60
+ } catch (cause) {
61
+ if (cause instanceof OpError) return failure(request.request_id, cause.code, cause.message);
62
+ return failure(request.request_id, "internal_error", String(cause));
63
+ }
64
+ }