@openape/apes 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Hofmann
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.
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/config.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ var CONFIG_DIR = join(homedir(), ".config", "apes");
8
+ var AUTH_FILE = join(CONFIG_DIR, "auth.json");
9
+ var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
10
+ var GRAPES_CONFIG_DIR = join(homedir(), ".config", "grapes");
11
+ var GRAPES_AUTH_FILE = join(GRAPES_CONFIG_DIR, "auth.json");
12
+ var GRAPES_CONFIG_FILE = join(GRAPES_CONFIG_DIR, "config.toml");
13
+ function ensureDir() {
14
+ if (!existsSync(CONFIG_DIR)) {
15
+ mkdirSync(CONFIG_DIR, { recursive: true });
16
+ }
17
+ }
18
+ function loadAuth() {
19
+ if (existsSync(AUTH_FILE)) {
20
+ try {
21
+ return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
22
+ } catch {
23
+ }
24
+ }
25
+ if (existsSync(GRAPES_AUTH_FILE)) {
26
+ try {
27
+ return JSON.parse(readFileSync(GRAPES_AUTH_FILE, "utf-8"));
28
+ } catch {
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function saveAuth(data) {
34
+ ensureDir();
35
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
36
+ }
37
+ function clearAuth() {
38
+ if (existsSync(AUTH_FILE)) {
39
+ writeFileSync(AUTH_FILE, "", { mode: 384 });
40
+ }
41
+ }
42
+ function loadConfig() {
43
+ if (existsSync(CONFIG_FILE)) {
44
+ try {
45
+ return parseTOML(readFileSync(CONFIG_FILE, "utf-8"));
46
+ } catch {
47
+ }
48
+ }
49
+ if (existsSync(GRAPES_CONFIG_FILE)) {
50
+ try {
51
+ return parseTOML(readFileSync(GRAPES_CONFIG_FILE, "utf-8"));
52
+ } catch {
53
+ }
54
+ }
55
+ return {};
56
+ }
57
+ function parseTOML(content) {
58
+ const config = {};
59
+ let section = "";
60
+ for (const line of content.split("\n")) {
61
+ const trimmed = line.trim();
62
+ if (!trimmed || trimmed.startsWith("#"))
63
+ continue;
64
+ const sectionMatch = trimmed.match(/^\[(.+)\]$/);
65
+ if (sectionMatch) {
66
+ section = sectionMatch[1];
67
+ continue;
68
+ }
69
+ const kvMatch = trimmed.match(/^(\w+)\s*=\s*"(.+)"$/);
70
+ if (kvMatch) {
71
+ const [, key, value] = kvMatch;
72
+ if (section === "defaults") {
73
+ config.defaults = config.defaults || {};
74
+ config.defaults[key] = value;
75
+ } else if (section === "agent") {
76
+ config.agent = config.agent || {};
77
+ config.agent[key] = value;
78
+ }
79
+ }
80
+ }
81
+ return config;
82
+ }
83
+ function saveConfig(config) {
84
+ ensureDir();
85
+ const lines = [];
86
+ if (config.defaults) {
87
+ lines.push("[defaults]");
88
+ for (const [key, value] of Object.entries(config.defaults)) {
89
+ if (value)
90
+ lines.push(`${key} = "${value}"`);
91
+ }
92
+ lines.push("");
93
+ }
94
+ if (config.agent) {
95
+ lines.push("[agent]");
96
+ for (const [key, value] of Object.entries(config.agent)) {
97
+ if (value)
98
+ lines.push(`${key} = "${value}"`);
99
+ }
100
+ lines.push("");
101
+ }
102
+ writeFileSync(CONFIG_FILE, lines.join("\n"), { mode: 384 });
103
+ }
104
+ function getIdpUrl(explicit) {
105
+ if (explicit)
106
+ return explicit;
107
+ if (process.env.APES_IDP)
108
+ return process.env.APES_IDP;
109
+ if (process.env.GRAPES_IDP)
110
+ return process.env.GRAPES_IDP;
111
+ const auth = loadAuth();
112
+ if (auth?.idp)
113
+ return auth.idp;
114
+ const config = loadConfig();
115
+ if (config.defaults?.idp)
116
+ return config.defaults.idp;
117
+ return null;
118
+ }
119
+ function getAuthToken() {
120
+ const auth = loadAuth();
121
+ if (!auth)
122
+ return null;
123
+ if (auth.expires_at && Date.now() / 1e3 > auth.expires_at - 30) {
124
+ return null;
125
+ }
126
+ return auth.access_token;
127
+ }
128
+ function getRequesterIdentity() {
129
+ return loadAuth()?.email ?? null;
130
+ }
131
+
132
+ // src/http.ts
133
+ var ApiError = class extends Error {
134
+ constructor(statusCode, message, problemDetails) {
135
+ super(message);
136
+ this.statusCode = statusCode;
137
+ this.problemDetails = problemDetails;
138
+ this.name = "ApiError";
139
+ }
140
+ };
141
+ var _discoveryCache = {};
142
+ async function discoverEndpoints(idpUrl) {
143
+ if (_discoveryCache[idpUrl]) {
144
+ return _discoveryCache[idpUrl];
145
+ }
146
+ try {
147
+ const response = await fetch(`${idpUrl}/.well-known/openid-configuration`);
148
+ if (response.ok) {
149
+ const data = await response.json();
150
+ _discoveryCache[idpUrl] = data;
151
+ return data;
152
+ }
153
+ } catch {
154
+ }
155
+ _discoveryCache[idpUrl] = {};
156
+ return {};
157
+ }
158
+ async function getGrantsEndpoint(idpUrl) {
159
+ const disco = await discoverEndpoints(idpUrl);
160
+ return disco.openape_grants_endpoint || `${idpUrl}/api/grants`;
161
+ }
162
+ async function getAgentChallengeEndpoint(idpUrl) {
163
+ const disco = await discoverEndpoints(idpUrl);
164
+ return disco.ddisa_agent_challenge_endpoint || `${idpUrl}/api/agent/challenge`;
165
+ }
166
+ async function getAgentAuthenticateEndpoint(idpUrl) {
167
+ const disco = await discoverEndpoints(idpUrl);
168
+ return disco.ddisa_agent_authenticate_endpoint || `${idpUrl}/api/agent/authenticate`;
169
+ }
170
+ async function getDelegationsEndpoint(idpUrl) {
171
+ const disco = await discoverEndpoints(idpUrl);
172
+ return disco.openape_delegations_endpoint || `${idpUrl}/api/delegations`;
173
+ }
174
+ async function apiFetch(path, options = {}) {
175
+ const token = options.token || getAuthToken();
176
+ if (!token) {
177
+ throw new Error("Not authenticated. Run `apes login` first.");
178
+ }
179
+ let url;
180
+ if (path.startsWith("http")) {
181
+ url = path;
182
+ } else {
183
+ const idp = options.idp || getIdpUrl();
184
+ if (!idp) {
185
+ throw new Error("No IdP URL configured. Run `apes login` first or pass --idp.");
186
+ }
187
+ url = `${idp}${path}`;
188
+ }
189
+ const headers = {
190
+ "Authorization": `Bearer ${token}`,
191
+ "Content-Type": "application/json"
192
+ };
193
+ const response = await fetch(url, {
194
+ method: options.method || "GET",
195
+ headers,
196
+ body: options.body ? JSON.stringify(options.body) : void 0
197
+ });
198
+ if (!response.ok) {
199
+ const contentType = response.headers.get("content-type") || "";
200
+ if (contentType.includes("application/problem+json") || contentType.includes("application/json")) {
201
+ try {
202
+ const problem = await response.json();
203
+ const message = problem.detail || problem.title || `${response.status} ${response.statusText}`;
204
+ throw new ApiError(response.status, message, problem);
205
+ } catch (e) {
206
+ if (e instanceof ApiError)
207
+ throw e;
208
+ }
209
+ }
210
+ const text = await response.text();
211
+ throw new ApiError(response.status, text || `${response.status} ${response.statusText}`);
212
+ }
213
+ return response.json();
214
+ }
215
+
216
+ export {
217
+ loadAuth,
218
+ saveAuth,
219
+ clearAuth,
220
+ loadConfig,
221
+ saveConfig,
222
+ getIdpUrl,
223
+ getAuthToken,
224
+ getRequesterIdentity,
225
+ ApiError,
226
+ discoverEndpoints,
227
+ getGrantsEndpoint,
228
+ getAgentChallengeEndpoint,
229
+ getAgentAuthenticateEndpoint,
230
+ getDelegationsEndpoint,
231
+ apiFetch
232
+ };
233
+ //# sourceMappingURL=chunk-2JEBWXMI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/http.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface AuthData {\n idp: string\n access_token: string\n refresh_token?: string\n email: string\n expires_at: number\n}\n\nexport interface ApesConfig {\n defaults?: {\n idp?: string\n approval?: string\n }\n agent?: {\n key?: string\n email?: string\n }\n}\n\nconst CONFIG_DIR = join(homedir(), '.config', 'apes')\nconst AUTH_FILE = join(CONFIG_DIR, 'auth.json')\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.toml')\n\nconst GRAPES_CONFIG_DIR = join(homedir(), '.config', 'grapes')\nconst GRAPES_AUTH_FILE = join(GRAPES_CONFIG_DIR, 'auth.json')\nconst GRAPES_CONFIG_FILE = join(GRAPES_CONFIG_DIR, 'config.toml')\n\nfunction ensureDir() {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true })\n }\n}\n\nexport function loadAuth(): AuthData | null {\n // Apes config first\n if (existsSync(AUTH_FILE)) {\n try {\n return JSON.parse(readFileSync(AUTH_FILE, 'utf-8'))\n }\n catch {}\n }\n\n // Fallback to grapes config\n if (existsSync(GRAPES_AUTH_FILE)) {\n try {\n return JSON.parse(readFileSync(GRAPES_AUTH_FILE, 'utf-8'))\n }\n catch {}\n }\n\n return null\n}\n\nexport function saveAuth(data: AuthData): void {\n ensureDir()\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 })\n}\n\nexport function clearAuth(): void {\n if (existsSync(AUTH_FILE)) {\n writeFileSync(AUTH_FILE, '', { mode: 0o600 })\n }\n}\n\nexport function loadConfig(): ApesConfig {\n // Apes config first\n if (existsSync(CONFIG_FILE)) {\n try {\n return parseTOML(readFileSync(CONFIG_FILE, 'utf-8'))\n }\n catch {}\n }\n\n // Fallback to grapes config\n if (existsSync(GRAPES_CONFIG_FILE)) {\n try {\n return parseTOML(readFileSync(GRAPES_CONFIG_FILE, 'utf-8'))\n }\n catch {}\n }\n\n return {}\n}\n\nfunction parseTOML(content: string): ApesConfig {\n const config: ApesConfig = {}\n let section = ''\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#'))\n continue\n\n const sectionMatch = trimmed.match(/^\\[(.+)\\]$/)\n if (sectionMatch) {\n section = sectionMatch[1]!\n continue\n }\n\n const kvMatch = trimmed.match(/^(\\w+)\\s*=\\s*\"(.+)\"$/)\n if (kvMatch) {\n const [, key, value] = kvMatch\n if (section === 'defaults') {\n config.defaults = config.defaults || {}\n ;(config.defaults as Record<string, string>)[key!] = value!\n }\n else if (section === 'agent') {\n config.agent = config.agent || {}\n ;(config.agent as Record<string, string>)[key!] = value!\n }\n }\n }\n\n return config\n}\n\nexport function saveConfig(config: ApesConfig): void {\n ensureDir()\n const lines: string[] = []\n\n if (config.defaults) {\n lines.push('[defaults]')\n for (const [key, value] of Object.entries(config.defaults)) {\n if (value)\n lines.push(`${key} = \"${value}\"`)\n }\n lines.push('')\n }\n\n if (config.agent) {\n lines.push('[agent]')\n for (const [key, value] of Object.entries(config.agent)) {\n if (value)\n lines.push(`${key} = \"${value}\"`)\n }\n lines.push('')\n }\n\n writeFileSync(CONFIG_FILE, lines.join('\\n'), { mode: 0o600 })\n}\n\nexport function getIdpUrl(explicit?: string): string | null {\n if (explicit)\n return explicit\n if (process.env.APES_IDP)\n return process.env.APES_IDP\n if (process.env.GRAPES_IDP)\n return process.env.GRAPES_IDP\n\n const auth = loadAuth()\n if (auth?.idp)\n return auth.idp\n\n const config = loadConfig()\n if (config.defaults?.idp)\n return config.defaults.idp\n\n return null\n}\n\nexport function getAuthToken(): string | null {\n const auth = loadAuth()\n if (!auth)\n return null\n\n // Check expiry (with 30s buffer)\n if (auth.expires_at && Date.now() / 1000 > auth.expires_at - 30) {\n return null // expired\n }\n\n return auth.access_token\n}\n\nexport function getRequesterIdentity(): string | null {\n return loadAuth()?.email ?? null\n}\n\nexport { CONFIG_DIR, AUTH_FILE }\n","import { getAuthToken, getIdpUrl } from './config'\n\nexport class ApiError extends Error {\n constructor(public statusCode: number, message: string, public problemDetails?: Record<string, unknown>) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\n// OIDC Discovery cache (one-time per CLI invocation)\nconst _discoveryCache: Record<string, Record<string, unknown>> = {}\n\nexport async function discoverEndpoints(idpUrl: string): Promise<Record<string, unknown>> {\n if (_discoveryCache[idpUrl]) {\n return _discoveryCache[idpUrl]\n }\n\n try {\n const response = await fetch(`${idpUrl}/.well-known/openid-configuration`)\n if (response.ok) {\n const data = await response.json() as Record<string, unknown>\n _discoveryCache[idpUrl] = data\n return data\n }\n }\n catch {}\n\n // Return empty if discovery fails (graceful degradation)\n _discoveryCache[idpUrl] = {}\n return {}\n}\n\nexport async function getGrantsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_grants_endpoint as string) || `${idpUrl}/api/grants`\n}\n\nexport async function getAgentChallengeEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_challenge_endpoint as string) || `${idpUrl}/api/agent/challenge`\n}\n\nexport async function getAgentAuthenticateEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_authenticate_endpoint as string) || `${idpUrl}/api/agent/authenticate`\n}\n\nexport async function getDelegationsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_delegations_endpoint as string) || `${idpUrl}/api/delegations`\n}\n\nexport async function apiFetch<T = unknown>(\n path: string,\n options: {\n method?: string\n body?: unknown\n idp?: string\n token?: string\n } = {},\n): Promise<T> {\n const token = options.token || getAuthToken()\n if (!token) {\n throw new Error('Not authenticated. Run `apes login` first.')\n }\n\n let url: string\n if (path.startsWith('http')) {\n url = path\n }\n else {\n const idp = options.idp || getIdpUrl()\n if (!idp) {\n throw new Error('No IdP URL configured. Run `apes login` first or pass --idp.')\n }\n url = `${idp}${path}`\n }\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n }\n\n const response = await fetch(url, {\n method: options.method || 'GET',\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n })\n\n if (!response.ok) {\n const contentType = response.headers.get('content-type') || ''\n\n // Parse RFC 7807 Problem Details\n if (contentType.includes('application/problem+json') || contentType.includes('application/json')) {\n try {\n const problem = await response.json() as Record<string, unknown>\n const message = (problem.detail as string) || (problem.title as string) || `${response.status} ${response.statusText}`\n throw new ApiError(response.status, message, problem)\n }\n catch (e) {\n if (e instanceof ApiError)\n throw e\n }\n }\n\n const text = await response.text()\n throw new ApiError(response.status, text || `${response.status} ${response.statusText}`)\n }\n\n return response.json() as Promise<T>\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/duration.ts
4
+ function parseDuration(value) {
5
+ const match = value.match(/^(\d+)\s*([smhd])$/);
6
+ if (!match) {
7
+ throw new Error(`Invalid duration format: "${value}". Use e.g. 30m, 1h, 7d`);
8
+ }
9
+ const amount = Number.parseInt(match[1], 10);
10
+ switch (match[2]) {
11
+ case "s":
12
+ return amount;
13
+ case "m":
14
+ return amount * 60;
15
+ case "h":
16
+ return amount * 3600;
17
+ case "d":
18
+ return amount * 86400;
19
+ default:
20
+ throw new Error(`Unknown duration unit: ${match[2]}`);
21
+ }
22
+ }
23
+
24
+ export {
25
+ parseDuration
26
+ };
27
+ //# sourceMappingURL=chunk-AGHP6MNV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/duration.ts"],"sourcesContent":["/**\n * Parse a human-readable duration string into seconds.\n * Supported formats: 30s, 5m, 1h, 7d\n */\nexport function parseDuration(value: string): number {\n const match = value.match(/^(\\d+)\\s*([smhd])$/)\n if (!match) {\n throw new Error(`Invalid duration format: \"${value}\". Use e.g. 30m, 1h, 7d`)\n }\n const amount = Number.parseInt(match[1]!, 10)\n switch (match[2]) {\n case 's': return amount\n case 'm': return amount * 60\n case 'h': return amount * 3600\n case 'd': return amount * 86400\n default: throw new Error(`Unknown duration unit: ${match[2]}`)\n }\n}\n"],"mappings":";;;AAIO,SAAS,cAAc,OAAuB;AACnD,QAAM,QAAQ,MAAM,MAAM,oBAAoB;AAC9C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,6BAA6B,KAAK,yBAAyB;AAAA,EAC7E;AACA,QAAM,SAAS,OAAO,SAAS,MAAM,CAAC,GAAI,EAAE;AAC5C,UAAQ,MAAM,CAAC,GAAG;AAAA,IAChB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO,SAAS;AAAA,IAC1B,KAAK;AAAK,aAAO,SAAS;AAAA,IAC1B,KAAK;AAAK,aAAO,SAAS;AAAA,IAC1B;AAAS,YAAM,IAAI,MAAM,0BAA0B,MAAM,CAAC,CAAC,EAAE;AAAA,EAC/D;AACF;","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }