@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.
- package/dist/api/client.d.ts +91 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +190 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/keys.d.ts +41 -0
- package/dist/auth/keys.d.ts.map +1 -0
- package/dist/auth/keys.js +133 -0
- package/dist/auth/keys.js.map +1 -0
- package/dist/auth/signing.d.ts +27 -0
- package/dist/auth/signing.d.ts.map +1 -0
- package/dist/auth/signing.js +61 -0
- package/dist/auth/signing.js.map +1 -0
- package/dist/commands/capabilities.d.ts +8 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +30 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +38 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connectors.d.ts +9 -0
- package/dist/commands/connectors.d.ts.map +1 -0
- package/dist/commands/connectors.js +34 -0
- package/dist/commands/connectors.js.map +1 -0
- package/dist/commands/execute.d.ts +17 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +58 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/register.d.ts +14 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +65 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/commands/request.d.ts +9 -0
- package/dist/commands/request.d.ts.map +1 -0
- package/dist/commands/request.js +54 -0
- package/dist/commands/request.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +44 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/verify.d.ts +9 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +61 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/whoami.d.ts +8 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +65 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config/store.d.ts +30 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +73 -0
- package/dist/config/store.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +9 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +16 -0
- package/dist/output.js.map +1 -0
- package/dist/util/shell.d.ts +12 -0
- package/dist/util/shell.d.ts.map +1 -0
- package/dist/util/shell.js +14 -0
- package/dist/util/shell.js.map +1 -0
- 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"}
|