@grantjs/cli 1.12.0 → 1.13.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/README.md +15 -2
- package/dist/api/authenticated.d.ts +11 -0
- package/dist/api/client.d.ts +11 -4
- package/dist/api/errors.d.ts +9 -0
- package/dist/api/transport.d.ts +15 -0
- package/dist/commands/api.d.ts +2 -0
- package/dist/commands/from-spec.d.ts +2 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/config/credentials.d.ts +29 -0
- package/dist/config/index.d.ts +0 -1
- package/dist/config/output.d.ts +3 -0
- package/dist/config/resolve-token.d.ts +4 -5
- package/dist/index.mjs +1736 -120
- package/dist/index.mjs.map +1 -1
- package/dist/spec/map-commands.d.ts +10 -0
- package/dist/spec/operations.d.ts +3 -0
- package/dist/spec/types.d.ts +8 -0
- package/dist/types/config.d.ts +5 -0
- package/package.json +9 -8
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import childProcess, { spawn } from "node:child_process";
|
|
4
4
|
import path, { join, resolve } from "node:path";
|
|
5
|
-
import fs, { existsSync } from "node:fs";
|
|
5
|
+
import fs, { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import process$1 from "node:process";
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -2964,7 +2964,302 @@ function useColor() {
|
|
|
2964
2964
|
}
|
|
2965
2965
|
new Command();
|
|
2966
2966
|
//#endregion
|
|
2967
|
+
//#region src/config/storage.ts
|
|
2968
|
+
var CONFIG_DIR_NAME = "grant";
|
|
2969
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
2970
|
+
var DEFAULT_PROFILE_NAME = "default";
|
|
2971
|
+
/**
|
|
2972
|
+
* Returns the platform-specific config directory for Grant CLI.
|
|
2973
|
+
* - Windows: %APPDATA%\grant
|
|
2974
|
+
* - Linux/macOS: $XDG_CONFIG_HOME/grant or ~/.config/grant
|
|
2975
|
+
*/
|
|
2976
|
+
function getConfigDir() {
|
|
2977
|
+
if (platform() === "win32") {
|
|
2978
|
+
const appData = process.env.APPDATA;
|
|
2979
|
+
if (!appData) return join(homedir(), "AppData", "Roaming", CONFIG_DIR_NAME);
|
|
2980
|
+
return join(appData, CONFIG_DIR_NAME);
|
|
2981
|
+
}
|
|
2982
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2983
|
+
if (xdg) return join(xdg, CONFIG_DIR_NAME);
|
|
2984
|
+
return join(homedir(), ".config", CONFIG_DIR_NAME);
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* Returns the path to the config file (config dir + config.json).
|
|
2988
|
+
*/
|
|
2989
|
+
function getConfigPath() {
|
|
2990
|
+
return join(getConfigDir(), CONFIG_FILE_NAME);
|
|
2991
|
+
}
|
|
2992
|
+
/** Detect legacy single-profile config (apiUrl at top level, no profiles). */
|
|
2993
|
+
function isLegacyConfig(data) {
|
|
2994
|
+
if (!data || typeof data !== "object") return false;
|
|
2995
|
+
const o = data;
|
|
2996
|
+
return typeof o.apiUrl === "string" && !("profiles" in o);
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Load config file from disk. Migrates legacy single-config to profiles shape.
|
|
3000
|
+
* Returns null if file does not exist or is invalid.
|
|
3001
|
+
*/
|
|
3002
|
+
async function loadConfigFile() {
|
|
3003
|
+
const path = getConfigPath();
|
|
3004
|
+
try {
|
|
3005
|
+
const raw = await readFile(path, "utf-8");
|
|
3006
|
+
const data = JSON.parse(raw);
|
|
3007
|
+
if (!data || typeof data !== "object") return null;
|
|
3008
|
+
if (isLegacyConfig(data)) {
|
|
3009
|
+
const file = {
|
|
3010
|
+
defaultProfile: DEFAULT_PROFILE_NAME,
|
|
3011
|
+
profiles: { [DEFAULT_PROFILE_NAME]: data }
|
|
3012
|
+
};
|
|
3013
|
+
await saveConfigFile(file);
|
|
3014
|
+
return file;
|
|
3015
|
+
}
|
|
3016
|
+
const file = data;
|
|
3017
|
+
if (typeof file.defaultProfile !== "string" || !file.profiles || typeof file.profiles !== "object") return null;
|
|
3018
|
+
return file;
|
|
3019
|
+
} catch {
|
|
3020
|
+
return null;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Save config file to disk. Creates config dir if needed. Sets file mode to 0o600 (owner read/write only).
|
|
3025
|
+
*/
|
|
3026
|
+
async function saveConfigFile(file) {
|
|
3027
|
+
const dir = getConfigDir();
|
|
3028
|
+
const path = getConfigPath();
|
|
3029
|
+
await mkdir(dir, {
|
|
3030
|
+
recursive: true,
|
|
3031
|
+
mode: 448
|
|
3032
|
+
});
|
|
3033
|
+
await writeFile(path, JSON.stringify(file, null, 2), {
|
|
3034
|
+
encoding: "utf-8",
|
|
3035
|
+
mode: 384,
|
|
3036
|
+
flag: "w"
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Resolve which profile name to use: explicit name, or file's default, or "default".
|
|
3041
|
+
*/
|
|
3042
|
+
function resolveProfileName(file, profileFlag) {
|
|
3043
|
+
if (profileFlag?.trim()) return profileFlag.trim();
|
|
3044
|
+
const fromEnv = process.env.GRANT_PROFILE?.trim();
|
|
3045
|
+
if (fromEnv) return fromEnv;
|
|
3046
|
+
return file.defaultProfile || "default";
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Get config for a profile. Returns null if profile does not exist.
|
|
3050
|
+
*/
|
|
3051
|
+
function getProfileConfig(file, profileName) {
|
|
3052
|
+
return file.profiles[profileName] ?? null;
|
|
3053
|
+
}
|
|
3054
|
+
/**
|
|
3055
|
+
* List profile names. Returns empty array if no file.
|
|
3056
|
+
*/
|
|
3057
|
+
function listProfileNames(file) {
|
|
3058
|
+
if (!file?.profiles) return [];
|
|
3059
|
+
return Object.keys(file.profiles);
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Load config file and return the resolved profile's config plus file and name.
|
|
3063
|
+
* Use when you need to read and then update (save) the file. Returns null if no file or profile does not exist.
|
|
3064
|
+
*/
|
|
3065
|
+
async function loadProfile(profileFlag) {
|
|
3066
|
+
const file = await loadConfigFile();
|
|
3067
|
+
if (!file) return null;
|
|
3068
|
+
const profileName = resolveProfileName(file, profileFlag);
|
|
3069
|
+
const config = getProfileConfig(file, profileName);
|
|
3070
|
+
if (!config) return null;
|
|
3071
|
+
return {
|
|
3072
|
+
file,
|
|
3073
|
+
config,
|
|
3074
|
+
profileName
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
//#endregion
|
|
3078
|
+
//#region src/config/credentials.ts
|
|
3079
|
+
var ENV = {
|
|
3080
|
+
profile: "GRANT_PROFILE",
|
|
3081
|
+
apiUrl: "GRANT_API_URL",
|
|
3082
|
+
clientId: "GRANT_CLIENT_ID",
|
|
3083
|
+
clientSecret: "GRANT_CLIENT_SECRET",
|
|
3084
|
+
scopeTenant: "GRANT_SCOPE_TENANT",
|
|
3085
|
+
scopeId: "GRANT_SCOPE_ID",
|
|
3086
|
+
originVerify: "GRANT_ORIGIN_VERIFY",
|
|
3087
|
+
accessToken: "GRANT_ACCESS_TOKEN"
|
|
3088
|
+
};
|
|
3089
|
+
function env(name) {
|
|
3090
|
+
return process.env[name]?.trim() || void 0;
|
|
3091
|
+
}
|
|
3092
|
+
function mergeScope(config, flags) {
|
|
3093
|
+
const id = flags.scopeId?.trim() || env(ENV.scopeId) || config.selectedScope?.id;
|
|
3094
|
+
const tenant = flags.tenant?.trim() || env(ENV.scopeTenant) || config.selectedScope?.tenant;
|
|
3095
|
+
if (id && tenant) return {
|
|
3096
|
+
id,
|
|
3097
|
+
tenant
|
|
3098
|
+
};
|
|
3099
|
+
return config.selectedScope;
|
|
3100
|
+
}
|
|
3101
|
+
function envApiKeyConfig(apiUrl) {
|
|
3102
|
+
const clientId = env(ENV.clientId);
|
|
3103
|
+
const clientSecret = env(ENV.clientSecret);
|
|
3104
|
+
const tenant = env(ENV.scopeTenant);
|
|
3105
|
+
const id = env(ENV.scopeId);
|
|
3106
|
+
if (!clientId || !clientSecret || !tenant || !id) return null;
|
|
3107
|
+
const scope = {
|
|
3108
|
+
tenant,
|
|
3109
|
+
id
|
|
3110
|
+
};
|
|
3111
|
+
return {
|
|
3112
|
+
apiUrl,
|
|
3113
|
+
authMethod: "api-key",
|
|
3114
|
+
apiKey: {
|
|
3115
|
+
clientId,
|
|
3116
|
+
clientSecret,
|
|
3117
|
+
scope
|
|
3118
|
+
},
|
|
3119
|
+
selectedScope: scope,
|
|
3120
|
+
originVerifySecret: env(ENV.originVerify)
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
function envTokenConfig(apiUrl) {
|
|
3124
|
+
const token = env(ENV.accessToken);
|
|
3125
|
+
if (!token) return null;
|
|
3126
|
+
const tenant = env(ENV.scopeTenant);
|
|
3127
|
+
const id = env(ENV.scopeId);
|
|
3128
|
+
return {
|
|
3129
|
+
apiUrl,
|
|
3130
|
+
authMethod: "session",
|
|
3131
|
+
session: { token },
|
|
3132
|
+
selectedScope: tenant && id ? {
|
|
3133
|
+
tenant,
|
|
3134
|
+
id
|
|
3135
|
+
} : void 0,
|
|
3136
|
+
originVerifySecret: env(ENV.originVerify)
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Resolve API URL, credentials, and scope.
|
|
3141
|
+
* Order: flags → GRANT_* env → named profile file.
|
|
3142
|
+
*/
|
|
3143
|
+
async function resolveRuntimeContext(flags = {}) {
|
|
3144
|
+
const loaded = await loadProfile(flags.profile?.trim() || env(ENV.profile));
|
|
3145
|
+
const apiUrl = env(ENV.apiUrl) || loaded?.config.apiUrl;
|
|
3146
|
+
if (!apiUrl) throw new Error("No Grant API URL. Set GRANT_API_URL, pass a profile with apiUrl, or run \"grant start\".");
|
|
3147
|
+
const envToken = envTokenConfig(apiUrl);
|
|
3148
|
+
const envKey = envApiKeyConfig(apiUrl);
|
|
3149
|
+
const base = envToken ?? envKey ?? loaded?.config;
|
|
3150
|
+
if (!base) throw new Error("No credentials. Set GRANT_ACCESS_TOKEN or GRANT_CLIENT_ID/GRANT_CLIENT_SECRET, or run \"grant start\".");
|
|
3151
|
+
const config = {
|
|
3152
|
+
...base,
|
|
3153
|
+
apiUrl,
|
|
3154
|
+
originVerifySecret: env(ENV.originVerify) || base.originVerifySecret,
|
|
3155
|
+
selectedScope: mergeScope(base, flags)
|
|
3156
|
+
};
|
|
3157
|
+
const accessToken = env(ENV.accessToken);
|
|
3158
|
+
if (accessToken && !config.session?.token) {
|
|
3159
|
+
config.authMethod = "session";
|
|
3160
|
+
config.session = { token: accessToken };
|
|
3161
|
+
}
|
|
3162
|
+
return {
|
|
3163
|
+
apiUrl,
|
|
3164
|
+
authMethod: env(ENV.accessToken) ? "token" : config.authMethod,
|
|
3165
|
+
config,
|
|
3166
|
+
profileName: loaded?.profileName ?? null,
|
|
3167
|
+
scope: config.selectedScope,
|
|
3168
|
+
originVerifySecret: config.originVerifySecret
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
var CliError = class extends Error {
|
|
3172
|
+
exitCode;
|
|
3173
|
+
constructor(message, exitCode = 1) {
|
|
3174
|
+
super(message);
|
|
3175
|
+
this.name = "CliError";
|
|
3176
|
+
this.exitCode = exitCode;
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
function isAuthFailure(status, message) {
|
|
3180
|
+
if (status === 401 || status === 403) return true;
|
|
3181
|
+
return /unauthorized|forbidden|invalid or expired|no credentials/i.test(message);
|
|
3182
|
+
}
|
|
3183
|
+
function handleCliError(err) {
|
|
3184
|
+
if (err instanceof CliError) {
|
|
3185
|
+
console.error(err.message);
|
|
3186
|
+
process.exit(err.exitCode);
|
|
3187
|
+
}
|
|
3188
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3189
|
+
console.error(message);
|
|
3190
|
+
process.exit(isAuthFailure(0, message) ? 2 : 1);
|
|
3191
|
+
}
|
|
3192
|
+
//#endregion
|
|
3193
|
+
//#region src/api/transport.ts
|
|
3194
|
+
var DEBUG = process.env.GRANT_CLI_DEBUG === "1" || process.env.GRANT_CLI_DEBUG === "true";
|
|
3195
|
+
function joinUrl(apiUrl, path) {
|
|
3196
|
+
const base = apiUrl.replace(/\/+$/, "");
|
|
3197
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
3198
|
+
return new URL(base + suffix);
|
|
3199
|
+
}
|
|
3200
|
+
function parseErrorMessage(status, text) {
|
|
3201
|
+
let message = `Request failed (${status})`;
|
|
3202
|
+
try {
|
|
3203
|
+
const data = JSON.parse(text);
|
|
3204
|
+
if (data?.error?.message) message = data.error.message;
|
|
3205
|
+
else if (data?.message) message = data.message;
|
|
3206
|
+
if (data?.reason) message += ` — ${data.reason}`;
|
|
3207
|
+
} catch {
|
|
3208
|
+
if (text) message = text.slice(0, 200);
|
|
3209
|
+
}
|
|
3210
|
+
return message;
|
|
3211
|
+
}
|
|
3212
|
+
async function apiRequest(req) {
|
|
3213
|
+
const url = joinUrl(req.apiUrl, req.path);
|
|
3214
|
+
if (req.query) for (const [key, value] of Object.entries(req.query)) {
|
|
3215
|
+
if (value === void 0 || value === "") continue;
|
|
3216
|
+
url.searchParams.set(key, String(value));
|
|
3217
|
+
}
|
|
3218
|
+
const headers = {
|
|
3219
|
+
Accept: "application/json",
|
|
3220
|
+
...req.extraHeaders
|
|
3221
|
+
};
|
|
3222
|
+
if (req.token) headers.Authorization = `Bearer ${req.token}`;
|
|
3223
|
+
if (req.originVerifySecret) headers["x-origin-verify"] = req.originVerifySecret;
|
|
3224
|
+
const init = {
|
|
3225
|
+
method: req.method.toUpperCase(),
|
|
3226
|
+
headers
|
|
3227
|
+
};
|
|
3228
|
+
if (req.body !== void 0) {
|
|
3229
|
+
headers["Content-Type"] = "application/json";
|
|
3230
|
+
init.body = JSON.stringify(req.body);
|
|
3231
|
+
}
|
|
3232
|
+
if (DEBUG) console.error(`[Grant CLI] ${init.method} ${url.href}`);
|
|
3233
|
+
let res;
|
|
3234
|
+
try {
|
|
3235
|
+
res = await fetch(url.href, init);
|
|
3236
|
+
} catch (err) {
|
|
3237
|
+
throw new CliError(`Request failed: ${err instanceof Error ? err.message : String(err)}. URL: ${url.href}`, 1);
|
|
3238
|
+
}
|
|
3239
|
+
const text = await res.text();
|
|
3240
|
+
let parsed = void 0;
|
|
3241
|
+
if (text) try {
|
|
3242
|
+
parsed = JSON.parse(text);
|
|
3243
|
+
} catch {
|
|
3244
|
+
parsed = text;
|
|
3245
|
+
}
|
|
3246
|
+
if (!res.ok) {
|
|
3247
|
+
const message = parseErrorMessage(res.status, text);
|
|
3248
|
+
throw new CliError(message, isAuthFailure(res.status, message) ? 2 : 1);
|
|
3249
|
+
}
|
|
3250
|
+
const envelope = parsed;
|
|
3251
|
+
const data = envelope && typeof envelope === "object" && envelope !== null && "data" in envelope ? envelope.data ?? envelope : envelope;
|
|
3252
|
+
return {
|
|
3253
|
+
status: res.status,
|
|
3254
|
+
data
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
//#endregion
|
|
2967
3258
|
//#region src/api/client.ts
|
|
3259
|
+
/**
|
|
3260
|
+
* Minimal REST client for Grant API.
|
|
3261
|
+
* Used by start (token exchange) and generate-types (resources/permissions).
|
|
3262
|
+
*/
|
|
2968
3263
|
process.env.GRANT_CLI_DEBUG === "1" || process.env.GRANT_CLI_DEBUG;
|
|
2969
3264
|
/** Log request URL, status, and response body when an API call fails (always on failure). */
|
|
2970
3265
|
function logFailedRequest(label, url, status, bodyText, extra) {
|
|
@@ -3021,6 +3316,23 @@ async function exchangeApiKey(baseUrl, body) {
|
|
|
3021
3316
|
return data.data;
|
|
3022
3317
|
}
|
|
3023
3318
|
/**
|
|
3319
|
+
* Refresh a session. Cookie is preferred by browsers; CLI sends the stored refresh token in the body.
|
|
3320
|
+
*/
|
|
3321
|
+
async function refreshSession(baseUrl, refreshToken, originVerifySecret) {
|
|
3322
|
+
const { data } = await apiRequest({
|
|
3323
|
+
apiUrl: baseUrl,
|
|
3324
|
+
method: "POST",
|
|
3325
|
+
path: "/api/auth/refresh",
|
|
3326
|
+
originVerifySecret,
|
|
3327
|
+
body: { refreshToken }
|
|
3328
|
+
});
|
|
3329
|
+
if (!data?.accessToken) throw new Error("Invalid refresh response: missing accessToken");
|
|
3330
|
+
return {
|
|
3331
|
+
accessToken: data.accessToken,
|
|
3332
|
+
refreshToken: data.refreshToken
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3024
3336
|
* Login with email and password. POST {baseUrl}/api/auth/login
|
|
3025
3337
|
* Returns access token, refresh token, and primary account (personal).
|
|
3026
3338
|
*/
|
|
@@ -3102,7 +3414,7 @@ async function exchangeCliCallback(baseUrl, code) {
|
|
|
3102
3414
|
* Fetch organizations (paginated). Requires Bearer token. GET /api/organizations?scopeId=&tenant=
|
|
3103
3415
|
* Scope is the account context (user's personal account id, tenant 'account').
|
|
3104
3416
|
*/
|
|
3105
|
-
async function fetchOrganizations(baseUrl, accessToken, scope) {
|
|
3417
|
+
async function fetchOrganizations(baseUrl, accessToken, scope, originVerifySecret) {
|
|
3106
3418
|
const items = [];
|
|
3107
3419
|
let page = 1;
|
|
3108
3420
|
let hasNextPage = true;
|
|
@@ -3112,7 +3424,9 @@ async function fetchOrganizations(baseUrl, accessToken, scope) {
|
|
|
3112
3424
|
url.searchParams.set("tenant", scope.tenant);
|
|
3113
3425
|
url.searchParams.set("page", String(page));
|
|
3114
3426
|
url.searchParams.set("limit", String(DEFAULT_PAGE_SIZE));
|
|
3115
|
-
const
|
|
3427
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
3428
|
+
if (originVerifySecret) headers["x-origin-verify"] = originVerifySecret;
|
|
3429
|
+
const res = await fetch(url.href, { headers });
|
|
3116
3430
|
if (!res.ok) {
|
|
3117
3431
|
const text = await res.text();
|
|
3118
3432
|
logFailedRequest("Organizations request", url.href, res.status, text, {
|
|
@@ -3142,7 +3456,7 @@ async function fetchOrganizations(baseUrl, accessToken, scope) {
|
|
|
3142
3456
|
* Fetch projects for a scope (account or organization). Paginated. Requires Bearer token.
|
|
3143
3457
|
* GET /api/projects?scopeId=&tenant=
|
|
3144
3458
|
*/
|
|
3145
|
-
async function fetchProjects(baseUrl, accessToken, scope) {
|
|
3459
|
+
async function fetchProjects(baseUrl, accessToken, scope, originVerifySecret) {
|
|
3146
3460
|
const items = [];
|
|
3147
3461
|
let page = 1;
|
|
3148
3462
|
let hasNextPage = true;
|
|
@@ -3152,7 +3466,9 @@ async function fetchProjects(baseUrl, accessToken, scope) {
|
|
|
3152
3466
|
url.searchParams.set("tenant", scope.tenant);
|
|
3153
3467
|
url.searchParams.set("page", String(page));
|
|
3154
3468
|
url.searchParams.set("limit", String(DEFAULT_PAGE_SIZE));
|
|
3155
|
-
const
|
|
3469
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
3470
|
+
if (originVerifySecret) headers["x-origin-verify"] = originVerifySecret;
|
|
3471
|
+
const res = await fetch(url.href, { headers });
|
|
3156
3472
|
if (!res.ok) {
|
|
3157
3473
|
const text = await res.text();
|
|
3158
3474
|
logFailedRequest("Projects request", url.href, res.status, text, {
|
|
@@ -3182,7 +3498,7 @@ var DEFAULT_PAGE_SIZE = 50;
|
|
|
3182
3498
|
/**
|
|
3183
3499
|
* Fetch all resources for a scope (paginated). Requires Bearer token.
|
|
3184
3500
|
*/
|
|
3185
|
-
async function fetchResources(baseUrl, accessToken, scope) {
|
|
3501
|
+
async function fetchResources(baseUrl, accessToken, scope, originVerifySecret) {
|
|
3186
3502
|
const items = [];
|
|
3187
3503
|
let page = 1;
|
|
3188
3504
|
let hasNextPage = true;
|
|
@@ -3192,7 +3508,9 @@ async function fetchResources(baseUrl, accessToken, scope) {
|
|
|
3192
3508
|
url.searchParams.set("tenant", scope.tenant);
|
|
3193
3509
|
url.searchParams.set("page", String(page));
|
|
3194
3510
|
url.searchParams.set("limit", String(DEFAULT_PAGE_SIZE));
|
|
3195
|
-
const
|
|
3511
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
3512
|
+
if (originVerifySecret) headers["x-origin-verify"] = originVerifySecret;
|
|
3513
|
+
const res = await fetch(url.href, { headers });
|
|
3196
3514
|
if (!res.ok) {
|
|
3197
3515
|
const text = await res.text();
|
|
3198
3516
|
let msg = `Resources request failed (${res.status})`;
|
|
@@ -3216,7 +3534,7 @@ async function fetchResources(baseUrl, accessToken, scope) {
|
|
|
3216
3534
|
/**
|
|
3217
3535
|
* Fetch all permissions for a scope (paginated). Requires Bearer token.
|
|
3218
3536
|
*/
|
|
3219
|
-
async function fetchPermissions(baseUrl, accessToken, scope) {
|
|
3537
|
+
async function fetchPermissions(baseUrl, accessToken, scope, originVerifySecret) {
|
|
3220
3538
|
const items = [];
|
|
3221
3539
|
let page = 1;
|
|
3222
3540
|
let hasNextPage = true;
|
|
@@ -3226,7 +3544,9 @@ async function fetchPermissions(baseUrl, accessToken, scope) {
|
|
|
3226
3544
|
url.searchParams.set("tenant", scope.tenant);
|
|
3227
3545
|
url.searchParams.set("page", String(page));
|
|
3228
3546
|
url.searchParams.set("limit", String(DEFAULT_PAGE_SIZE));
|
|
3229
|
-
const
|
|
3547
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
3548
|
+
if (originVerifySecret) headers["x-origin-verify"] = originVerifySecret;
|
|
3549
|
+
const res = await fetch(url.href, { headers });
|
|
3230
3550
|
if (!res.ok) {
|
|
3231
3551
|
const text = await res.text();
|
|
3232
3552
|
let msg = `Permissions request failed (${res.status})`;
|
|
@@ -3250,13 +3570,13 @@ async function fetchPermissions(baseUrl, accessToken, scope) {
|
|
|
3250
3570
|
//#endregion
|
|
3251
3571
|
//#region src/config/resolve-token.ts
|
|
3252
3572
|
/**
|
|
3253
|
-
* Resolve a valid access token from stored config.
|
|
3254
|
-
*
|
|
3255
|
-
*
|
|
3256
|
-
* - API key: exchanges clientId + clientSecret for a fresh token (no credentials stored after exchange).
|
|
3257
|
-
* - Session: returns the stored access token. When it expires, the user must run "grant start" again to re-authenticate.
|
|
3573
|
+
* Resolve a valid access token from env or stored config.
|
|
3574
|
+
* API key: exchanges clientId + clientSecret for a fresh token.
|
|
3575
|
+
* Session: returns the stored access token. GRANT_ACCESS_TOKEN wins when set.
|
|
3258
3576
|
*/
|
|
3259
3577
|
async function resolveAccessToken(config) {
|
|
3578
|
+
const envToken = process.env[ENV.accessToken]?.trim();
|
|
3579
|
+
if (envToken) return envToken;
|
|
3260
3580
|
if (config.authMethod === "api-key" && config.apiKey) {
|
|
3261
3581
|
const { accessToken } = await exchangeApiKey(config.apiUrl, {
|
|
3262
3582
|
clientId: config.apiKey.clientId,
|
|
@@ -3268,114 +3588,127 @@ async function resolveAccessToken(config) {
|
|
|
3268
3588
|
if (config.authMethod === "session" && config.session?.token) return config.session.token;
|
|
3269
3589
|
throw new Error("No credentials in config. Run \"grant start\" to set up authentication.");
|
|
3270
3590
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3591
|
+
async function refreshAndPersistSession(config, profileName) {
|
|
3592
|
+
const refreshToken = config.session?.refreshToken;
|
|
3593
|
+
if (!refreshToken) throw new Error("Session expired. Run \"grant start\" again or use an api-key profile.");
|
|
3594
|
+
const rotated = await refreshSession(config.apiUrl, refreshToken, config.originVerifySecret);
|
|
3595
|
+
config.session = {
|
|
3596
|
+
token: rotated.accessToken,
|
|
3597
|
+
refreshToken: rotated.refreshToken ?? refreshToken
|
|
3598
|
+
};
|
|
3599
|
+
if (profileName) {
|
|
3600
|
+
const loaded = await loadProfile(profileName);
|
|
3601
|
+
if (loaded) {
|
|
3602
|
+
loaded.config.session = config.session;
|
|
3603
|
+
loaded.file.profiles[loaded.profileName] = loaded.config;
|
|
3604
|
+
await saveConfigFile(loaded.file);
|
|
3605
|
+
}
|
|
3286
3606
|
}
|
|
3287
|
-
|
|
3288
|
-
if (xdg) return join(xdg, CONFIG_DIR_NAME);
|
|
3289
|
-
return join(homedir(), ".config", CONFIG_DIR_NAME);
|
|
3290
|
-
}
|
|
3291
|
-
/**
|
|
3292
|
-
* Returns the path to the config file (config dir + config.json).
|
|
3293
|
-
*/
|
|
3294
|
-
function getConfigPath() {
|
|
3295
|
-
return join(getConfigDir(), CONFIG_FILE_NAME);
|
|
3296
|
-
}
|
|
3297
|
-
/** Detect legacy single-profile config (apiUrl at top level, no profiles). */
|
|
3298
|
-
function isLegacyConfig(data) {
|
|
3299
|
-
if (!data || typeof data !== "object") return false;
|
|
3300
|
-
const o = data;
|
|
3301
|
-
return typeof o.apiUrl === "string" && !("profiles" in o);
|
|
3607
|
+
return rotated.accessToken;
|
|
3302
3608
|
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
const
|
|
3609
|
+
//#endregion
|
|
3610
|
+
//#region src/api/authenticated.ts
|
|
3611
|
+
var REAUTH_HINT = "Session expired or unauthorized. Run \"grant start\" again, or use an api-key profile / GRANT_ACCESS_TOKEN.";
|
|
3612
|
+
async function authenticatedRequest(req) {
|
|
3613
|
+
const ctx = await resolveRuntimeContext(req.flags);
|
|
3614
|
+
const query = { ...req.query };
|
|
3615
|
+
if (req.injectScope !== false && ctx.scope) {
|
|
3616
|
+
query.scopeId ??= ctx.scope.id;
|
|
3617
|
+
query.tenant ??= ctx.scope.tenant;
|
|
3618
|
+
}
|
|
3619
|
+
const send = (token) => apiRequest({
|
|
3620
|
+
apiUrl: ctx.apiUrl,
|
|
3621
|
+
method: req.method,
|
|
3622
|
+
path: req.path,
|
|
3623
|
+
token,
|
|
3624
|
+
originVerifySecret: ctx.originVerifySecret,
|
|
3625
|
+
query,
|
|
3626
|
+
body: req.body
|
|
3627
|
+
});
|
|
3628
|
+
let token = await resolveAccessToken(ctx.config);
|
|
3309
3629
|
try {
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
if (!
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
await
|
|
3319
|
-
|
|
3630
|
+
return await send(token);
|
|
3631
|
+
} catch (err) {
|
|
3632
|
+
if (!(err instanceof CliError && err.exitCode === 2 && ctx.config.authMethod === "session" && Boolean(ctx.config.session?.refreshToken))) {
|
|
3633
|
+
if (err instanceof CliError && err.exitCode === 2) throw new CliError(REAUTH_HINT, 2);
|
|
3634
|
+
throw err;
|
|
3635
|
+
}
|
|
3636
|
+
try {
|
|
3637
|
+
token = await refreshAndPersistSession(ctx.config, ctx.profileName);
|
|
3638
|
+
return await send(token);
|
|
3639
|
+
} catch (refreshErr) {
|
|
3640
|
+
if (refreshErr instanceof CliError && refreshErr.exitCode === 2) throw new CliError(REAUTH_HINT, 2);
|
|
3641
|
+
throw refreshErr;
|
|
3320
3642
|
}
|
|
3321
|
-
const file = data;
|
|
3322
|
-
if (typeof file.defaultProfile !== "string" || !file.profiles || typeof file.profiles !== "object") return null;
|
|
3323
|
-
return file;
|
|
3324
|
-
} catch {
|
|
3325
|
-
return null;
|
|
3326
3643
|
}
|
|
3327
3644
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
const dir = getConfigDir();
|
|
3333
|
-
const path = getConfigPath();
|
|
3334
|
-
await mkdir(dir, {
|
|
3335
|
-
recursive: true,
|
|
3336
|
-
mode: 448
|
|
3337
|
-
});
|
|
3338
|
-
await writeFile(path, JSON.stringify(file, null, 2), {
|
|
3339
|
-
encoding: "utf-8",
|
|
3340
|
-
mode: 384,
|
|
3341
|
-
flag: "w"
|
|
3342
|
-
});
|
|
3645
|
+
//#endregion
|
|
3646
|
+
//#region src/config/output.ts
|
|
3647
|
+
function defaultOutputFormat() {
|
|
3648
|
+
return process.stdout.isTTY ? "text" : "json";
|
|
3343
3649
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
if (
|
|
3349
|
-
|
|
3650
|
+
function parseOutputFormat(value) {
|
|
3651
|
+
if (!value) return defaultOutputFormat();
|
|
3652
|
+
const normalized = value.trim().toLowerCase();
|
|
3653
|
+
if (normalized === "json" || normalized === "text") return normalized;
|
|
3654
|
+
if (normalized === "table") return "text";
|
|
3655
|
+
throw new Error(`Unknown --output format "${value}". Use json or text.`);
|
|
3350
3656
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3657
|
+
function printOutput(data, format) {
|
|
3658
|
+
if (format === "json") {
|
|
3659
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
if (typeof data === "string") {
|
|
3663
|
+
console.log(data);
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3356
3667
|
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3668
|
+
//#endregion
|
|
3669
|
+
//#region src/commands/api.ts
|
|
3670
|
+
function parseQuery$1(pairs) {
|
|
3671
|
+
const query = {};
|
|
3672
|
+
for (const pair of pairs ?? []) {
|
|
3673
|
+
const eq = pair.indexOf("=");
|
|
3674
|
+
if (eq <= 0) throw new Error(`Invalid --query "${pair}". Use key=value.`);
|
|
3675
|
+
query[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
3676
|
+
}
|
|
3677
|
+
return query;
|
|
3363
3678
|
}
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3679
|
+
function parseBody(options) {
|
|
3680
|
+
const raw = options.bodyFile ? readFileSync(options.bodyFile, "utf-8") : options.body;
|
|
3681
|
+
if (raw === void 0) return void 0;
|
|
3682
|
+
const trimmed = raw.trim();
|
|
3683
|
+
if (!trimmed) return void 0;
|
|
3684
|
+
if (trimmed === "-") {
|
|
3685
|
+
const stdin = readFileSync(0, "utf-8");
|
|
3686
|
+
return stdin.trim() ? JSON.parse(stdin) : void 0;
|
|
3687
|
+
}
|
|
3688
|
+
return JSON.parse(trimmed);
|
|
3689
|
+
}
|
|
3690
|
+
function createApiCommand(program) {
|
|
3691
|
+
program.command("api").description("Call any Grant REST path with the resolved profile (method + path)").argument("<method>", "HTTP method (GET, POST, PATCH, PUT, DELETE)").argument("<path>", "Path beginning with /api/…").option("-p, --profile <name>", "Profile to use (or GRANT_PROFILE)").option("--output <format>", "json or text").option("--scope-id <id>", "Override selected scope id").option("--tenant <tenant>", "Override selected scope tenant").option("-q, --query <pair...>", "Query pairs (key=value). Repeatable").option("--body <json>", "JSON body (use - to read stdin)").option("--body-file <path>", "Read JSON body from a file").option("--no-scope", "Do not inject profile scopeId/tenant query params").action(async (method, path, options) => {
|
|
3692
|
+
try {
|
|
3693
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
3694
|
+
const format = parseOutputFormat(options.output);
|
|
3695
|
+
const { data } = await authenticatedRequest({
|
|
3696
|
+
flags: {
|
|
3697
|
+
profile: options.profile,
|
|
3698
|
+
scopeId: options.scopeId,
|
|
3699
|
+
tenant: options.tenant
|
|
3700
|
+
},
|
|
3701
|
+
method,
|
|
3702
|
+
path,
|
|
3703
|
+
query: parseQuery$1(options.query),
|
|
3704
|
+
body: parseBody(options),
|
|
3705
|
+
injectScope: options.scope !== false
|
|
3706
|
+
});
|
|
3707
|
+
printOutput(data, format);
|
|
3708
|
+
} catch (err) {
|
|
3709
|
+
handleCliError(err);
|
|
3710
|
+
}
|
|
3711
|
+
});
|
|
3379
3712
|
}
|
|
3380
3713
|
//#endregion
|
|
3381
3714
|
//#region src/commands/config-cmd.ts
|
|
@@ -3443,8 +3776,9 @@ function createConfigCommand(program) {
|
|
|
3443
3776
|
console.log("Auth method:", config.authMethod);
|
|
3444
3777
|
if (config.selectedScope) console.log("Selected scope:", `${config.selectedScope.tenant}:${config.selectedScope.id}`);
|
|
3445
3778
|
if (config.generateTypesOutputPath) console.log("Generate-types output:", config.generateTypesOutputPath);
|
|
3779
|
+
if (config.originVerifySecret) console.log("Origin-verify: set");
|
|
3446
3780
|
});
|
|
3447
|
-
const setCmd = configCmd.command("set").description("Set a config value for a profile (use a subcommand: api-url, auth-method, credentials, scope, generate-types-output, default-profile)").option("-p, --profile <name>", "Profile to update (default: default profile)");
|
|
3781
|
+
const setCmd = configCmd.command("set").description("Set a config value for a profile (use a subcommand: api-url, auth-method, credentials, scope, generate-types-output, origin-verify, default-profile)").option("-p, --profile <name>", "Profile to update (default: default profile)");
|
|
3448
3782
|
setCmd.command("api-url <url>").description("Set the Grant API base URL (e.g. http://localhost:4000)").action(async (url, cmd) => {
|
|
3449
3783
|
const profileFlag = cmd.parent?.opts?.()?.profile;
|
|
3450
3784
|
const { file, config, profileName } = await requireProfile(profileFlag);
|
|
@@ -3565,6 +3899,22 @@ function createConfigCommand(program) {
|
|
|
3565
3899
|
await saveConfigFile(file);
|
|
3566
3900
|
console.log("generate-types-output set to", config.generateTypesOutputPath, "(profile:", profileName + ")");
|
|
3567
3901
|
});
|
|
3902
|
+
setCmd.command("origin-verify [secret]").description("Set x-origin-verify secret (only when apiUrl is the raw API origin). Empty clears it.").action(async (secret, cmd) => {
|
|
3903
|
+
const profileFlag = cmd.parent?.opts?.()?.profile;
|
|
3904
|
+
const { file, config, profileName } = await requireProfile(profileFlag);
|
|
3905
|
+
const trimmed = secret?.trim() ?? "";
|
|
3906
|
+
if (!trimmed) {
|
|
3907
|
+
delete config.originVerifySecret;
|
|
3908
|
+
file.profiles[profileName] = config;
|
|
3909
|
+
await saveConfigFile(file);
|
|
3910
|
+
console.log("origin-verify cleared (profile:", profileName + ")");
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
config.originVerifySecret = trimmed;
|
|
3914
|
+
file.profiles[profileName] = config;
|
|
3915
|
+
await saveConfigFile(file);
|
|
3916
|
+
console.log("origin-verify set (profile:", profileName + ")");
|
|
3917
|
+
});
|
|
3568
3918
|
setCmd.command("default-profile <name>").description("Set the default profile (used when --profile is not passed)").action(async (name) => {
|
|
3569
3919
|
const file = await loadConfigFile();
|
|
3570
3920
|
if (!file) {
|
|
@@ -3582,6 +3932,1231 @@ function createConfigCommand(program) {
|
|
|
3582
3932
|
});
|
|
3583
3933
|
}
|
|
3584
3934
|
//#endregion
|
|
3935
|
+
//#region src/spec/map-commands.ts
|
|
3936
|
+
function kebab(value) {
|
|
3937
|
+
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
3938
|
+
}
|
|
3939
|
+
function groupFromTag(tag, path) {
|
|
3940
|
+
if (tag) return kebab(tag);
|
|
3941
|
+
const first = path.replace(/^\/api\/?/, "").split("/").find(Boolean);
|
|
3942
|
+
return first ? kebab(first) : "api";
|
|
3943
|
+
}
|
|
3944
|
+
var SINGLETONS = /* @__PURE__ */ new Set([
|
|
3945
|
+
"me",
|
|
3946
|
+
"config",
|
|
3947
|
+
"jwks"
|
|
3948
|
+
]);
|
|
3949
|
+
function deriveVerb(method, path) {
|
|
3950
|
+
const parts = path.replace(/^\/api\/?/, "").split("/").filter(Boolean);
|
|
3951
|
+
const staticRest = parts.slice(1).filter((part) => !part.startsWith("{"));
|
|
3952
|
+
const lastIsParam = (parts.at(-1) ?? "").startsWith("{");
|
|
3953
|
+
const verbLike = staticRest.at(-1);
|
|
3954
|
+
if (verbLike && /^(revoke|rotate|confirm|read|read-all)$/i.test(verbLike)) return kebab(verbLike);
|
|
3955
|
+
if (staticRest.length === 0) {
|
|
3956
|
+
if (method === "get") {
|
|
3957
|
+
if (lastIsParam) return "get";
|
|
3958
|
+
return SINGLETONS.has(parts[0] ?? "") ? "get" : "list";
|
|
3959
|
+
}
|
|
3960
|
+
if (method === "post") return "create";
|
|
3961
|
+
if (method === "put" || method === "patch") return "update";
|
|
3962
|
+
if (method === "delete") return "delete";
|
|
3963
|
+
return method;
|
|
3964
|
+
}
|
|
3965
|
+
const action = staticRest.map(kebab).join("-");
|
|
3966
|
+
if (method === "get") return lastIsParam ? `get-${action}` : `list-${action}`;
|
|
3967
|
+
if (method === "post") return `create-${action}`;
|
|
3968
|
+
if (method === "put" || method === "patch") return `update-${action}`;
|
|
3969
|
+
if (method === "delete") return `delete-${action}`;
|
|
3970
|
+
return `${method}-${action}`;
|
|
3971
|
+
}
|
|
3972
|
+
function mapOperation(operation) {
|
|
3973
|
+
return {
|
|
3974
|
+
group: groupFromTag(operation.tags[0], operation.path),
|
|
3975
|
+
verb: deriveVerb(operation.method.toLowerCase(), operation.path),
|
|
3976
|
+
operation
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
function mapOperations(operations) {
|
|
3980
|
+
const used = /* @__PURE__ */ new Map();
|
|
3981
|
+
return operations.map((operation) => {
|
|
3982
|
+
const mapped = mapOperation(operation);
|
|
3983
|
+
const key = `${mapped.group} ${mapped.verb}`;
|
|
3984
|
+
const count = used.get(key) ?? 0;
|
|
3985
|
+
used.set(key, count + 1);
|
|
3986
|
+
if (count > 0) mapped.verb = `${mapped.verb}-${operation.operationId}`;
|
|
3987
|
+
return mapped;
|
|
3988
|
+
});
|
|
3989
|
+
}
|
|
3990
|
+
function applyPathParams(path, values) {
|
|
3991
|
+
return path.replace(/\{([^}]+)\}/g, (_all, name) => {
|
|
3992
|
+
const value = values[name];
|
|
3993
|
+
if (!value) throw new Error(`Missing path parameter --${kebab(name)}`);
|
|
3994
|
+
return encodeURIComponent(value);
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
3997
|
+
function flagName(param) {
|
|
3998
|
+
return kebab(param);
|
|
3999
|
+
}
|
|
4000
|
+
//#endregion
|
|
4001
|
+
//#region src/spec/operations.ts
|
|
4002
|
+
/** Generated by scripts/vendor-cli-openapi.mjs — do not edit by hand. */
|
|
4003
|
+
var vendoredOperations = [
|
|
4004
|
+
{
|
|
4005
|
+
operationId: "deleteApiApiKeysById",
|
|
4006
|
+
method: "delete",
|
|
4007
|
+
path: "/api/api-keys/{id}",
|
|
4008
|
+
tags: ["API Keys"],
|
|
4009
|
+
summary: "Delete an API key",
|
|
4010
|
+
pathParams: ["id"]
|
|
4011
|
+
},
|
|
4012
|
+
{
|
|
4013
|
+
operationId: "deleteApiGroupsById",
|
|
4014
|
+
method: "delete",
|
|
4015
|
+
path: "/api/groups/{id}",
|
|
4016
|
+
tags: ["Groups"],
|
|
4017
|
+
summary: "Delete a group",
|
|
4018
|
+
pathParams: ["id"]
|
|
4019
|
+
},
|
|
4020
|
+
{
|
|
4021
|
+
operationId: "deleteApiMeAccounts",
|
|
4022
|
+
method: "delete",
|
|
4023
|
+
path: "/api/me/accounts",
|
|
4024
|
+
tags: ["Me"],
|
|
4025
|
+
summary: "Delete all current user accounts and user",
|
|
4026
|
+
pathParams: []
|
|
4027
|
+
},
|
|
4028
|
+
{
|
|
4029
|
+
operationId: "deleteApiMeSessionsBySessionId",
|
|
4030
|
+
method: "delete",
|
|
4031
|
+
path: "/api/me/sessions/{sessionId}",
|
|
4032
|
+
tags: ["Me"],
|
|
4033
|
+
summary: "Revoke a specific session for the current user",
|
|
4034
|
+
pathParams: ["sessionId"]
|
|
4035
|
+
},
|
|
4036
|
+
{
|
|
4037
|
+
operationId: "deleteApiOrganizationInvitationsById",
|
|
4038
|
+
method: "delete",
|
|
4039
|
+
path: "/api/organization-invitations/{id}",
|
|
4040
|
+
tags: ["Organization Invitations"],
|
|
4041
|
+
summary: "Revoke invitation",
|
|
4042
|
+
pathParams: ["id"]
|
|
4043
|
+
},
|
|
4044
|
+
{
|
|
4045
|
+
operationId: "deleteApiOrganizationMembersByUserId",
|
|
4046
|
+
method: "delete",
|
|
4047
|
+
path: "/api/organization-members/{userId}",
|
|
4048
|
+
tags: ["Organization Members"],
|
|
4049
|
+
summary: "Remove organization member",
|
|
4050
|
+
pathParams: ["userId"]
|
|
4051
|
+
},
|
|
4052
|
+
{
|
|
4053
|
+
operationId: "deleteApiOrganizationsById",
|
|
4054
|
+
method: "delete",
|
|
4055
|
+
path: "/api/organizations/{id}",
|
|
4056
|
+
tags: ["Organizations"],
|
|
4057
|
+
summary: "Delete organization",
|
|
4058
|
+
pathParams: ["id"]
|
|
4059
|
+
},
|
|
4060
|
+
{
|
|
4061
|
+
operationId: "deleteApiPermissionsById",
|
|
4062
|
+
method: "delete",
|
|
4063
|
+
path: "/api/permissions/{id}",
|
|
4064
|
+
tags: ["Permissions"],
|
|
4065
|
+
summary: "Delete a permission",
|
|
4066
|
+
pathParams: ["id"]
|
|
4067
|
+
},
|
|
4068
|
+
{
|
|
4069
|
+
operationId: "deleteApiProjectAppsById",
|
|
4070
|
+
method: "delete",
|
|
4071
|
+
path: "/api/project-apps/{id}",
|
|
4072
|
+
tags: ["Project Apps"],
|
|
4073
|
+
summary: "Delete a project app",
|
|
4074
|
+
pathParams: ["id"]
|
|
4075
|
+
},
|
|
4076
|
+
{
|
|
4077
|
+
operationId: "deleteApiProjectAppsByIdPicture",
|
|
4078
|
+
method: "delete",
|
|
4079
|
+
path: "/api/project-apps/{id}/picture",
|
|
4080
|
+
tags: ["Project Apps"],
|
|
4081
|
+
summary: "Clear a project-app picture",
|
|
4082
|
+
pathParams: ["id"]
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
operationId: "deleteApiProjectOauthConnections",
|
|
4086
|
+
method: "delete",
|
|
4087
|
+
path: "/api/project-oauth-connections",
|
|
4088
|
+
tags: ["Project OAuth Connections"],
|
|
4089
|
+
summary: "Clear a project OAuth connection",
|
|
4090
|
+
pathParams: []
|
|
4091
|
+
},
|
|
4092
|
+
{
|
|
4093
|
+
operationId: "deleteApiProjectsById",
|
|
4094
|
+
method: "delete",
|
|
4095
|
+
path: "/api/projects/{id}",
|
|
4096
|
+
tags: ["Projects"],
|
|
4097
|
+
summary: "Delete a project",
|
|
4098
|
+
pathParams: ["id"]
|
|
4099
|
+
},
|
|
4100
|
+
{
|
|
4101
|
+
operationId: "deleteApiProjectsByIdPicture",
|
|
4102
|
+
method: "delete",
|
|
4103
|
+
path: "/api/projects/{id}/picture",
|
|
4104
|
+
tags: ["Projects"],
|
|
4105
|
+
summary: "Clear a project picture",
|
|
4106
|
+
pathParams: ["id"]
|
|
4107
|
+
},
|
|
4108
|
+
{
|
|
4109
|
+
operationId: "deleteApiProjectsByIdSyncJobsByJobId",
|
|
4110
|
+
method: "delete",
|
|
4111
|
+
path: "/api/projects/{id}/sync/jobs/{jobId}",
|
|
4112
|
+
tags: ["Projects"],
|
|
4113
|
+
summary: "Cancel a project sync job",
|
|
4114
|
+
pathParams: ["id", "jobId"]
|
|
4115
|
+
},
|
|
4116
|
+
{
|
|
4117
|
+
operationId: "deleteApiResourcesById",
|
|
4118
|
+
method: "delete",
|
|
4119
|
+
path: "/api/resources/{id}",
|
|
4120
|
+
tags: ["Resources"],
|
|
4121
|
+
summary: "Delete a resource",
|
|
4122
|
+
pathParams: ["id"]
|
|
4123
|
+
},
|
|
4124
|
+
{
|
|
4125
|
+
operationId: "deleteApiRolesById",
|
|
4126
|
+
method: "delete",
|
|
4127
|
+
path: "/api/roles/{id}",
|
|
4128
|
+
tags: ["Roles"],
|
|
4129
|
+
summary: "Delete a role",
|
|
4130
|
+
pathParams: ["id"]
|
|
4131
|
+
},
|
|
4132
|
+
{
|
|
4133
|
+
operationId: "deleteApiTagsById",
|
|
4134
|
+
method: "delete",
|
|
4135
|
+
path: "/api/tags/{id}",
|
|
4136
|
+
tags: ["Tags"],
|
|
4137
|
+
summary: "Delete a tag",
|
|
4138
|
+
pathParams: ["id"]
|
|
4139
|
+
},
|
|
4140
|
+
{
|
|
4141
|
+
operationId: "deleteApiUsersById",
|
|
4142
|
+
method: "delete",
|
|
4143
|
+
path: "/api/users/{id}",
|
|
4144
|
+
tags: ["Users"],
|
|
4145
|
+
summary: "Delete a user",
|
|
4146
|
+
pathParams: ["id"]
|
|
4147
|
+
},
|
|
4148
|
+
{
|
|
4149
|
+
operationId: "deleteApiWebhookSubscriptionsById",
|
|
4150
|
+
method: "delete",
|
|
4151
|
+
path: "/api/webhook-subscriptions/{id}",
|
|
4152
|
+
tags: [],
|
|
4153
|
+
summary: "Delete a webhook subscription",
|
|
4154
|
+
pathParams: ["id"]
|
|
4155
|
+
},
|
|
4156
|
+
{
|
|
4157
|
+
operationId: "get.wellKnownJwks.json",
|
|
4158
|
+
method: "get",
|
|
4159
|
+
path: "/.well-known/jwks.json",
|
|
4160
|
+
tags: ["JWKS"],
|
|
4161
|
+
summary: "Get system JWKS",
|
|
4162
|
+
pathParams: []
|
|
4163
|
+
},
|
|
4164
|
+
{
|
|
4165
|
+
operationId: "getAccByAccIdPrjByProjectId.wellKnownJwks.json",
|
|
4166
|
+
method: "get",
|
|
4167
|
+
path: "/acc/{accId}/prj/{projectId}/.well-known/jwks.json",
|
|
4168
|
+
tags: ["JWKS"],
|
|
4169
|
+
summary: "Get account project JWKS",
|
|
4170
|
+
pathParams: ["accId", "projectId"]
|
|
4171
|
+
},
|
|
4172
|
+
{
|
|
4173
|
+
operationId: "getApiApiKeys",
|
|
4174
|
+
method: "get",
|
|
4175
|
+
path: "/api/api-keys",
|
|
4176
|
+
tags: ["API Keys"],
|
|
4177
|
+
summary: "List API keys",
|
|
4178
|
+
pathParams: []
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
operationId: "getApiAuthGithub",
|
|
4182
|
+
method: "get",
|
|
4183
|
+
path: "/api/auth/github",
|
|
4184
|
+
tags: ["Authentication"],
|
|
4185
|
+
summary: "Initiate GitHub OAuth",
|
|
4186
|
+
pathParams: []
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
operationId: "getApiAuthGithubCallback",
|
|
4190
|
+
method: "get",
|
|
4191
|
+
path: "/api/auth/github/callback",
|
|
4192
|
+
tags: ["Authentication"],
|
|
4193
|
+
summary: "GitHub OAuth callback",
|
|
4194
|
+
pathParams: []
|
|
4195
|
+
},
|
|
4196
|
+
{
|
|
4197
|
+
operationId: "getApiAuthGoogle",
|
|
4198
|
+
method: "get",
|
|
4199
|
+
path: "/api/auth/google",
|
|
4200
|
+
tags: ["Authentication"],
|
|
4201
|
+
summary: "Initiate Google OAuth",
|
|
4202
|
+
pathParams: []
|
|
4203
|
+
},
|
|
4204
|
+
{
|
|
4205
|
+
operationId: "getApiAuthGoogleCallback",
|
|
4206
|
+
method: "get",
|
|
4207
|
+
path: "/api/auth/google/callback",
|
|
4208
|
+
tags: ["Authentication"],
|
|
4209
|
+
summary: "Google OAuth callback",
|
|
4210
|
+
pathParams: []
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
operationId: "getApiAuthProjectAppInfo",
|
|
4214
|
+
method: "get",
|
|
4215
|
+
path: "/api/auth/project/app-info",
|
|
4216
|
+
tags: ["Authentication"],
|
|
4217
|
+
summary: "Project app public info",
|
|
4218
|
+
pathParams: []
|
|
4219
|
+
},
|
|
4220
|
+
{
|
|
4221
|
+
operationId: "getApiAuthProjectAuthorize",
|
|
4222
|
+
method: "get",
|
|
4223
|
+
path: "/api/auth/project/authorize",
|
|
4224
|
+
tags: ["Authentication"],
|
|
4225
|
+
summary: "Initiate project OAuth",
|
|
4226
|
+
pathParams: []
|
|
4227
|
+
},
|
|
4228
|
+
{
|
|
4229
|
+
operationId: "getApiAuthProjectCallback",
|
|
4230
|
+
method: "get",
|
|
4231
|
+
path: "/api/auth/project/callback",
|
|
4232
|
+
tags: ["Authentication"],
|
|
4233
|
+
summary: "Project OAuth callback",
|
|
4234
|
+
pathParams: []
|
|
4235
|
+
},
|
|
4236
|
+
{
|
|
4237
|
+
operationId: "getApiAuthProjectConsentInfo",
|
|
4238
|
+
method: "get",
|
|
4239
|
+
path: "/api/auth/project/consent-info",
|
|
4240
|
+
tags: ["Authentication"],
|
|
4241
|
+
summary: "Consent info",
|
|
4242
|
+
pathParams: []
|
|
4243
|
+
},
|
|
4244
|
+
{
|
|
4245
|
+
operationId: "getApiAuthProviders",
|
|
4246
|
+
method: "get",
|
|
4247
|
+
path: "/api/auth/providers",
|
|
4248
|
+
tags: ["Authentication"],
|
|
4249
|
+
summary: "List OAuth providers",
|
|
4250
|
+
pathParams: []
|
|
4251
|
+
},
|
|
4252
|
+
{
|
|
4253
|
+
operationId: "getApiConfig",
|
|
4254
|
+
method: "get",
|
|
4255
|
+
path: "/api/config",
|
|
4256
|
+
tags: ["Config"],
|
|
4257
|
+
summary: "Get runtime config",
|
|
4258
|
+
pathParams: []
|
|
4259
|
+
},
|
|
4260
|
+
{
|
|
4261
|
+
operationId: "getApiGroups",
|
|
4262
|
+
method: "get",
|
|
4263
|
+
path: "/api/groups",
|
|
4264
|
+
tags: ["Groups"],
|
|
4265
|
+
summary: "List groups",
|
|
4266
|
+
pathParams: []
|
|
4267
|
+
},
|
|
4268
|
+
{
|
|
4269
|
+
operationId: "getApiMe",
|
|
4270
|
+
method: "get",
|
|
4271
|
+
path: "/api/me",
|
|
4272
|
+
tags: ["Me"],
|
|
4273
|
+
summary: "Get current user information",
|
|
4274
|
+
pathParams: []
|
|
4275
|
+
},
|
|
4276
|
+
{
|
|
4277
|
+
operationId: "getApiMeAuthenticationMethods",
|
|
4278
|
+
method: "get",
|
|
4279
|
+
path: "/api/me/authentication-methods",
|
|
4280
|
+
tags: ["Me"],
|
|
4281
|
+
summary: "Get current user authentication methods",
|
|
4282
|
+
pathParams: []
|
|
4283
|
+
},
|
|
4284
|
+
{
|
|
4285
|
+
operationId: "getApiMeExport",
|
|
4286
|
+
method: "get",
|
|
4287
|
+
path: "/api/me/export",
|
|
4288
|
+
tags: ["Me"],
|
|
4289
|
+
summary: "Export current user data",
|
|
4290
|
+
pathParams: []
|
|
4291
|
+
},
|
|
4292
|
+
{
|
|
4293
|
+
operationId: "getApiMeMfaRecoveryCodesStatus",
|
|
4294
|
+
method: "get",
|
|
4295
|
+
path: "/api/me/mfa/recovery-codes/status",
|
|
4296
|
+
tags: ["Me"],
|
|
4297
|
+
summary: "MFA recovery code status (metadata only)",
|
|
4298
|
+
pathParams: []
|
|
4299
|
+
},
|
|
4300
|
+
{
|
|
4301
|
+
operationId: "getApiMeNotificationPreferences",
|
|
4302
|
+
method: "get",
|
|
4303
|
+
path: "/api/me/notification-preferences",
|
|
4304
|
+
tags: ["Me"],
|
|
4305
|
+
summary: "List my notification preferences for a tenant",
|
|
4306
|
+
pathParams: []
|
|
4307
|
+
},
|
|
4308
|
+
{
|
|
4309
|
+
operationId: "getApiMeNotifications",
|
|
4310
|
+
method: "get",
|
|
4311
|
+
path: "/api/me/notifications",
|
|
4312
|
+
tags: ["Me"],
|
|
4313
|
+
summary: "List my in-app notifications",
|
|
4314
|
+
pathParams: []
|
|
4315
|
+
},
|
|
4316
|
+
{
|
|
4317
|
+
operationId: "getApiMeNotificationsUnreadCount",
|
|
4318
|
+
method: "get",
|
|
4319
|
+
path: "/api/me/notifications/unread-count",
|
|
4320
|
+
tags: ["Me"],
|
|
4321
|
+
summary: "Get my unread notification count",
|
|
4322
|
+
pathParams: []
|
|
4323
|
+
},
|
|
4324
|
+
{
|
|
4325
|
+
operationId: "getApiMeProjectMemberships",
|
|
4326
|
+
method: "get",
|
|
4327
|
+
path: "/api/me/project-memberships",
|
|
4328
|
+
tags: ["Me"],
|
|
4329
|
+
summary: "List my project memberships",
|
|
4330
|
+
pathParams: []
|
|
4331
|
+
},
|
|
4332
|
+
{
|
|
4333
|
+
operationId: "getApiMeProjectMembershipsByProjectId",
|
|
4334
|
+
method: "get",
|
|
4335
|
+
path: "/api/me/project-memberships/{projectId}",
|
|
4336
|
+
tags: ["Me"],
|
|
4337
|
+
summary: "Get one of my project memberships",
|
|
4338
|
+
pathParams: ["projectId"]
|
|
4339
|
+
},
|
|
4340
|
+
{
|
|
4341
|
+
operationId: "getApiMeSessions",
|
|
4342
|
+
method: "get",
|
|
4343
|
+
path: "/api/me/sessions",
|
|
4344
|
+
tags: ["Me"],
|
|
4345
|
+
summary: "Get current user sessions",
|
|
4346
|
+
pathParams: []
|
|
4347
|
+
},
|
|
4348
|
+
{
|
|
4349
|
+
operationId: "getApiOrganizationInvitations",
|
|
4350
|
+
method: "get",
|
|
4351
|
+
path: "/api/organization-invitations",
|
|
4352
|
+
tags: ["Organization Invitations"],
|
|
4353
|
+
summary: "List organization invitations",
|
|
4354
|
+
pathParams: []
|
|
4355
|
+
},
|
|
4356
|
+
{
|
|
4357
|
+
operationId: "getApiOrganizationInvitationsByToken",
|
|
4358
|
+
method: "get",
|
|
4359
|
+
path: "/api/organization-invitations/{token}",
|
|
4360
|
+
tags: ["Organization Invitations"],
|
|
4361
|
+
summary: "Get invitation by token",
|
|
4362
|
+
pathParams: ["token"]
|
|
4363
|
+
},
|
|
4364
|
+
{
|
|
4365
|
+
operationId: "getApiOrganizationMembers",
|
|
4366
|
+
method: "get",
|
|
4367
|
+
path: "/api/organization-members",
|
|
4368
|
+
tags: ["Organization Members"],
|
|
4369
|
+
summary: "List organization members",
|
|
4370
|
+
pathParams: []
|
|
4371
|
+
},
|
|
4372
|
+
{
|
|
4373
|
+
operationId: "getApiOrganizations",
|
|
4374
|
+
method: "get",
|
|
4375
|
+
path: "/api/organizations",
|
|
4376
|
+
tags: ["Organizations"],
|
|
4377
|
+
summary: "List organizations",
|
|
4378
|
+
pathParams: []
|
|
4379
|
+
},
|
|
4380
|
+
{
|
|
4381
|
+
operationId: "getApiOrganizationsById",
|
|
4382
|
+
method: "get",
|
|
4383
|
+
path: "/api/organizations/{id}",
|
|
4384
|
+
tags: ["Organizations"],
|
|
4385
|
+
summary: "Get organization by ID",
|
|
4386
|
+
pathParams: ["id"]
|
|
4387
|
+
},
|
|
4388
|
+
{
|
|
4389
|
+
operationId: "getApiPermissions",
|
|
4390
|
+
method: "get",
|
|
4391
|
+
path: "/api/permissions",
|
|
4392
|
+
tags: ["Permissions"],
|
|
4393
|
+
summary: "List permissions",
|
|
4394
|
+
pathParams: []
|
|
4395
|
+
},
|
|
4396
|
+
{
|
|
4397
|
+
operationId: "getApiProjectApps",
|
|
4398
|
+
method: "get",
|
|
4399
|
+
path: "/api/project-apps",
|
|
4400
|
+
tags: ["Project Apps"],
|
|
4401
|
+
summary: "List project apps",
|
|
4402
|
+
pathParams: []
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
operationId: "getApiProjectOauthConnections",
|
|
4406
|
+
method: "get",
|
|
4407
|
+
path: "/api/project-oauth-connections",
|
|
4408
|
+
tags: ["Project OAuth Connections"],
|
|
4409
|
+
summary: "List project OAuth connections",
|
|
4410
|
+
pathParams: []
|
|
4411
|
+
},
|
|
4412
|
+
{
|
|
4413
|
+
operationId: "getApiProjects",
|
|
4414
|
+
method: "get",
|
|
4415
|
+
path: "/api/projects",
|
|
4416
|
+
tags: ["Projects"],
|
|
4417
|
+
summary: "List projects",
|
|
4418
|
+
pathParams: []
|
|
4419
|
+
},
|
|
4420
|
+
{
|
|
4421
|
+
operationId: "getApiProjectsByIdSyncJobs",
|
|
4422
|
+
method: "get",
|
|
4423
|
+
path: "/api/projects/{id}/sync/jobs",
|
|
4424
|
+
tags: ["Projects"],
|
|
4425
|
+
summary: "List project sync jobs",
|
|
4426
|
+
pathParams: ["id"]
|
|
4427
|
+
},
|
|
4428
|
+
{
|
|
4429
|
+
operationId: "getApiProjectsByIdSyncJobsByJobId",
|
|
4430
|
+
method: "get",
|
|
4431
|
+
path: "/api/projects/{id}/sync/jobs/{jobId}",
|
|
4432
|
+
tags: ["Projects"],
|
|
4433
|
+
summary: "Get the status of a project sync job",
|
|
4434
|
+
pathParams: ["id", "jobId"]
|
|
4435
|
+
},
|
|
4436
|
+
{
|
|
4437
|
+
operationId: "getApiProjectsByIdSyncJobsByJobIdPayload",
|
|
4438
|
+
method: "get",
|
|
4439
|
+
path: "/api/projects/{id}/sync/jobs/{jobId}/payload",
|
|
4440
|
+
tags: ["Projects"],
|
|
4441
|
+
summary: "Download the original CDM JSON payload for a sync job",
|
|
4442
|
+
pathParams: ["id", "jobId"]
|
|
4443
|
+
},
|
|
4444
|
+
{
|
|
4445
|
+
operationId: "getApiProjectsByIdSyncJobsByJobIdSnapshot",
|
|
4446
|
+
method: "get",
|
|
4447
|
+
path: "/api/projects/{id}/sync/jobs/{jobId}/snapshot",
|
|
4448
|
+
tags: ["Projects"],
|
|
4449
|
+
summary: "Download the CDM JSON snapshot attached to a sync job",
|
|
4450
|
+
pathParams: ["id", "jobId"]
|
|
4451
|
+
},
|
|
4452
|
+
{
|
|
4453
|
+
operationId: "getApiResources",
|
|
4454
|
+
method: "get",
|
|
4455
|
+
path: "/api/resources",
|
|
4456
|
+
tags: ["Resources"],
|
|
4457
|
+
summary: "List resources",
|
|
4458
|
+
pathParams: []
|
|
4459
|
+
},
|
|
4460
|
+
{
|
|
4461
|
+
operationId: "getApiRoles",
|
|
4462
|
+
method: "get",
|
|
4463
|
+
path: "/api/roles",
|
|
4464
|
+
tags: ["Roles"],
|
|
4465
|
+
summary: "List roles",
|
|
4466
|
+
pathParams: []
|
|
4467
|
+
},
|
|
4468
|
+
{
|
|
4469
|
+
operationId: "getApiSigningKeys",
|
|
4470
|
+
method: "get",
|
|
4471
|
+
path: "/api/signing-keys",
|
|
4472
|
+
tags: ["Signing Keys"],
|
|
4473
|
+
summary: "List signing keys",
|
|
4474
|
+
pathParams: []
|
|
4475
|
+
},
|
|
4476
|
+
{
|
|
4477
|
+
operationId: "getApiTags",
|
|
4478
|
+
method: "get",
|
|
4479
|
+
path: "/api/tags",
|
|
4480
|
+
tags: ["Tags"],
|
|
4481
|
+
summary: "List tags",
|
|
4482
|
+
pathParams: []
|
|
4483
|
+
},
|
|
4484
|
+
{
|
|
4485
|
+
operationId: "getApiUsers",
|
|
4486
|
+
method: "get",
|
|
4487
|
+
path: "/api/users",
|
|
4488
|
+
tags: ["Users"],
|
|
4489
|
+
summary: "List users",
|
|
4490
|
+
pathParams: []
|
|
4491
|
+
},
|
|
4492
|
+
{
|
|
4493
|
+
operationId: "getApiWebhookSubscriptions",
|
|
4494
|
+
method: "get",
|
|
4495
|
+
path: "/api/webhook-subscriptions",
|
|
4496
|
+
tags: [],
|
|
4497
|
+
summary: "List webhook subscriptions",
|
|
4498
|
+
pathParams: []
|
|
4499
|
+
},
|
|
4500
|
+
{
|
|
4501
|
+
operationId: "getApiWebhookSubscriptionsById",
|
|
4502
|
+
method: "get",
|
|
4503
|
+
path: "/api/webhook-subscriptions/{id}",
|
|
4504
|
+
tags: [],
|
|
4505
|
+
summary: "Get a webhook subscription",
|
|
4506
|
+
pathParams: ["id"]
|
|
4507
|
+
},
|
|
4508
|
+
{
|
|
4509
|
+
operationId: "getApiWebhookSubscriptionsDeliveries",
|
|
4510
|
+
method: "get",
|
|
4511
|
+
path: "/api/webhook-subscriptions/deliveries",
|
|
4512
|
+
tags: [],
|
|
4513
|
+
summary: "List webhook delivery attempts",
|
|
4514
|
+
pathParams: []
|
|
4515
|
+
},
|
|
4516
|
+
{
|
|
4517
|
+
operationId: "getOrgByOrgIdPrjByProjectId.wellKnownJwks.json",
|
|
4518
|
+
method: "get",
|
|
4519
|
+
path: "/org/{orgId}/prj/{projectId}/.well-known/jwks.json",
|
|
4520
|
+
tags: ["JWKS"],
|
|
4521
|
+
summary: "Get organization project JWKS",
|
|
4522
|
+
pathParams: ["orgId", "projectId"]
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
operationId: "patchApiGroupsById",
|
|
4526
|
+
method: "patch",
|
|
4527
|
+
path: "/api/groups/{id}",
|
|
4528
|
+
tags: ["Groups"],
|
|
4529
|
+
summary: "Update a group",
|
|
4530
|
+
pathParams: ["id"]
|
|
4531
|
+
},
|
|
4532
|
+
{
|
|
4533
|
+
operationId: "patchApiMeProjectMembershipsByProjectId",
|
|
4534
|
+
method: "patch",
|
|
4535
|
+
path: "/api/me/project-memberships/{projectId}",
|
|
4536
|
+
tags: ["Me"],
|
|
4537
|
+
summary: "Update my project membership profile",
|
|
4538
|
+
pathParams: ["projectId"]
|
|
4539
|
+
},
|
|
4540
|
+
{
|
|
4541
|
+
operationId: "patchApiOrganizationMembersByUserId",
|
|
4542
|
+
method: "patch",
|
|
4543
|
+
path: "/api/organization-members/{userId}",
|
|
4544
|
+
tags: ["Organization Members"],
|
|
4545
|
+
summary: "Update organization member role",
|
|
4546
|
+
pathParams: ["userId"]
|
|
4547
|
+
},
|
|
4548
|
+
{
|
|
4549
|
+
operationId: "patchApiOrganizationsById",
|
|
4550
|
+
method: "patch",
|
|
4551
|
+
path: "/api/organizations/{id}",
|
|
4552
|
+
tags: ["Organizations"],
|
|
4553
|
+
summary: "Update organization",
|
|
4554
|
+
pathParams: ["id"]
|
|
4555
|
+
},
|
|
4556
|
+
{
|
|
4557
|
+
operationId: "patchApiPermissionsById",
|
|
4558
|
+
method: "patch",
|
|
4559
|
+
path: "/api/permissions/{id}",
|
|
4560
|
+
tags: ["Permissions"],
|
|
4561
|
+
summary: "Update a permission",
|
|
4562
|
+
pathParams: ["id"]
|
|
4563
|
+
},
|
|
4564
|
+
{
|
|
4565
|
+
operationId: "patchApiProjectAppsById",
|
|
4566
|
+
method: "patch",
|
|
4567
|
+
path: "/api/project-apps/{id}",
|
|
4568
|
+
tags: ["Project Apps"],
|
|
4569
|
+
summary: "Update a project app",
|
|
4570
|
+
pathParams: ["id"]
|
|
4571
|
+
},
|
|
4572
|
+
{
|
|
4573
|
+
operationId: "patchApiProjectsById",
|
|
4574
|
+
method: "patch",
|
|
4575
|
+
path: "/api/projects/{id}",
|
|
4576
|
+
tags: ["Projects"],
|
|
4577
|
+
summary: "Update a project",
|
|
4578
|
+
pathParams: ["id"]
|
|
4579
|
+
},
|
|
4580
|
+
{
|
|
4581
|
+
operationId: "patchApiResourcesById",
|
|
4582
|
+
method: "patch",
|
|
4583
|
+
path: "/api/resources/{id}",
|
|
4584
|
+
tags: ["Resources"],
|
|
4585
|
+
summary: "Update a resource",
|
|
4586
|
+
pathParams: ["id"]
|
|
4587
|
+
},
|
|
4588
|
+
{
|
|
4589
|
+
operationId: "patchApiRolesById",
|
|
4590
|
+
method: "patch",
|
|
4591
|
+
path: "/api/roles/{id}",
|
|
4592
|
+
tags: ["Roles"],
|
|
4593
|
+
summary: "Update a role",
|
|
4594
|
+
pathParams: ["id"]
|
|
4595
|
+
},
|
|
4596
|
+
{
|
|
4597
|
+
operationId: "patchApiTagsById",
|
|
4598
|
+
method: "patch",
|
|
4599
|
+
path: "/api/tags/{id}",
|
|
4600
|
+
tags: ["Tags"],
|
|
4601
|
+
summary: "Update a tag",
|
|
4602
|
+
pathParams: ["id"]
|
|
4603
|
+
},
|
|
4604
|
+
{
|
|
4605
|
+
operationId: "patchApiUsersById",
|
|
4606
|
+
method: "patch",
|
|
4607
|
+
path: "/api/users/{id}",
|
|
4608
|
+
tags: ["Users"],
|
|
4609
|
+
summary: "Update a user",
|
|
4610
|
+
pathParams: ["id"]
|
|
4611
|
+
},
|
|
4612
|
+
{
|
|
4613
|
+
operationId: "patchApiWebhookSubscriptionsById",
|
|
4614
|
+
method: "patch",
|
|
4615
|
+
path: "/api/webhook-subscriptions/{id}",
|
|
4616
|
+
tags: [],
|
|
4617
|
+
summary: "Update a webhook subscription",
|
|
4618
|
+
pathParams: ["id"]
|
|
4619
|
+
},
|
|
4620
|
+
{
|
|
4621
|
+
operationId: "postApiApiKeys",
|
|
4622
|
+
method: "post",
|
|
4623
|
+
path: "/api/api-keys",
|
|
4624
|
+
tags: ["API Keys"],
|
|
4625
|
+
summary: "Create a new API key",
|
|
4626
|
+
pathParams: []
|
|
4627
|
+
},
|
|
4628
|
+
{
|
|
4629
|
+
operationId: "postApiApiKeysByIdRevoke",
|
|
4630
|
+
method: "post",
|
|
4631
|
+
path: "/api/api-keys/{id}/revoke",
|
|
4632
|
+
tags: ["API Keys"],
|
|
4633
|
+
summary: "Revoke an API key",
|
|
4634
|
+
pathParams: ["id"]
|
|
4635
|
+
},
|
|
4636
|
+
{
|
|
4637
|
+
operationId: "postApiApiKeysByIdRotate",
|
|
4638
|
+
method: "post",
|
|
4639
|
+
path: "/api/api-keys/{id}/rotate",
|
|
4640
|
+
tags: ["API Keys"],
|
|
4641
|
+
summary: "Rotate an API key secret",
|
|
4642
|
+
pathParams: ["id"]
|
|
4643
|
+
},
|
|
4644
|
+
{
|
|
4645
|
+
operationId: "postApiAuthCliCallback",
|
|
4646
|
+
method: "post",
|
|
4647
|
+
path: "/api/auth/cli-callback",
|
|
4648
|
+
tags: ["Authentication"],
|
|
4649
|
+
summary: "Exchange CLI one-time code for session tokens",
|
|
4650
|
+
pathParams: []
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
operationId: "postApiAuthIsAuthorized",
|
|
4654
|
+
method: "post",
|
|
4655
|
+
path: "/api/auth/is-authorized",
|
|
4656
|
+
tags: ["Authentication"],
|
|
4657
|
+
summary: "Check if user is authorized",
|
|
4658
|
+
pathParams: []
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
operationId: "postApiAuthLogin",
|
|
4662
|
+
method: "post",
|
|
4663
|
+
path: "/api/auth/login",
|
|
4664
|
+
tags: ["Authentication"],
|
|
4665
|
+
summary: "Login user",
|
|
4666
|
+
pathParams: []
|
|
4667
|
+
},
|
|
4668
|
+
{
|
|
4669
|
+
operationId: "postApiAuthLogout",
|
|
4670
|
+
method: "post",
|
|
4671
|
+
path: "/api/auth/logout",
|
|
4672
|
+
tags: ["Authentication"],
|
|
4673
|
+
summary: "Logout user",
|
|
4674
|
+
pathParams: []
|
|
4675
|
+
},
|
|
4676
|
+
{
|
|
4677
|
+
operationId: "postApiAuthMfaRecoveryVerify",
|
|
4678
|
+
method: "post",
|
|
4679
|
+
path: "/api/auth/mfa/recovery/verify",
|
|
4680
|
+
tags: ["Authentication"],
|
|
4681
|
+
summary: "Complete MFA challenge with recovery code",
|
|
4682
|
+
pathParams: []
|
|
4683
|
+
},
|
|
4684
|
+
{
|
|
4685
|
+
operationId: "postApiAuthMfaSetup",
|
|
4686
|
+
method: "post",
|
|
4687
|
+
path: "/api/auth/mfa/setup",
|
|
4688
|
+
tags: ["Authentication"],
|
|
4689
|
+
summary: "Initialize TOTP MFA enrollment",
|
|
4690
|
+
pathParams: []
|
|
4691
|
+
},
|
|
4692
|
+
{
|
|
4693
|
+
operationId: "postApiAuthMfaVerify",
|
|
4694
|
+
method: "post",
|
|
4695
|
+
path: "/api/auth/mfa/verify",
|
|
4696
|
+
tags: ["Authentication"],
|
|
4697
|
+
summary: "Complete MFA challenge with TOTP",
|
|
4698
|
+
pathParams: []
|
|
4699
|
+
},
|
|
4700
|
+
{
|
|
4701
|
+
operationId: "postApiAuthProjectConsentApprove",
|
|
4702
|
+
method: "post",
|
|
4703
|
+
path: "/api/auth/project/consent/approve",
|
|
4704
|
+
tags: ["Authentication"],
|
|
4705
|
+
summary: "Approve consent",
|
|
4706
|
+
pathParams: []
|
|
4707
|
+
},
|
|
4708
|
+
{
|
|
4709
|
+
operationId: "postApiAuthProjectConsentDeny",
|
|
4710
|
+
method: "post",
|
|
4711
|
+
path: "/api/auth/project/consent/deny",
|
|
4712
|
+
tags: ["Authentication"],
|
|
4713
|
+
summary: "Deny consent",
|
|
4714
|
+
pathParams: []
|
|
4715
|
+
},
|
|
4716
|
+
{
|
|
4717
|
+
operationId: "postApiAuthProjectEmailRequest",
|
|
4718
|
+
method: "post",
|
|
4719
|
+
path: "/api/auth/project/email/request",
|
|
4720
|
+
tags: ["Authentication"],
|
|
4721
|
+
summary: "Request project OAuth magic link",
|
|
4722
|
+
pathParams: []
|
|
4723
|
+
},
|
|
4724
|
+
{
|
|
4725
|
+
operationId: "postApiAuthRefresh",
|
|
4726
|
+
method: "post",
|
|
4727
|
+
path: "/api/auth/refresh",
|
|
4728
|
+
tags: ["Authentication"],
|
|
4729
|
+
summary: "Refresh session",
|
|
4730
|
+
pathParams: []
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
operationId: "postApiAuthRegister",
|
|
4734
|
+
method: "post",
|
|
4735
|
+
path: "/api/auth/register",
|
|
4736
|
+
tags: ["Authentication"],
|
|
4737
|
+
summary: "Register new user",
|
|
4738
|
+
pathParams: []
|
|
4739
|
+
},
|
|
4740
|
+
{
|
|
4741
|
+
operationId: "postApiAuthRequestPasswordReset",
|
|
4742
|
+
method: "post",
|
|
4743
|
+
path: "/api/auth/request-password-reset",
|
|
4744
|
+
tags: ["Authentication"],
|
|
4745
|
+
summary: "Request password reset",
|
|
4746
|
+
pathParams: []
|
|
4747
|
+
},
|
|
4748
|
+
{
|
|
4749
|
+
operationId: "postApiAuthResendVerification",
|
|
4750
|
+
method: "post",
|
|
4751
|
+
path: "/api/auth/resend-verification",
|
|
4752
|
+
tags: ["Authentication"],
|
|
4753
|
+
summary: "Resend verification email",
|
|
4754
|
+
pathParams: []
|
|
4755
|
+
},
|
|
4756
|
+
{
|
|
4757
|
+
operationId: "postApiAuthResetPassword",
|
|
4758
|
+
method: "post",
|
|
4759
|
+
path: "/api/auth/reset-password",
|
|
4760
|
+
tags: ["Authentication"],
|
|
4761
|
+
summary: "Reset password",
|
|
4762
|
+
pathParams: []
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
operationId: "postApiAuthToken",
|
|
4766
|
+
method: "post",
|
|
4767
|
+
path: "/api/auth/token",
|
|
4768
|
+
tags: ["API Keys", "Authentication"],
|
|
4769
|
+
summary: "Exchange API key for access token",
|
|
4770
|
+
pathParams: []
|
|
4771
|
+
},
|
|
4772
|
+
{
|
|
4773
|
+
operationId: "postApiAuthVerifyEmail",
|
|
4774
|
+
method: "post",
|
|
4775
|
+
path: "/api/auth/verify-email",
|
|
4776
|
+
tags: ["Authentication"],
|
|
4777
|
+
summary: "Verify email address",
|
|
4778
|
+
pathParams: []
|
|
4779
|
+
},
|
|
4780
|
+
{
|
|
4781
|
+
operationId: "postApiGroups",
|
|
4782
|
+
method: "post",
|
|
4783
|
+
path: "/api/groups",
|
|
4784
|
+
tags: ["Groups"],
|
|
4785
|
+
summary: "Create a new group",
|
|
4786
|
+
pathParams: []
|
|
4787
|
+
},
|
|
4788
|
+
{
|
|
4789
|
+
operationId: "postApiMeAccounts",
|
|
4790
|
+
method: "post",
|
|
4791
|
+
path: "/api/me/accounts",
|
|
4792
|
+
tags: ["Me"],
|
|
4793
|
+
summary: "Create a secondary account for the current user",
|
|
4794
|
+
pathParams: []
|
|
4795
|
+
},
|
|
4796
|
+
{
|
|
4797
|
+
operationId: "postApiMeAuthenticationMethods",
|
|
4798
|
+
method: "post",
|
|
4799
|
+
path: "/api/me/authentication-methods",
|
|
4800
|
+
tags: ["Me"],
|
|
4801
|
+
summary: "Create a new authentication method for the current user",
|
|
4802
|
+
pathParams: []
|
|
4803
|
+
},
|
|
4804
|
+
{
|
|
4805
|
+
operationId: "postApiMeChangePassword",
|
|
4806
|
+
method: "post",
|
|
4807
|
+
path: "/api/me/change-password",
|
|
4808
|
+
tags: ["Me"],
|
|
4809
|
+
summary: "Change current user password",
|
|
4810
|
+
pathParams: []
|
|
4811
|
+
},
|
|
4812
|
+
{
|
|
4813
|
+
operationId: "postApiMeLogout",
|
|
4814
|
+
method: "post",
|
|
4815
|
+
path: "/api/me/logout",
|
|
4816
|
+
tags: ["Me"],
|
|
4817
|
+
summary: "Logout current user and revoke session",
|
|
4818
|
+
pathParams: []
|
|
4819
|
+
},
|
|
4820
|
+
{
|
|
4821
|
+
operationId: "postApiMeNotificationsByIdRead",
|
|
4822
|
+
method: "post",
|
|
4823
|
+
path: "/api/me/notifications/{id}/read",
|
|
4824
|
+
tags: ["Me"],
|
|
4825
|
+
summary: "Mark a notification as read",
|
|
4826
|
+
pathParams: ["id"]
|
|
4827
|
+
},
|
|
4828
|
+
{
|
|
4829
|
+
operationId: "postApiMeNotificationsReadAll",
|
|
4830
|
+
method: "post",
|
|
4831
|
+
path: "/api/me/notifications/read-all",
|
|
4832
|
+
tags: ["Me"],
|
|
4833
|
+
summary: "Mark all my notifications as read",
|
|
4834
|
+
pathParams: []
|
|
4835
|
+
},
|
|
4836
|
+
{
|
|
4837
|
+
operationId: "postApiMePicture",
|
|
4838
|
+
method: "post",
|
|
4839
|
+
path: "/api/me/picture",
|
|
4840
|
+
tags: ["Me"],
|
|
4841
|
+
summary: "Upload current user profile picture",
|
|
4842
|
+
pathParams: []
|
|
4843
|
+
},
|
|
4844
|
+
{
|
|
4845
|
+
operationId: "postApiMePictureConfirm",
|
|
4846
|
+
method: "post",
|
|
4847
|
+
path: "/api/me/picture/confirm",
|
|
4848
|
+
tags: ["Me"],
|
|
4849
|
+
summary: "Record a completed direct upload of the current user picture",
|
|
4850
|
+
pathParams: []
|
|
4851
|
+
},
|
|
4852
|
+
{
|
|
4853
|
+
operationId: "postApiMePictureUploadUrl",
|
|
4854
|
+
method: "post",
|
|
4855
|
+
path: "/api/me/picture/upload-url",
|
|
4856
|
+
tags: ["Me"],
|
|
4857
|
+
summary: "Request a direct-upload URL for the current user picture",
|
|
4858
|
+
pathParams: []
|
|
4859
|
+
},
|
|
4860
|
+
{
|
|
4861
|
+
operationId: "postApiOrganizationInvitationsAccept",
|
|
4862
|
+
method: "post",
|
|
4863
|
+
path: "/api/organization-invitations/accept",
|
|
4864
|
+
tags: ["Organization Invitations"],
|
|
4865
|
+
summary: "Accept organization invitation",
|
|
4866
|
+
pathParams: []
|
|
4867
|
+
},
|
|
4868
|
+
{
|
|
4869
|
+
operationId: "postApiOrganizationInvitationsByIdRenew",
|
|
4870
|
+
method: "post",
|
|
4871
|
+
path: "/api/organization-invitations/{id}/renew",
|
|
4872
|
+
tags: ["Organization Invitations"],
|
|
4873
|
+
summary: "Renew expired invitation",
|
|
4874
|
+
pathParams: ["id"]
|
|
4875
|
+
},
|
|
4876
|
+
{
|
|
4877
|
+
operationId: "postApiOrganizationInvitationsByIdResendEmail",
|
|
4878
|
+
method: "post",
|
|
4879
|
+
path: "/api/organization-invitations/{id}/resend-email",
|
|
4880
|
+
tags: ["Organization Invitations"],
|
|
4881
|
+
summary: "Resend invitation email",
|
|
4882
|
+
pathParams: ["id"]
|
|
4883
|
+
},
|
|
4884
|
+
{
|
|
4885
|
+
operationId: "postApiOrganizationInvitationsInvite",
|
|
4886
|
+
method: "post",
|
|
4887
|
+
path: "/api/organization-invitations/invite",
|
|
4888
|
+
tags: ["Organization Invitations"],
|
|
4889
|
+
summary: "Invite member to organization",
|
|
4890
|
+
pathParams: []
|
|
4891
|
+
},
|
|
4892
|
+
{
|
|
4893
|
+
operationId: "postApiOrganizations",
|
|
4894
|
+
method: "post",
|
|
4895
|
+
path: "/api/organizations",
|
|
4896
|
+
tags: ["Organizations"],
|
|
4897
|
+
summary: "Create organization",
|
|
4898
|
+
pathParams: []
|
|
4899
|
+
},
|
|
4900
|
+
{
|
|
4901
|
+
operationId: "postApiOrganizationsByIdPicture",
|
|
4902
|
+
method: "post",
|
|
4903
|
+
path: "/api/organizations/{id}/picture",
|
|
4904
|
+
tags: ["Organizations"],
|
|
4905
|
+
summary: "Upload organization logo",
|
|
4906
|
+
pathParams: ["id"]
|
|
4907
|
+
},
|
|
4908
|
+
{
|
|
4909
|
+
operationId: "postApiOrganizationsByIdPictureConfirm",
|
|
4910
|
+
method: "post",
|
|
4911
|
+
path: "/api/organizations/{id}/picture/confirm",
|
|
4912
|
+
tags: ["Organizations"],
|
|
4913
|
+
summary: "Record a completed direct upload of an organization logo",
|
|
4914
|
+
pathParams: ["id"]
|
|
4915
|
+
},
|
|
4916
|
+
{
|
|
4917
|
+
operationId: "postApiOrganizationsByIdPictureUploadUrl",
|
|
4918
|
+
method: "post",
|
|
4919
|
+
path: "/api/organizations/{id}/picture/upload-url",
|
|
4920
|
+
tags: ["Organizations"],
|
|
4921
|
+
summary: "Request a direct-upload URL for an organization logo",
|
|
4922
|
+
pathParams: ["id"]
|
|
4923
|
+
},
|
|
4924
|
+
{
|
|
4925
|
+
operationId: "postApiPermissions",
|
|
4926
|
+
method: "post",
|
|
4927
|
+
path: "/api/permissions",
|
|
4928
|
+
tags: ["Permissions"],
|
|
4929
|
+
summary: "Create a new permission",
|
|
4930
|
+
pathParams: []
|
|
4931
|
+
},
|
|
4932
|
+
{
|
|
4933
|
+
operationId: "postApiProjectApps",
|
|
4934
|
+
method: "post",
|
|
4935
|
+
path: "/api/project-apps",
|
|
4936
|
+
tags: ["Project Apps"],
|
|
4937
|
+
summary: "Create a project app",
|
|
4938
|
+
pathParams: []
|
|
4939
|
+
},
|
|
4940
|
+
{
|
|
4941
|
+
operationId: "postApiProjectAppsByIdPicture",
|
|
4942
|
+
method: "post",
|
|
4943
|
+
path: "/api/project-apps/{id}/picture",
|
|
4944
|
+
tags: ["Project Apps"],
|
|
4945
|
+
summary: "Upload a project-app picture",
|
|
4946
|
+
pathParams: ["id"]
|
|
4947
|
+
},
|
|
4948
|
+
{
|
|
4949
|
+
operationId: "postApiProjectAppsByIdPictureConfirm",
|
|
4950
|
+
method: "post",
|
|
4951
|
+
path: "/api/project-apps/{id}/picture/confirm",
|
|
4952
|
+
tags: ["Project Apps"],
|
|
4953
|
+
summary: "Record a completed direct upload of a project-app picture",
|
|
4954
|
+
pathParams: ["id"]
|
|
4955
|
+
},
|
|
4956
|
+
{
|
|
4957
|
+
operationId: "postApiProjectAppsByIdPictureUploadUrl",
|
|
4958
|
+
method: "post",
|
|
4959
|
+
path: "/api/project-apps/{id}/picture/upload-url",
|
|
4960
|
+
tags: ["Project Apps"],
|
|
4961
|
+
summary: "Request a direct-upload URL for a project-app picture",
|
|
4962
|
+
pathParams: ["id"]
|
|
4963
|
+
},
|
|
4964
|
+
{
|
|
4965
|
+
operationId: "postApiProjects",
|
|
4966
|
+
method: "post",
|
|
4967
|
+
path: "/api/projects",
|
|
4968
|
+
tags: ["Projects"],
|
|
4969
|
+
summary: "Create a new project",
|
|
4970
|
+
pathParams: []
|
|
4971
|
+
},
|
|
4972
|
+
{
|
|
4973
|
+
operationId: "postApiProjectsByIdPicture",
|
|
4974
|
+
method: "post",
|
|
4975
|
+
path: "/api/projects/{id}/picture",
|
|
4976
|
+
tags: ["Projects"],
|
|
4977
|
+
summary: "Upload a project picture",
|
|
4978
|
+
pathParams: ["id"]
|
|
4979
|
+
},
|
|
4980
|
+
{
|
|
4981
|
+
operationId: "postApiProjectsByIdPictureConfirm",
|
|
4982
|
+
method: "post",
|
|
4983
|
+
path: "/api/projects/{id}/picture/confirm",
|
|
4984
|
+
tags: ["Projects"],
|
|
4985
|
+
summary: "Record a completed direct upload of a project picture",
|
|
4986
|
+
pathParams: ["id"]
|
|
4987
|
+
},
|
|
4988
|
+
{
|
|
4989
|
+
operationId: "postApiProjectsByIdPictureUploadUrl",
|
|
4990
|
+
method: "post",
|
|
4991
|
+
path: "/api/projects/{id}/picture/upload-url",
|
|
4992
|
+
tags: ["Projects"],
|
|
4993
|
+
summary: "Request a direct-upload URL for a project picture",
|
|
4994
|
+
pathParams: ["id"]
|
|
4995
|
+
},
|
|
4996
|
+
{
|
|
4997
|
+
operationId: "postApiProjectsByIdSyncJobs",
|
|
4998
|
+
method: "post",
|
|
4999
|
+
path: "/api/projects/{id}/sync/jobs",
|
|
5000
|
+
tags: ["Projects"],
|
|
5001
|
+
summary: "Start an asynchronous CDM import job",
|
|
5002
|
+
pathParams: ["id"]
|
|
5003
|
+
},
|
|
5004
|
+
{
|
|
5005
|
+
operationId: "postApiProjectsByIdSyncJobsExport",
|
|
5006
|
+
method: "post",
|
|
5007
|
+
path: "/api/projects/{id}/sync/jobs/export",
|
|
5008
|
+
tags: ["Projects"],
|
|
5009
|
+
summary: "Enqueue an asynchronous CDM export job",
|
|
5010
|
+
pathParams: ["id"]
|
|
5011
|
+
},
|
|
5012
|
+
{
|
|
5013
|
+
operationId: "postApiResources",
|
|
5014
|
+
method: "post",
|
|
5015
|
+
path: "/api/resources",
|
|
5016
|
+
tags: ["Resources"],
|
|
5017
|
+
summary: "Create a new resource",
|
|
5018
|
+
pathParams: []
|
|
5019
|
+
},
|
|
5020
|
+
{
|
|
5021
|
+
operationId: "postApiRoles",
|
|
5022
|
+
method: "post",
|
|
5023
|
+
path: "/api/roles",
|
|
5024
|
+
tags: ["Roles"],
|
|
5025
|
+
summary: "Create a new role",
|
|
5026
|
+
pathParams: []
|
|
5027
|
+
},
|
|
5028
|
+
{
|
|
5029
|
+
operationId: "postApiSigningKeysRotate",
|
|
5030
|
+
method: "post",
|
|
5031
|
+
path: "/api/signing-keys/rotate",
|
|
5032
|
+
tags: ["Signing Keys"],
|
|
5033
|
+
summary: "Rotate signing key",
|
|
5034
|
+
pathParams: []
|
|
5035
|
+
},
|
|
5036
|
+
{
|
|
5037
|
+
operationId: "postApiTags",
|
|
5038
|
+
method: "post",
|
|
5039
|
+
path: "/api/tags",
|
|
5040
|
+
tags: ["Tags"],
|
|
5041
|
+
summary: "Create a new tag",
|
|
5042
|
+
pathParams: []
|
|
5043
|
+
},
|
|
5044
|
+
{
|
|
5045
|
+
operationId: "postApiUsers",
|
|
5046
|
+
method: "post",
|
|
5047
|
+
path: "/api/users",
|
|
5048
|
+
tags: ["Users"],
|
|
5049
|
+
summary: "Create a new user",
|
|
5050
|
+
pathParams: []
|
|
5051
|
+
},
|
|
5052
|
+
{
|
|
5053
|
+
operationId: "postApiWebhookSubscriptions",
|
|
5054
|
+
method: "post",
|
|
5055
|
+
path: "/api/webhook-subscriptions",
|
|
5056
|
+
tags: [],
|
|
5057
|
+
summary: "Create webhook subscription",
|
|
5058
|
+
pathParams: []
|
|
5059
|
+
},
|
|
5060
|
+
{
|
|
5061
|
+
operationId: "postApiWebhookSubscriptionsByIdRotateSecret",
|
|
5062
|
+
method: "post",
|
|
5063
|
+
path: "/api/webhook-subscriptions/{id}/rotate-secret",
|
|
5064
|
+
tags: [],
|
|
5065
|
+
summary: "Rotate a webhook subscription secret",
|
|
5066
|
+
pathParams: ["id"]
|
|
5067
|
+
},
|
|
5068
|
+
{
|
|
5069
|
+
operationId: "postApiWebhookSubscriptionsDeliveriesByDeliveryIdReplay",
|
|
5070
|
+
method: "post",
|
|
5071
|
+
path: "/api/webhook-subscriptions/deliveries/{deliveryId}/replay",
|
|
5072
|
+
tags: [],
|
|
5073
|
+
summary: "Replay a webhook delivery",
|
|
5074
|
+
pathParams: ["deliveryId"]
|
|
5075
|
+
},
|
|
5076
|
+
{
|
|
5077
|
+
operationId: "putApiMeNotificationPreferences",
|
|
5078
|
+
method: "put",
|
|
5079
|
+
path: "/api/me/notification-preferences",
|
|
5080
|
+
tags: ["Me"],
|
|
5081
|
+
summary: "Set one of my notification preferences",
|
|
5082
|
+
pathParams: []
|
|
5083
|
+
},
|
|
5084
|
+
{
|
|
5085
|
+
operationId: "putApiProjectOauthConnections",
|
|
5086
|
+
method: "put",
|
|
5087
|
+
path: "/api/project-oauth-connections",
|
|
5088
|
+
tags: ["Project OAuth Connections"],
|
|
5089
|
+
summary: "Upsert a project OAuth connection",
|
|
5090
|
+
pathParams: []
|
|
5091
|
+
}
|
|
5092
|
+
];
|
|
5093
|
+
//#endregion
|
|
5094
|
+
//#region src/commands/from-spec.ts
|
|
5095
|
+
var SKIP_PATHS = /* @__PURE__ */ new Set([
|
|
5096
|
+
"/api/auth/login",
|
|
5097
|
+
"/api/auth/register",
|
|
5098
|
+
"/api/auth/cli-callback",
|
|
5099
|
+
"/api/auth/github"
|
|
5100
|
+
]);
|
|
5101
|
+
function parseQuery(pairs) {
|
|
5102
|
+
const query = {};
|
|
5103
|
+
for (const pair of pairs ?? []) {
|
|
5104
|
+
const eq = pair.indexOf("=");
|
|
5105
|
+
if (eq <= 0) continue;
|
|
5106
|
+
query[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
5107
|
+
}
|
|
5108
|
+
return query;
|
|
5109
|
+
}
|
|
5110
|
+
function addOperationCommand(group, operation, verb) {
|
|
5111
|
+
const cmd = group.command(verb).description(operation.summary || `${operation.method.toUpperCase()} ${operation.path}`).option("-p, --profile <name>", "Profile to use (or GRANT_PROFILE)").option("--output <format>", "json or text").option("--scope-id <id>", "Override selected scope id").option("--tenant <tenant>", "Override selected scope tenant").option("-q, --query <pair...>", "Extra query pairs (key=value)").option("--body <json>", "JSON body");
|
|
5112
|
+
for (const param of operation.pathParams) cmd.requiredOption(`--${flagName(param)} <value>`, `Path parameter ${param}`);
|
|
5113
|
+
cmd.action(async (options) => {
|
|
5114
|
+
try {
|
|
5115
|
+
const params = {};
|
|
5116
|
+
for (const param of operation.pathParams) {
|
|
5117
|
+
const value = options[param] ?? options[flagName(param)];
|
|
5118
|
+
if (typeof value === "string") params[param] = value;
|
|
5119
|
+
}
|
|
5120
|
+
const path = applyPathParams(operation.path, params);
|
|
5121
|
+
const format = parseOutputFormat(options.output);
|
|
5122
|
+
const body = options.body ? JSON.parse(options.body) : void 0;
|
|
5123
|
+
const { data } = await authenticatedRequest({
|
|
5124
|
+
flags: {
|
|
5125
|
+
profile: options.profile,
|
|
5126
|
+
scopeId: options.scopeId,
|
|
5127
|
+
tenant: options.tenant
|
|
5128
|
+
},
|
|
5129
|
+
method: operation.method,
|
|
5130
|
+
path,
|
|
5131
|
+
query: parseQuery(options.query),
|
|
5132
|
+
body
|
|
5133
|
+
});
|
|
5134
|
+
printOutput(data, format);
|
|
5135
|
+
} catch (err) {
|
|
5136
|
+
handleCliError(err);
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
function createSpecCommands(program) {
|
|
5141
|
+
const mapped = mapOperations(vendoredOperations.filter((operation) => !SKIP_PATHS.has(operation.path)));
|
|
5142
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5143
|
+
for (const item of mapped) {
|
|
5144
|
+
const list = groups.get(item.group) ?? [];
|
|
5145
|
+
list.push(item);
|
|
5146
|
+
groups.set(item.group, list);
|
|
5147
|
+
}
|
|
5148
|
+
for (const [name, items] of [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
5149
|
+
if (program.commands.some((existing) => existing.name() === name)) continue;
|
|
5150
|
+
const group = program.command(name).description(`REST ${name} operations`);
|
|
5151
|
+
const verbs = /* @__PURE__ */ new Set();
|
|
5152
|
+
for (const item of items) {
|
|
5153
|
+
if (verbs.has(item.verb) || group.commands.some((existing) => existing.name() === item.verb)) continue;
|
|
5154
|
+
verbs.add(item.verb);
|
|
5155
|
+
addOperationCommand(group, item.operation, item.verb);
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
//#endregion
|
|
3585
5160
|
//#region src/commands/generate-types-impl.ts
|
|
3586
5161
|
/**
|
|
3587
5162
|
* Generate TypeScript content for ResourceSlug and ResourceAction from project data.
|
|
@@ -3630,19 +5205,25 @@ function generateTypesContent(slugs, actions) {
|
|
|
3630
5205
|
var DEFAULT_OUTPUT = "./grant-types.ts";
|
|
3631
5206
|
function createGenerateTypesCommand(program) {
|
|
3632
5207
|
program.command("generate-types").description("Query the selected project's resources and permissions, then generate ResourceSlug and ResourceAction TypeScript constants").option("-p, --profile <name>", "Profile to use (default: default profile)").option("-o, --output <path>", "Output file path (default: from grant start, or ./grant-types.ts)").option("--dry-run", "Print what would be generated without writing").addHelpText("after", "\nExample:\n grant generate-types --profile staging -o ./src/grant-types.ts\n").action(async (options) => {
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
5208
|
+
let config;
|
|
5209
|
+
let scope;
|
|
5210
|
+
try {
|
|
5211
|
+
const ctx = await resolveRuntimeContext({ profile: options.profile });
|
|
5212
|
+
config = ctx.config;
|
|
5213
|
+
scope = ctx.scope;
|
|
5214
|
+
} catch (err) {
|
|
5215
|
+
handleCliError(err);
|
|
5216
|
+
}
|
|
5217
|
+
if (!config || !scope) {
|
|
5218
|
+
console.error("No project selected for this profile. Run \"grant start\" first or set GRANT_SCOPE_ID and GRANT_SCOPE_TENANT.");
|
|
3636
5219
|
process.exitCode = 1;
|
|
3637
5220
|
return;
|
|
3638
5221
|
}
|
|
3639
|
-
const config = result.config;
|
|
3640
|
-
const scope = result.config.selectedScope;
|
|
3641
5222
|
const outputPath = resolve(process.cwd(), options.output ?? config.generateTypesOutputPath ?? DEFAULT_OUTPUT);
|
|
3642
5223
|
const dryRun = options.dryRun === true;
|
|
3643
5224
|
try {
|
|
3644
5225
|
const accessToken = await resolveAccessToken(config);
|
|
3645
|
-
const [resources, permissions] = await Promise.all([fetchResources(config.apiUrl, accessToken, scope), fetchPermissions(config.apiUrl, accessToken, scope)]);
|
|
5226
|
+
const [resources, permissions] = await Promise.all([fetchResources(config.apiUrl, accessToken, scope, config.originVerifySecret), fetchPermissions(config.apiUrl, accessToken, scope, config.originVerifySecret)]);
|
|
3646
5227
|
const slugs = resources.map((r) => r.slug).filter(Boolean);
|
|
3647
5228
|
const actions = permissions.map((p) => p.action).filter(Boolean);
|
|
3648
5229
|
const content = generateTypesContent(slugs, actions);
|
|
@@ -4116,7 +5697,7 @@ function createStartCommand(program) {
|
|
|
4116
5697
|
//#endregion
|
|
4117
5698
|
//#region src/utils/package.ts
|
|
4118
5699
|
function getPackageVersion() {
|
|
4119
|
-
return "1.
|
|
5700
|
+
return "1.13.0";
|
|
4120
5701
|
}
|
|
4121
5702
|
//#endregion
|
|
4122
5703
|
//#region src/commands/version.ts
|
|
@@ -4128,6 +5709,35 @@ function createVersionCommand(program) {
|
|
|
4128
5709
|
});
|
|
4129
5710
|
}
|
|
4130
5711
|
//#endregion
|
|
5712
|
+
//#region src/commands/whoami.ts
|
|
5713
|
+
function createWhoamiCommand(program) {
|
|
5714
|
+
program.command("whoami").description("Show the authenticated caller (GET /api/me)").option("-p, --profile <name>", "Profile to use (or GRANT_PROFILE)").option("--output <format>", "json or text").option("--scope-id <id>", "Override selected scope id").option("--tenant <tenant>", "Override selected scope tenant").action(async (options) => {
|
|
5715
|
+
try {
|
|
5716
|
+
const format = parseOutputFormat(options.output);
|
|
5717
|
+
const ctx = await resolveRuntimeContext({
|
|
5718
|
+
profile: options.profile,
|
|
5719
|
+
scopeId: options.scopeId,
|
|
5720
|
+
tenant: options.tenant
|
|
5721
|
+
});
|
|
5722
|
+
const { data } = await authenticatedRequest({
|
|
5723
|
+
flags: options,
|
|
5724
|
+
method: "GET",
|
|
5725
|
+
path: "/api/me",
|
|
5726
|
+
injectScope: false
|
|
5727
|
+
});
|
|
5728
|
+
printOutput({
|
|
5729
|
+
profile: ctx.profileName,
|
|
5730
|
+
apiUrl: ctx.apiUrl,
|
|
5731
|
+
authMethod: ctx.authMethod,
|
|
5732
|
+
scope: ctx.scope ?? null,
|
|
5733
|
+
me: data
|
|
5734
|
+
}, format);
|
|
5735
|
+
} catch (err) {
|
|
5736
|
+
handleCliError(err);
|
|
5737
|
+
}
|
|
5738
|
+
});
|
|
5739
|
+
}
|
|
5740
|
+
//#endregion
|
|
4131
5741
|
//#region src/index.ts
|
|
4132
5742
|
var program = new Command();
|
|
4133
5743
|
program.name("grant").description("Grant CLI - Setup, authentication, and typings generation for @grantjs/server").enablePositionalOptions().addHelpText("after", `
|
|
@@ -4137,6 +5747,9 @@ Examples:
|
|
|
4137
5747
|
grant config list List profiles and default
|
|
4138
5748
|
grant config show --profile staging Show config for a profile
|
|
4139
5749
|
grant config set api-url http://localhost:4000 --profile default
|
|
5750
|
+
grant whoami --profile ci --output json
|
|
5751
|
+
grant api get /api/users --profile ci
|
|
5752
|
+
grant users list --output json
|
|
4140
5753
|
grant generate-types --profile staging Generate types for a profile
|
|
4141
5754
|
grant --help Show this help
|
|
4142
5755
|
grant config set --help Show help for config set subcommands
|
|
@@ -4145,6 +5758,9 @@ createVersionCommand(program);
|
|
|
4145
5758
|
createConfigCommand(program);
|
|
4146
5759
|
createStartCommand(program);
|
|
4147
5760
|
createGenerateTypesCommand(program);
|
|
5761
|
+
createWhoamiCommand(program);
|
|
5762
|
+
createApiCommand(program);
|
|
5763
|
+
createSpecCommands(program);
|
|
4148
5764
|
program.parse();
|
|
4149
5765
|
//#endregion
|
|
4150
5766
|
|