@permission-slip/cli 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.
Files changed (65) hide show
  1. package/dist/api/client.d.ts +91 -0
  2. package/dist/api/client.d.ts.map +1 -0
  3. package/dist/api/client.js +190 -0
  4. package/dist/api/client.js.map +1 -0
  5. package/dist/auth/keys.d.ts +41 -0
  6. package/dist/auth/keys.d.ts.map +1 -0
  7. package/dist/auth/keys.js +133 -0
  8. package/dist/auth/keys.js.map +1 -0
  9. package/dist/auth/signing.d.ts +27 -0
  10. package/dist/auth/signing.d.ts.map +1 -0
  11. package/dist/auth/signing.js +61 -0
  12. package/dist/auth/signing.js.map +1 -0
  13. package/dist/commands/capabilities.d.ts +8 -0
  14. package/dist/commands/capabilities.d.ts.map +1 -0
  15. package/dist/commands/capabilities.js +30 -0
  16. package/dist/commands/capabilities.js.map +1 -0
  17. package/dist/commands/config.d.ts +8 -0
  18. package/dist/commands/config.d.ts.map +1 -0
  19. package/dist/commands/config.js +38 -0
  20. package/dist/commands/config.js.map +1 -0
  21. package/dist/commands/connectors.d.ts +9 -0
  22. package/dist/commands/connectors.d.ts.map +1 -0
  23. package/dist/commands/connectors.js +34 -0
  24. package/dist/commands/connectors.js.map +1 -0
  25. package/dist/commands/execute.d.ts +17 -0
  26. package/dist/commands/execute.d.ts.map +1 -0
  27. package/dist/commands/execute.js +58 -0
  28. package/dist/commands/execute.js.map +1 -0
  29. package/dist/commands/register.d.ts +14 -0
  30. package/dist/commands/register.d.ts.map +1 -0
  31. package/dist/commands/register.js +65 -0
  32. package/dist/commands/register.js.map +1 -0
  33. package/dist/commands/request.d.ts +9 -0
  34. package/dist/commands/request.d.ts.map +1 -0
  35. package/dist/commands/request.js +54 -0
  36. package/dist/commands/request.js.map +1 -0
  37. package/dist/commands/status.d.ts +9 -0
  38. package/dist/commands/status.d.ts.map +1 -0
  39. package/dist/commands/status.js +44 -0
  40. package/dist/commands/status.js.map +1 -0
  41. package/dist/commands/verify.d.ts +9 -0
  42. package/dist/commands/verify.d.ts.map +1 -0
  43. package/dist/commands/verify.js +61 -0
  44. package/dist/commands/verify.js.map +1 -0
  45. package/dist/commands/whoami.d.ts +8 -0
  46. package/dist/commands/whoami.d.ts.map +1 -0
  47. package/dist/commands/whoami.js +65 -0
  48. package/dist/commands/whoami.js.map +1 -0
  49. package/dist/config/store.d.ts +30 -0
  50. package/dist/config/store.d.ts.map +1 -0
  51. package/dist/config/store.js +73 -0
  52. package/dist/config/store.js.map +1 -0
  53. package/dist/index.d.ts +22 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +51 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/output.d.ts +9 -0
  58. package/dist/output.d.ts.map +1 -0
  59. package/dist/output.js +16 -0
  60. package/dist/output.js.map +1 -0
  61. package/dist/util/shell.d.ts +12 -0
  62. package/dist/util/shell.d.ts.map +1 -0
  63. package/dist/util/shell.js +14 -0
  64. package/dist/util/shell.js.map +1 -0
  65. package/package.json +54 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * HTTP client for the Permission Slip API.
3
+ *
4
+ * Transparently handles:
5
+ * - Request signing (X-Permission-Slip-Signature)
6
+ * - JSON serialization / deserialization
7
+ * - Error formatting
8
+ *
9
+ * Endpoints follow docs/agents.md. Signing paths use the router path
10
+ * (e.g. /agents/42/verify), not the full URL including the /api/v1 prefix.
11
+ */
12
+ export interface ApiError {
13
+ code: string;
14
+ message: string;
15
+ retryable: boolean;
16
+ details?: Record<string, unknown>;
17
+ trace_id?: string;
18
+ }
19
+ export declare class PermissionSlipApiError extends Error {
20
+ readonly statusCode: number;
21
+ readonly apiError: ApiError;
22
+ constructor(statusCode: number, apiError: ApiError);
23
+ }
24
+ export interface ClientOptions {
25
+ /** Base URL of the Permission Slip server, e.g. https://app.permissionslip.dev */
26
+ serverUrl: string;
27
+ /** Agent ID — use REGISTRATION_AGENT_ID during registration */
28
+ agentId: number | string;
29
+ }
30
+ interface RequestOptions {
31
+ method: "GET" | "POST" | "DELETE";
32
+ routerPath: string;
33
+ apiPath?: string;
34
+ body?: unknown;
35
+ /** Override agentId just for this request (e.g. REGISTRATION_AGENT_ID) */
36
+ agentIdOverride?: number | string;
37
+ /** Whether this is a public endpoint (no signing required) */
38
+ public?: boolean;
39
+ }
40
+ export declare class ApiClient {
41
+ private base;
42
+ private agentId;
43
+ constructor(opts: ClientOptions);
44
+ request<T>(opts: RequestOptions): Promise<T>;
45
+ /** POST /invite/{code} — register with an invite code */
46
+ register(inviteCode: string, publicKey: string, name: string, version?: string): Promise<{
47
+ agent_id: number;
48
+ expires_at: string;
49
+ verification_required: boolean;
50
+ }>;
51
+ /** POST /agents/{id}/verify — verify registration with confirmation code */
52
+ verify(agentId: number, confirmationCode: string): Promise<{
53
+ status: string;
54
+ registered_at: string;
55
+ }>;
56
+ /** GET /agents/me — get own agent record */
57
+ status(): Promise<{
58
+ agent_id: number;
59
+ status: string;
60
+ metadata: Record<string, unknown>;
61
+ registered_at: string;
62
+ last_active_at: string;
63
+ created_at: string;
64
+ }>;
65
+ /** GET /agents/{id}/capabilities */
66
+ capabilities(agentId: number): Promise<unknown>;
67
+ /** GET /connectors — public, no auth */
68
+ connectors(): Promise<unknown>;
69
+ /** GET /connectors/{id} — public, no auth */
70
+ connector(id: string): Promise<unknown>;
71
+ /** POST /approvals/request */
72
+ requestApproval(actionId: string, params: unknown, context?: {
73
+ description?: string;
74
+ risk_level?: string;
75
+ }): Promise<{
76
+ approval_id: string;
77
+ approval_url: string;
78
+ status: string;
79
+ expires_at: string;
80
+ verification_required: boolean;
81
+ }>;
82
+ /** POST /actions/execute */
83
+ execute(tokenOrConfigId: {
84
+ token: string;
85
+ } | {
86
+ configuration_id: string;
87
+ request_id?: string;
88
+ }, actionId: string | undefined, params: unknown): Promise<unknown>;
89
+ }
90
+ export {};
91
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,QAAQ;gBADlB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,QAAQ;CAKrC;AAED,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAiBD,UAAU,cAAc;IACtB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,OAAO,CAAkB;gBAErB,IAAI,EAAE,aAAa;IAkBzB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAwDlD,yDAAyD;IACnD,QAAQ,CACZ,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,SAAU;kBAIL,MAAM;oBACJ,MAAM;+BACK,OAAO;;IAclC,4EAA4E;IACtE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM;gBAEtB,MAAM;uBAAiB,MAAM;;IAQ7D,4CAA4C;IACtC,MAAM;kBAEE,MAAM;gBACR,MAAM;kBACJ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;uBAClB,MAAM;wBACL,MAAM;oBACV,MAAM;;IAOtB,oCAAoC;IAC9B,YAAY,CAAC,OAAO,EAAE,MAAM;IAQlC,wCAAwC;IAClC,UAAU;IAQhB,6CAA6C;IACvC,SAAS,CAAC,EAAE,EAAE,MAAM;IAQ1B,8BAA8B;IACxB,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;qBAIxC,MAAM;sBACL,MAAM;gBACZ,MAAM;oBACF,MAAM;+BACK,OAAO;;IAQlC,4BAA4B;IACtB,OAAO,CACX,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACtF,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,OAAO;CAmBlB"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * HTTP client for the Permission Slip API.
3
+ *
4
+ * Transparently handles:
5
+ * - Request signing (X-Permission-Slip-Signature)
6
+ * - JSON serialization / deserialization
7
+ * - Error formatting
8
+ *
9
+ * Endpoints follow docs/agents.md. Signing paths use the router path
10
+ * (e.g. /agents/42/verify), not the full URL including the /api/v1 prefix.
11
+ */
12
+ import crypto from "node:crypto";
13
+ import { buildSignatureHeader, REGISTRATION_AGENT_ID } from "../auth/signing.js";
14
+ export class PermissionSlipApiError extends Error {
15
+ statusCode;
16
+ apiError;
17
+ constructor(statusCode, apiError) {
18
+ super(apiError.message);
19
+ this.statusCode = statusCode;
20
+ this.apiError = apiError;
21
+ this.name = "PermissionSlipApiError";
22
+ }
23
+ }
24
+ /**
25
+ * Strips trailing slashes from a URL.
26
+ */
27
+ function normalizeBase(url) {
28
+ return url.replace(/\/+$/, "");
29
+ }
30
+ /**
31
+ * Extracts the router path (without /api/v1) for signing purposes.
32
+ * The invite endpoint is at the host root (no /api/v1 prefix).
33
+ */
34
+ function signingPath(routerPath) {
35
+ return routerPath;
36
+ }
37
+ export class ApiClient {
38
+ base;
39
+ agentId;
40
+ constructor(opts) {
41
+ // Reject non-http(s) schemes before ever making a request.
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(opts.serverUrl);
45
+ }
46
+ catch {
47
+ throw new Error(`Invalid server URL: ${opts.serverUrl}`);
48
+ }
49
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
50
+ throw new Error(`Server URL must use http or https (got ${parsed.protocol}). ` +
51
+ "Check the --server flag.");
52
+ }
53
+ this.base = normalizeBase(opts.serverUrl);
54
+ this.agentId = opts.agentId;
55
+ }
56
+ async request(opts) {
57
+ const bodyStr = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;
58
+ const headers = {
59
+ "Content-Type": "application/json",
60
+ "User-Agent": "@permission-slip/cli",
61
+ };
62
+ if (!opts.public) {
63
+ const agentId = opts.agentIdOverride ?? this.agentId;
64
+ headers["X-Permission-Slip-Signature"] = buildSignatureHeader({
65
+ agentId,
66
+ method: opts.method,
67
+ path: signingPath(opts.routerPath),
68
+ body: bodyStr,
69
+ });
70
+ }
71
+ const fullPath = opts.apiPath ?? `/api/v1${opts.routerPath}`;
72
+ const url = `${this.base}${fullPath}`;
73
+ const res = await fetch(url, {
74
+ method: opts.method,
75
+ headers,
76
+ body: bodyStr,
77
+ });
78
+ if (!res.ok) {
79
+ let apiError;
80
+ try {
81
+ const errBody = (await res.json());
82
+ apiError = errBody.error ?? {
83
+ code: "unknown",
84
+ message: `HTTP ${res.status}`,
85
+ retryable: false,
86
+ };
87
+ }
88
+ catch {
89
+ apiError = {
90
+ code: "unknown",
91
+ message: `HTTP ${res.status} ${res.statusText}`,
92
+ retryable: false,
93
+ };
94
+ }
95
+ throw new PermissionSlipApiError(res.status, apiError);
96
+ }
97
+ // Some responses may be empty (204)
98
+ if (res.status === 204) {
99
+ return undefined;
100
+ }
101
+ return res.json();
102
+ }
103
+ // ---------- Typed API methods ----------
104
+ /** POST /invite/{code} — register with an invite code */
105
+ async register(inviteCode, publicKey, name, version = "1.0.0") {
106
+ const requestId = crypto.randomUUID();
107
+ return this.request({
108
+ method: "POST",
109
+ routerPath: `/invite/${inviteCode}`,
110
+ apiPath: `/invite/${inviteCode}`,
111
+ body: {
112
+ request_id: requestId,
113
+ public_key: publicKey,
114
+ metadata: { name, version },
115
+ },
116
+ agentIdOverride: REGISTRATION_AGENT_ID,
117
+ });
118
+ }
119
+ /** POST /agents/{id}/verify — verify registration with confirmation code */
120
+ async verify(agentId, confirmationCode) {
121
+ const requestId = crypto.randomUUID();
122
+ return this.request({
123
+ method: "POST",
124
+ routerPath: `/agents/${agentId}/verify`,
125
+ body: { request_id: requestId, confirmation_code: confirmationCode },
126
+ agentIdOverride: agentId,
127
+ });
128
+ }
129
+ /** GET /agents/me — get own agent record */
130
+ async status() {
131
+ return this.request({
132
+ method: "GET",
133
+ routerPath: "/agents/me",
134
+ });
135
+ }
136
+ /** GET /agents/{id}/capabilities */
137
+ async capabilities(agentId) {
138
+ return this.request({
139
+ method: "GET",
140
+ routerPath: `/agents/${agentId}/capabilities`,
141
+ agentIdOverride: agentId,
142
+ });
143
+ }
144
+ /** GET /connectors — public, no auth */
145
+ async connectors() {
146
+ return this.request({
147
+ method: "GET",
148
+ routerPath: "/connectors",
149
+ public: true,
150
+ });
151
+ }
152
+ /** GET /connectors/{id} — public, no auth */
153
+ async connector(id) {
154
+ return this.request({
155
+ method: "GET",
156
+ routerPath: `/connectors/${id}`,
157
+ public: true,
158
+ });
159
+ }
160
+ /** POST /approvals/request */
161
+ async requestApproval(actionId, params, context) {
162
+ const requestId = crypto.randomUUID();
163
+ return this.request({
164
+ method: "POST",
165
+ routerPath: "/approvals/request",
166
+ body: { request_id: requestId, action_id: actionId, parameters: params, context },
167
+ });
168
+ }
169
+ /** POST /actions/execute */
170
+ async execute(tokenOrConfigId, actionId, params) {
171
+ const requestId = crypto.randomUUID();
172
+ let body;
173
+ if ("token" in tokenOrConfigId) {
174
+ body = { token: tokenOrConfigId.token, action_id: actionId, parameters: params };
175
+ }
176
+ else {
177
+ body = {
178
+ request_id: tokenOrConfigId.request_id ?? requestId,
179
+ configuration_id: tokenOrConfigId.configuration_id,
180
+ parameters: params,
181
+ };
182
+ }
183
+ return this.request({
184
+ method: "POST",
185
+ routerPath: "/actions/execute",
186
+ body,
187
+ });
188
+ }
189
+ }
190
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Ed25519 key management for Permission Slip.
3
+ *
4
+ * Keys are stored in OpenSSH format:
5
+ * Private: ~/.ssh/permission_slip_agent
6
+ * Public: ~/.ssh/permission_slip_agent.pub
7
+ *
8
+ * We use Node's built-in `crypto` module and `child_process` (ssh-keygen) to
9
+ * generate the key pair, matching the existing manual flow agents used to do.
10
+ */
11
+ import crypto from "node:crypto";
12
+ export interface KeyPair {
13
+ privateKeyFile: string;
14
+ publicKey: string;
15
+ }
16
+ /**
17
+ * Returns true if a Permission Slip key pair already exists.
18
+ */
19
+ export declare function keyPairExists(): boolean;
20
+ /**
21
+ * Reads the public key from disk.
22
+ * Returns the full "ssh-ed25519 AAAA..." string (just key type + base64, no comment).
23
+ */
24
+ export declare function readPublicKey(): string;
25
+ /**
26
+ * Loads the private key from disk as a Node KeyObject.
27
+ */
28
+ export declare function loadPrivateKey(): crypto.KeyObject;
29
+ /**
30
+ * Generates a new Ed25519 key pair using ssh-keygen and saves it to
31
+ * ~/.ssh/permission_slip_agent{,.pub}.
32
+ *
33
+ * Throws if ssh-keygen is not available or if keys already exist and
34
+ * overwrite is false.
35
+ */
36
+ export declare function generateKeyPair(overwrite?: boolean): KeyPair;
37
+ /**
38
+ * Returns a path relative to home for display purposes.
39
+ */
40
+ export declare function displayPath(absPath: string): string;
41
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/auth/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAMjC,MAAM,WAAW,OAAO;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAIvC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAatC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,SAAS,CAQjD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,UAAQ,GAAG,OAAO,CAoC1D;AAgDD;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnD"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Ed25519 key management for Permission Slip.
3
+ *
4
+ * Keys are stored in OpenSSH format:
5
+ * Private: ~/.ssh/permission_slip_agent
6
+ * Public: ~/.ssh/permission_slip_agent.pub
7
+ *
8
+ * We use Node's built-in `crypto` module and `child_process` (ssh-keygen) to
9
+ * generate the key pair, matching the existing manual flow agents used to do.
10
+ */
11
+ import crypto from "node:crypto";
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import { execFileSync } from "node:child_process";
15
+ import { PUBLIC_KEY_FILE, PRIVATE_KEY_FILE, SSH_DIR } from "../config/store.js";
16
+ /**
17
+ * Returns true if a Permission Slip key pair already exists.
18
+ */
19
+ export function keyPairExists() {
20
+ return (fs.existsSync(PRIVATE_KEY_FILE) && fs.existsSync(PUBLIC_KEY_FILE));
21
+ }
22
+ /**
23
+ * Reads the public key from disk.
24
+ * Returns the full "ssh-ed25519 AAAA..." string (just key type + base64, no comment).
25
+ */
26
+ export function readPublicKey() {
27
+ if (!fs.existsSync(PUBLIC_KEY_FILE)) {
28
+ throw new Error(`Public key not found at ${PUBLIC_KEY_FILE}. Run 'permission-slip register' to generate one.`);
29
+ }
30
+ const raw = fs.readFileSync(PUBLIC_KEY_FILE, "utf-8").trim();
31
+ // Take only type + key, drop optional comment
32
+ const parts = raw.split(/\s+/);
33
+ if (parts.length < 2) {
34
+ throw new Error(`Invalid public key format in ${PUBLIC_KEY_FILE}`);
35
+ }
36
+ return `${parts[0]} ${parts[1]}`;
37
+ }
38
+ /**
39
+ * Loads the private key from disk as a Node KeyObject.
40
+ */
41
+ export function loadPrivateKey() {
42
+ if (!fs.existsSync(PRIVATE_KEY_FILE)) {
43
+ throw new Error(`Private key not found at ${PRIVATE_KEY_FILE}. Run 'permission-slip register' to generate one.`);
44
+ }
45
+ const pem = fs.readFileSync(PRIVATE_KEY_FILE);
46
+ return crypto.createPrivateKey({ key: pem, format: "pem" });
47
+ }
48
+ /**
49
+ * Generates a new Ed25519 key pair using ssh-keygen and saves it to
50
+ * ~/.ssh/permission_slip_agent{,.pub}.
51
+ *
52
+ * Throws if ssh-keygen is not available or if keys already exist and
53
+ * overwrite is false.
54
+ */
55
+ export function generateKeyPair(overwrite = false) {
56
+ if (!overwrite && keyPairExists()) {
57
+ return {
58
+ privateKeyFile: PRIVATE_KEY_FILE,
59
+ publicKey: readPublicKey(),
60
+ };
61
+ }
62
+ if (!fs.existsSync(SSH_DIR)) {
63
+ fs.mkdirSync(SSH_DIR, { recursive: true, mode: 0o700 });
64
+ }
65
+ // Remove existing key files if overwriting to avoid ssh-keygen prompt
66
+ if (overwrite) {
67
+ if (fs.existsSync(PRIVATE_KEY_FILE))
68
+ fs.unlinkSync(PRIVATE_KEY_FILE);
69
+ if (fs.existsSync(PUBLIC_KEY_FILE))
70
+ fs.unlinkSync(PUBLIC_KEY_FILE);
71
+ }
72
+ try {
73
+ // Use execFileSync (not execSync) to avoid shell interpolation of the key path.
74
+ execFileSync("ssh-keygen", ["-t", "ed25519", "-f", PRIVATE_KEY_FILE, "-N", "", "-C", "permission-slip"], { stdio: "pipe" });
75
+ }
76
+ catch (err) {
77
+ // Fallback: generate key using Node crypto and write in OpenSSH format
78
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
79
+ writeOpenSSHPrivateKey(privateKey, PRIVATE_KEY_FILE);
80
+ writeOpenSSHPublicKey(publicKey, PUBLIC_KEY_FILE);
81
+ }
82
+ return {
83
+ privateKeyFile: PRIVATE_KEY_FILE,
84
+ publicKey: readPublicKey(),
85
+ };
86
+ }
87
+ /**
88
+ * Writes an Ed25519 private key in PKCS8 PEM format.
89
+ * Used as a fallback when ssh-keygen is not available.
90
+ *
91
+ * NOTE: This produces PKCS8 PEM (`-----BEGIN PRIVATE KEY-----`), not the
92
+ * OpenSSH native format (`-----BEGIN OPENSSH PRIVATE KEY-----`). Node's
93
+ * `crypto.createPrivateKey` accepts both, so signing works correctly.
94
+ * However, standard SSH tooling (e.g. `ssh-add`, `ssh-keygen -y`) will
95
+ * not recognize PKCS8 PEM — if interoperability with SSH tools is required,
96
+ * use a platform that has `ssh-keygen` available.
97
+ */
98
+ function writeOpenSSHPrivateKey(key, filePath) {
99
+ const pem = key.export({ type: "pkcs8", format: "pem" });
100
+ fs.writeFileSync(filePath, pem, { mode: 0o600 });
101
+ }
102
+ /**
103
+ * Writes the public key in OpenSSH authorized_keys format: "ssh-ed25519 <base64> permission-slip"
104
+ */
105
+ function writeOpenSSHPublicKey(key, filePath) {
106
+ // Export as SubjectPublicKeyInfo DER, then convert to OpenSSH wire format
107
+ const derBuf = key.export({ type: "spki", format: "der" });
108
+ // The last 32 bytes of the SPKI DER for ed25519 are the raw public key bytes
109
+ const rawPubKey = derBuf.slice(-32);
110
+ // Build OpenSSH wire format: length-prefixed "ssh-ed25519" + length-prefixed key bytes
111
+ const keyType = Buffer.from("ssh-ed25519");
112
+ const buf = Buffer.alloc(4 + keyType.length + 4 + rawPubKey.length);
113
+ let offset = 0;
114
+ buf.writeUInt32BE(keyType.length, offset);
115
+ offset += 4;
116
+ keyType.copy(buf, offset);
117
+ offset += keyType.length;
118
+ buf.writeUInt32BE(rawPubKey.length, offset);
119
+ offset += 4;
120
+ rawPubKey.copy(buf, offset);
121
+ const b64 = buf.toString("base64");
122
+ fs.writeFileSync(filePath, `ssh-ed25519 ${b64} permission-slip\n`, {
123
+ mode: 0o644,
124
+ });
125
+ }
126
+ /**
127
+ * Returns a path relative to home for display purposes.
128
+ */
129
+ export function displayPath(absPath) {
130
+ const home = os.homedir();
131
+ return absPath.startsWith(home) ? absPath.replace(home, "~") : absPath;
132
+ }
133
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/auth/keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Request signing for Permission Slip.
3
+ *
4
+ * Implements the X-Permission-Slip-Signature header as documented in docs/agents.md.
5
+ *
6
+ * Header format:
7
+ * agent_id="42", algorithm="Ed25519", timestamp="1708617600", signature="<base64url>"
8
+ *
9
+ * Canonical string (5 lines joined by \n):
10
+ * METHOD\nPATH\nQUERY\nTIMESTAMP\nBODY_HASH
11
+ *
12
+ * During registration, use MAX_INT64 as the agent_id placeholder.
13
+ */
14
+ export declare const REGISTRATION_AGENT_ID = "9223372036854775807";
15
+ export interface SignatureOptions {
16
+ agentId: number | string;
17
+ method: string;
18
+ path: string;
19
+ query?: string;
20
+ body?: string;
21
+ timestamp?: number;
22
+ }
23
+ /**
24
+ * Computes the X-Permission-Slip-Signature header value.
25
+ */
26
+ export declare function buildSignatureHeader(opts: SignatureOptions): string;
27
+ //# sourceMappingURL=signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/auth/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAYnE"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Request signing for Permission Slip.
3
+ *
4
+ * Implements the X-Permission-Slip-Signature header as documented in docs/agents.md.
5
+ *
6
+ * Header format:
7
+ * agent_id="42", algorithm="Ed25519", timestamp="1708617600", signature="<base64url>"
8
+ *
9
+ * Canonical string (5 lines joined by \n):
10
+ * METHOD\nPATH\nQUERY\nTIMESTAMP\nBODY_HASH
11
+ *
12
+ * During registration, use MAX_INT64 as the agent_id placeholder.
13
+ */
14
+ import crypto from "node:crypto";
15
+ import { loadPrivateKey } from "./keys.js";
16
+ // Placeholder agent_id used during registration before we have a real ID
17
+ export const REGISTRATION_AGENT_ID = "9223372036854775807";
18
+ /**
19
+ * Computes the X-Permission-Slip-Signature header value.
20
+ */
21
+ export function buildSignatureHeader(opts) {
22
+ const privateKey = loadPrivateKey();
23
+ const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1000);
24
+ const method = opts.method.toUpperCase();
25
+ const urlPath = opts.path;
26
+ const query = canonicalizeQuery(opts.query ?? "");
27
+ const bodyHash = hashBody(opts.body ?? "");
28
+ const canonical = `${method}\n${urlPath}\n${query}\n${timestamp}\n${bodyHash}`;
29
+ const sig = signCanonical(privateKey, canonical);
30
+ return `agent_id="${opts.agentId}", algorithm="Ed25519", timestamp="${timestamp}", signature="${sig}"`;
31
+ }
32
+ function canonicalizeQuery(raw) {
33
+ if (!raw)
34
+ return "";
35
+ const pairs = new URLSearchParams(raw);
36
+ const sorted = Array.from(pairs.entries()).sort(([a], [b]) => a.localeCompare(b));
37
+ return sorted
38
+ .map(([k, v]) => `${encodeRFC3986(k)}=${encodeRFC3986(v)}`)
39
+ .join("&");
40
+ }
41
+ function encodeRFC3986(str) {
42
+ return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
43
+ }
44
+ function hashBody(body) {
45
+ if (!body) {
46
+ // SHA-256 of empty string
47
+ return "e3b0c44298fc1c149afbf4c8996fb924" +
48
+ "27ae41e4649b934ca495991b7852b855";
49
+ }
50
+ return crypto.createHash("sha256").update(body, "utf-8").digest("hex");
51
+ }
52
+ function signCanonical(privateKey, canonical) {
53
+ const sig = crypto.sign(null, Buffer.from(canonical, "utf-8"), privateKey);
54
+ // base64url without padding
55
+ return sig
56
+ .toString("base64")
57
+ .replace(/\+/g, "-")
58
+ .replace(/\//g, "_")
59
+ .replace(/=+$/, "");
60
+ }
61
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/auth/signing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * permission-slip capabilities [--server <url>]
3
+ *
4
+ * Lists the action configurations and standing approvals available to this agent.
5
+ */
6
+ import type { Command } from "commander";
7
+ export declare function capabilitiesCommand(program: Command): void;
8
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/commands/capabilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2B1D"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * permission-slip capabilities [--server <url>]
3
+ *
4
+ * Lists the action configurations and standing approvals available to this agent.
5
+ */
6
+ import { ApiClient } from "../api/client.js";
7
+ import { resolveAgentId } from "./status.js";
8
+ import { output } from "../output.js";
9
+ export function capabilitiesCommand(program) {
10
+ program
11
+ .command("capabilities")
12
+ .description("List available action configurations and standing approvals")
13
+ .option("--server <url>", "Permission Slip server URL", "https://app.permissionslip.dev")
14
+ .option("--agent-id <id>", "Agent ID (auto-detected from saved registration)")
15
+ .option("--pretty", "Pretty-printed JSON (default is compact JSON)")
16
+ .action(async (opts) => {
17
+ const outputOpts = { pretty: opts.pretty ?? false };
18
+ try {
19
+ const agentId = resolveAgentId(opts.server, opts.agentId);
20
+ const client = new ApiClient({ serverUrl: opts.server, agentId });
21
+ const result = await client.capabilities(agentId);
22
+ output(result, outputOpts);
23
+ }
24
+ catch (err) {
25
+ output({ error: err instanceof Error ? err.message : String(err) }, outputOpts);
26
+ process.exit(1);
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/commands/capabilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAsB,MAAM,cAAc,CAAC;AAE1D,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,6DAA6D,CAAC;SAC1E,MAAM,CACL,gBAAgB,EAChB,4BAA4B,EAC5B,gCAAgC,CACjC;SACA,MAAM,CAAC,iBAAiB,EAAE,kDAAkD,CAAC;SAC7E,MAAM,CAAC,UAAU,EAAE,+CAA+C,CAAC;SACnE,MAAM,CAAC,KAAK,EAAE,IAId,EAAE,EAAE;QACH,MAAM,UAAU,GAAkB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;YAClE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * permission-slip config [--server <url>]
3
+ *
4
+ * Shows all saved registrations or the registration for a specific server.
5
+ */
6
+ import type { Command } from "commander";
7
+ export declare function configCommand(program: Command): void;
8
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYzC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2BpD"}