ninepm 0.58.0 → 0.60.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 +2 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +85 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Authenticate in your own browser. The CLI stores the token in native protected s
|
|
|
23
23
|
|
|
24
24
|
For CI, set `NINEPM_API_KEY` yourself in the CI secret store. `--token` and `NINEPM_API_KEY` still override the stored login token.
|
|
25
25
|
|
|
26
|
+
A key for CI or a server is minted from a signed-in machine with `9pm api-key create --name <name>`; the token is printed once. `9pm api-key ls` lists your active keys and `9pm api-key revoke <key-id>` retires one. Each account holds at most 10 such keys at a time (login tokens do not count). Store the key under any name but `NINEPM_*` when it lives in a 9pm app's own environment, and pass it with `--token`.
|
|
27
|
+
|
|
26
28
|
## Inventory
|
|
27
29
|
|
|
28
30
|
List the apps you have on 9pm.ai before deploying or modifying:
|
package/dist/index.d.ts
CHANGED
|
@@ -116,7 +116,7 @@ export interface CreatedServiceToken {
|
|
|
116
116
|
clientSecret: string;
|
|
117
117
|
}
|
|
118
118
|
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
119
|
-
export declare const CLI_VERSION = "0.
|
|
119
|
+
export declare const CLI_VERSION = "0.60.0";
|
|
120
120
|
type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
|
|
121
121
|
export interface StoredCredentialEntry {
|
|
122
122
|
token: string;
|
|
@@ -453,6 +453,21 @@ type CliCommand = {
|
|
|
453
453
|
site?: string;
|
|
454
454
|
apiUrl: string;
|
|
455
455
|
token: string;
|
|
456
|
+
} | {
|
|
457
|
+
kind: "api-key";
|
|
458
|
+
action: {
|
|
459
|
+
kind: "create";
|
|
460
|
+
name?: string;
|
|
461
|
+
json: boolean;
|
|
462
|
+
} | {
|
|
463
|
+
kind: "ls";
|
|
464
|
+
json: boolean;
|
|
465
|
+
} | {
|
|
466
|
+
kind: "revoke";
|
|
467
|
+
keyId: string;
|
|
468
|
+
};
|
|
469
|
+
apiUrl: string;
|
|
470
|
+
token: string;
|
|
456
471
|
} | {
|
|
457
472
|
kind: "files";
|
|
458
473
|
action: {
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { managedHostnamesIn, siblingCallWarning, siblingsOf } from "./sibling-ho
|
|
|
15
15
|
import { MAX_STATIC_ASSET_FILE_BYTES, MAX_WORKER_ENTRY_COMPRESSED_BYTES, MAX_WORKER_SCRIPT_COMPRESSED_BYTES, WORKER_ENTRY_COMPRESSED_WARN_BYTES, assetFileTooLargeMessage, formatMebibytes, formatMegabytes, oversizedAssets, workerBundleNearLimitMessage, workerBundleTooLargeMessage, workerEntryNearLimitMessage, workerEntryTooLargeMessage, } from "./deploy-limits.js";
|
|
16
16
|
import { buildFileManifest, readManifestFile } from "./manifest.js";
|
|
17
17
|
import { API_KEY_HELP, API_URL_HELP, CLI_CLIENT_ID, DEFAULT_API_URL, reportCliError } from "./cli-config.js";
|
|
18
|
-
export const CLI_VERSION = "0.
|
|
18
|
+
export const CLI_VERSION = "0.60.0";
|
|
19
19
|
const PUBLIC_DASHBOARD_URL = "https://9pm.ai/dashboard";
|
|
20
20
|
const TOKEN_SERVICE = "9pm";
|
|
21
21
|
const TOKEN_ACCOUNT = "default";
|
|
@@ -903,15 +903,21 @@ function revocationFailureReason(error) {
|
|
|
903
903
|
}
|
|
904
904
|
return error instanceof Error ? error.message : String(error);
|
|
905
905
|
}
|
|
906
|
-
// The
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
// credential arrived at all and malformed_api_key means the header was not a
|
|
910
|
-
// bearer token, both of which a proxy that strips or rewrites Authorization
|
|
911
|
-
//
|
|
912
|
-
//
|
|
906
|
+
// The refusals that prove something about the stored credential: requireAuth emits
|
|
907
|
+
// both AFTER looking the presented token up (src/server/routes/guards.ts), so each
|
|
908
|
+
// is the control plane judging this credential. Their siblings do not — missing_api_key
|
|
909
|
+
// means no credential arrived at all and malformed_api_key means the header was not a
|
|
910
|
+
// bearer token, both of which a proxy that strips or rewrites Authorization produces
|
|
911
|
+
// for a perfectly live key. Anything else answering 401/403 is an intermediary rather
|
|
912
|
+
// than the control plane judging this credential.
|
|
913
|
+
//
|
|
914
|
+
// A set rather than a comparison because the list is now open: a key refused for
|
|
915
|
+
// inactivity (#854) is as dead to its holder as a revoked one, and a refusal this
|
|
916
|
+
// function does not recognise turns a clean `9pm logout` into a reported failure
|
|
917
|
+
// blaming an intermediary that was never involved.
|
|
918
|
+
const CREDENTIAL_REFUSAL_CODES = new Set(["invalid_api_key", "expired_api_key"]);
|
|
913
919
|
async function isAuthRefusal(response) {
|
|
914
|
-
return (await responseErrorCode(response))
|
|
920
|
+
return CREDENTIAL_REFUSAL_CODES.has(await responseErrorCode(response));
|
|
915
921
|
}
|
|
916
922
|
// The `code` from the control plane's {error:{code}} envelope, or "" when the
|
|
917
923
|
// body is not that shape (proxy error page, empty body).
|
|
@@ -2285,6 +2291,9 @@ export function parseArgs(argv) {
|
|
|
2285
2291
|
if (command === "support") {
|
|
2286
2292
|
return parseSupportArgs(argv.slice(1));
|
|
2287
2293
|
}
|
|
2294
|
+
if (command === "api-key" || command === "api-keys") {
|
|
2295
|
+
return parseApiKeyArgs(argv.slice(1));
|
|
2296
|
+
}
|
|
2288
2297
|
if (command !== "deploy") {
|
|
2289
2298
|
throw new Error(`Unknown command: ${command}`);
|
|
2290
2299
|
}
|
|
@@ -2969,6 +2978,30 @@ function readBaseApiOptions(flags) {
|
|
|
2969
2978
|
function readApiToken(flags) {
|
|
2970
2979
|
return readStringFlag(flags, "token") ?? process.env.NINEPM_API_KEY ?? readStoredCredential();
|
|
2971
2980
|
}
|
|
2981
|
+
function parseApiKeyArgs(argv) {
|
|
2982
|
+
const subcommand = argv[0];
|
|
2983
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
2984
|
+
throw new UsageError();
|
|
2985
|
+
}
|
|
2986
|
+
const flags = parseFlags(argv);
|
|
2987
|
+
const base = readBaseApiOptions(flags);
|
|
2988
|
+
const json = flags.has("json");
|
|
2989
|
+
if (subcommand === "create") {
|
|
2990
|
+
const name = readStringFlag(flags, "name");
|
|
2991
|
+
return { kind: "api-key", ...base, action: { kind: "create", ...(name ? { name } : {}), json } };
|
|
2992
|
+
}
|
|
2993
|
+
if (subcommand === "ls" || subcommand === "list") {
|
|
2994
|
+
return { kind: "api-key", ...base, action: { kind: "ls", json } };
|
|
2995
|
+
}
|
|
2996
|
+
if (subcommand === "revoke") {
|
|
2997
|
+
const keyId = argv[1];
|
|
2998
|
+
if (!keyId || keyId.startsWith("--")) {
|
|
2999
|
+
throw new Error("Usage: 9pm api-key revoke <key-id>");
|
|
3000
|
+
}
|
|
3001
|
+
return { kind: "api-key", ...base, action: { kind: "revoke", keyId } };
|
|
3002
|
+
}
|
|
3003
|
+
throw new Error(`Unknown api-key subcommand: ${subcommand}`);
|
|
3004
|
+
}
|
|
2972
3005
|
function parseSupportArgs(argv) {
|
|
2973
3006
|
// Normalize the -m short flag so parseFlags (which only handles --flags) sees it.
|
|
2974
3007
|
const normalized = argv.map((arg) => (arg === "-m" ? "--message" : arg));
|
|
@@ -4228,6 +4261,10 @@ async function main() {
|
|
|
4228
4261
|
await runSupportCommand(command);
|
|
4229
4262
|
return;
|
|
4230
4263
|
}
|
|
4264
|
+
if (command.kind === "api-key") {
|
|
4265
|
+
await runApiKeyCommand(command);
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
4231
4268
|
await runAccessCommand(command);
|
|
4232
4269
|
}
|
|
4233
4270
|
catch (error) {
|
|
@@ -4240,6 +4277,42 @@ async function main() {
|
|
|
4240
4277
|
process.exitCode = 1;
|
|
4241
4278
|
}
|
|
4242
4279
|
}
|
|
4280
|
+
async function runApiKeyCommand(command) {
|
|
4281
|
+
const base = { apiUrl: command.apiUrl, token: command.token };
|
|
4282
|
+
const action = command.action;
|
|
4283
|
+
if (action.kind === "create") {
|
|
4284
|
+
const result = await apiRequest(base, "/v1/me/api-keys", {
|
|
4285
|
+
method: "POST",
|
|
4286
|
+
body: JSON.stringify(action.name ? { name: action.name } : {}),
|
|
4287
|
+
});
|
|
4288
|
+
if (action.json) {
|
|
4289
|
+
console.log(JSON.stringify({ apiKey: result.apiKey, token: result.token }));
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
console.log(`Created API key ${result.apiKey.id} (${result.apiKey.name}). The token is shown once:`);
|
|
4293
|
+
console.log(result.token);
|
|
4294
|
+
console.log("Store it in your CI or server secret store under any name but NINEPM_*, and pass it as --token or NINEPM_API_KEY.");
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
if (action.kind === "ls") {
|
|
4298
|
+
const result = await apiRequest(base, "/v1/me/api-keys", { method: "GET" });
|
|
4299
|
+
const live = result.apiKeys.filter((key) => !key.revokedAt);
|
|
4300
|
+
if (action.json) {
|
|
4301
|
+
console.log(JSON.stringify({ apiKeys: live }));
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
if (live.length === 0) {
|
|
4305
|
+
console.log("No active API keys.");
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
for (const key of live) {
|
|
4309
|
+
console.log(`${key.id}\t${key.name}\t${key.prefix}...\tcreated ${key.createdAt.slice(0, 10)}\tlast used ${key.lastUsedAt ? key.lastUsedAt.slice(0, 10) : "never"}`);
|
|
4310
|
+
}
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const revoked = await apiRequest(base, `/v1/me/api-keys/${encodeURIComponent(action.keyId)}/revoke`, { method: "POST" });
|
|
4314
|
+
console.log(`Revoked API key ${revoked.apiKey.id} (${revoked.apiKey.name}).`);
|
|
4315
|
+
}
|
|
4243
4316
|
async function runSupportCommand(command) {
|
|
4244
4317
|
const subject = command.subject ?? (command.message.split("\n")[0] ?? command.message).slice(0, 200);
|
|
4245
4318
|
const body = {
|
|
@@ -4481,6 +4554,9 @@ function printUsage() {
|
|
|
4481
4554
|
9pm access service revoke <site> <service-token-id>
|
|
4482
4555
|
9pm access token create <site> [--name <name>]
|
|
4483
4556
|
9pm access token revoke <site> <token-id>
|
|
4557
|
+
9pm api-key create [--name <name>] [--json] mint a platform API key for CI or a server; the token is shown once
|
|
4558
|
+
9pm api-key ls [--json]
|
|
4559
|
+
9pm api-key revoke <key-id>
|
|
4484
4560
|
|
|
4485
4561
|
Environment:
|
|
4486
4562
|
NINEPM_API_URL ${API_URL_HELP}
|