run402 3.7.14 → 3.8.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/cli.mjs +6 -0
- package/core-dist/config.js +9 -0
- package/core-dist/keystore.js +103 -68
- package/core-dist/profile-state.js +137 -0
- package/lib/config.mjs +31 -20
- package/lib/credentials.mjs +273 -0
- package/lib/domains.mjs +40 -20
- package/lib/next-actions.mjs +7 -0
- package/lib/projects.mjs +65 -27
- package/lib/sdk-errors.mjs +24 -3
- package/lib/sdk.mjs +3 -2
- package/package.json +1 -1
- package/sdk/core-dist/config.js +9 -0
- package/sdk/core-dist/keystore.js +103 -68
- package/sdk/core-dist/profile-state.js +137 -0
- package/sdk/dist/credentials.d.ts +24 -7
- package/sdk/dist/credentials.d.ts.map +1 -1
- package/sdk/dist/credentials.js +4 -4
- package/sdk/dist/errors.d.ts +27 -1
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +61 -3
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +7 -2
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +8 -3
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +2 -0
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +5 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/ai.d.ts.map +1 -1
- package/sdk/dist/namespaces/ai.js +5 -10
- package/sdk/dist/namespaces/ai.js.map +1 -1
- package/sdk/dist/namespaces/apps.d.ts.map +1 -1
- package/sdk/dist/namespaces/apps.js +5 -13
- package/sdk/dist/namespaces/apps.js.map +1 -1
- package/sdk/dist/namespaces/assets.d.ts.map +1 -1
- package/sdk/dist/namespaces/assets.js +9 -22
- package/sdk/dist/namespaces/assets.js.map +1 -1
- package/sdk/dist/namespaces/auth.d.ts.map +1 -1
- package/sdk/dist/namespaces/auth.js +16 -43
- package/sdk/dist/namespaces/auth.js.map +1 -1
- package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
- package/sdk/dist/namespaces/contracts.js +12 -31
- package/sdk/dist/namespaces/contracts.js.map +1 -1
- package/sdk/dist/namespaces/credentials.d.ts +48 -0
- package/sdk/dist/namespaces/credentials.d.ts.map +1 -0
- package/sdk/dist/namespaces/credentials.js +115 -0
- package/sdk/dist/namespaces/credentials.js.map +1 -0
- package/sdk/dist/namespaces/domains.d.ts +8 -2
- package/sdk/dist/namespaces/domains.d.ts.map +1 -1
- package/sdk/dist/namespaces/domains.js +41 -23
- package/sdk/dist/namespaces/domains.js.map +1 -1
- package/sdk/dist/namespaces/email.d.ts.map +1 -1
- package/sdk/dist/namespaces/email.js +8 -19
- package/sdk/dist/namespaces/email.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +14 -37
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/jobs.d.ts.map +1 -1
- package/sdk/dist/namespaces/jobs.js +8 -19
- package/sdk/dist/namespaces/jobs.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +4 -4
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +18 -42
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/secrets.d.ts.map +1 -1
- package/sdk/dist/namespaces/secrets.js +5 -10
- package/sdk/dist/namespaces/secrets.js.map +1 -1
- package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -1
- package/sdk/dist/namespaces/sender-domain.js +6 -16
- package/sdk/dist/namespaces/sender-domain.js.map +1 -1
- package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
- package/sdk/dist/namespaces/subdomains.js +5 -10
- package/sdk/dist/namespaces/subdomains.js.map +1 -1
- package/sdk/dist/node/credentials.d.ts +18 -4
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +37 -9
- package/sdk/dist/node/credentials.js.map +1 -1
- package/sdk/dist/node/index.d.ts +4 -2
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +2 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/target-profile.d.ts.map +1 -1
- package/sdk/dist/node/target-profile.js +6 -4
- package/sdk/dist/node/target-profile.js.map +1 -1
- package/sdk/dist/project-auth-classification.d.ts +125 -0
- package/sdk/dist/project-auth-classification.d.ts.map +1 -0
- package/sdk/dist/project-auth-classification.js +135 -0
- package/sdk/dist/project-auth-classification.js.map +1 -0
- package/sdk/dist/project-credentials.d.ts +4 -0
- package/sdk/dist/project-credentials.d.ts.map +1 -0
- package/sdk/dist/project-credentials.js +9 -0
- package/sdk/dist/project-credentials.js.map +1 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
activeProfile,
|
|
5
|
+
getProject,
|
|
6
|
+
loadKeyStore,
|
|
7
|
+
projectCredentialsFile,
|
|
8
|
+
removeProject,
|
|
9
|
+
saveProject,
|
|
10
|
+
} from "./config.mjs";
|
|
11
|
+
import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
|
|
12
|
+
import { fail } from "./sdk-errors.mjs";
|
|
13
|
+
|
|
14
|
+
const HELP = `run402 credentials — Manage local credential material
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
run402 credentials <subcommand> [args...]
|
|
18
|
+
|
|
19
|
+
Subcommands:
|
|
20
|
+
project-keys Manage the local project-key cache
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
run402 credentials project-keys list
|
|
24
|
+
run402 credentials project-keys status --project prj_abc123
|
|
25
|
+
run402 credentials project-keys import --project prj_abc123 --service-key-stdin
|
|
26
|
+
run402 credentials project-keys export --project prj_abc123 --reveal
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const PROJECT_KEYS_HELP = `run402 credentials project-keys — Manage local project-key cache entries
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
run402 credentials project-keys <subcommand> [options]
|
|
33
|
+
|
|
34
|
+
Subcommands:
|
|
35
|
+
list List cached project-key entries, redacted
|
|
36
|
+
status --project <id> Show one cached entry, redacted
|
|
37
|
+
import --project <id> --service-key-stdin
|
|
38
|
+
import --project <id> --service-key-env <env>
|
|
39
|
+
export --project <id> --reveal Print cached keys, including secrets
|
|
40
|
+
remove --project <id> Remove one cached key entry
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- This is a LOCAL CACHE surface. It is not project inventory.
|
|
44
|
+
- list/status never reveal full keys.
|
|
45
|
+
- export requires --reveal.
|
|
46
|
+
- import accepts service keys through stdin or an environment variable, not argv.
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
function parseProjectKeyFlags(args, extraKnown = [], valueFlagsExtra = []) {
|
|
50
|
+
const parsed = normalizeArgv(args);
|
|
51
|
+
const valueFlags = ["--project", ...valueFlagsExtra];
|
|
52
|
+
assertKnownFlags(parsed, ["--project", "--help", "-h", ...extraKnown], valueFlags);
|
|
53
|
+
return {
|
|
54
|
+
projectId: flagValue(parsed, "--project"),
|
|
55
|
+
parsed,
|
|
56
|
+
rest: positionalArgs(parsed, valueFlags),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fingerprint(value) {
|
|
61
|
+
if (!value) return null;
|
|
62
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function prefix(value) {
|
|
66
|
+
if (!value) return null;
|
|
67
|
+
return `${value.slice(0, 8)}...`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function provenance() {
|
|
71
|
+
const profile = activeProfile();
|
|
72
|
+
return {
|
|
73
|
+
source: "local_cache",
|
|
74
|
+
cache_path: projectCredentialsFile(),
|
|
75
|
+
wallet: profile,
|
|
76
|
+
profile,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function redactedEntry(projectId, entry) {
|
|
81
|
+
return {
|
|
82
|
+
project_id: projectId,
|
|
83
|
+
configured: Boolean(entry),
|
|
84
|
+
has_anon_key: Boolean(entry?.anon_key),
|
|
85
|
+
has_service_key: Boolean(entry?.service_key),
|
|
86
|
+
anon_key_prefix: prefix(entry?.anon_key),
|
|
87
|
+
service_key_prefix: prefix(entry?.service_key),
|
|
88
|
+
anon_key_fingerprint: fingerprint(entry?.anon_key),
|
|
89
|
+
service_key_fingerprint: fingerprint(entry?.service_key),
|
|
90
|
+
site_url: entry?.site_url ?? null,
|
|
91
|
+
cached_at: entry?.cached_at ?? null,
|
|
92
|
+
...provenance(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function requireProjectFlag(projectId, usage) {
|
|
97
|
+
if (!projectId) {
|
|
98
|
+
fail({
|
|
99
|
+
code: "BAD_USAGE",
|
|
100
|
+
message: "Missing --project <id>.",
|
|
101
|
+
hint: usage,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return projectId;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function requireCachedProject(projectId) {
|
|
108
|
+
const entry = getProject(projectId);
|
|
109
|
+
if (!entry) {
|
|
110
|
+
fail({
|
|
111
|
+
code: "PROJECT_CREDENTIAL_NOT_FOUND",
|
|
112
|
+
message: `No local project credentials cached for ${projectId}.`,
|
|
113
|
+
hint: "Import keys with `run402 credentials project-keys import --project <id> --service-key-stdin` if this operation truly requires local project credentials.",
|
|
114
|
+
details: { project_id: projectId, ...provenance() },
|
|
115
|
+
next_actions: [{
|
|
116
|
+
type: "run_command",
|
|
117
|
+
command: `run402 credentials project-keys import --project ${projectId} --service-key-stdin`,
|
|
118
|
+
why: "Import a service key for credential-required operations.",
|
|
119
|
+
}],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return entry;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function list(args) {
|
|
126
|
+
const { rest } = parseProjectKeyFlags(args);
|
|
127
|
+
if (rest.length > 0) {
|
|
128
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys list: ${rest[0]}` });
|
|
129
|
+
}
|
|
130
|
+
const store = loadKeyStore(projectCredentialsFile());
|
|
131
|
+
const projects = Object.entries(store.projects ?? {})
|
|
132
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
133
|
+
.map(([projectId, entry]) => redactedEntry(projectId, entry));
|
|
134
|
+
console.log(JSON.stringify({ projects, ...provenance() }, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function status(args) {
|
|
138
|
+
const { projectId, rest } = parseProjectKeyFlags(args);
|
|
139
|
+
if (rest.length > 0) {
|
|
140
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys status: ${rest[0]}` });
|
|
141
|
+
}
|
|
142
|
+
const id = requireProjectFlag(projectId, "run402 credentials project-keys status --project <id>");
|
|
143
|
+
console.log(JSON.stringify(redactedEntry(id, getProject(id)), null, 2));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function readSecretInput(parsed) {
|
|
147
|
+
const fromEnv = flagValue(parsed, "--service-key-env");
|
|
148
|
+
const fromStdin = parsed.includes("--service-key-stdin");
|
|
149
|
+
if (fromEnv && fromStdin) {
|
|
150
|
+
fail({ code: "BAD_USAGE", message: "Use either --service-key-env or --service-key-stdin, not both." });
|
|
151
|
+
}
|
|
152
|
+
if (fromEnv) {
|
|
153
|
+
const value = process.env[fromEnv];
|
|
154
|
+
if (!value) {
|
|
155
|
+
fail({
|
|
156
|
+
code: "BAD_ENV",
|
|
157
|
+
message: `Environment variable ${fromEnv} is empty or unset.`,
|
|
158
|
+
details: { env: fromEnv },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return value.trim();
|
|
162
|
+
}
|
|
163
|
+
if (fromStdin) return readFileSync(0, "utf-8").trim();
|
|
164
|
+
fail({
|
|
165
|
+
code: "BAD_USAGE",
|
|
166
|
+
message: "Import requires --service-key-stdin or --service-key-env <env>.",
|
|
167
|
+
hint: "Do not pass service keys as command-line values; argv can leak through shell history and process listings.",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function importKey(args) {
|
|
172
|
+
const { projectId, parsed, rest } = parseProjectKeyFlags(
|
|
173
|
+
args,
|
|
174
|
+
["--service-key-stdin", "--service-key-env", "--anon-key-env"],
|
|
175
|
+
["--service-key-env", "--anon-key-env"],
|
|
176
|
+
);
|
|
177
|
+
if (rest.length > 0) {
|
|
178
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys import: ${rest[0]}` });
|
|
179
|
+
}
|
|
180
|
+
const id = requireProjectFlag(projectId, "run402 credentials project-keys import --project <id> --service-key-stdin");
|
|
181
|
+
const serviceKey = readSecretInput(parsed);
|
|
182
|
+
if (!serviceKey) {
|
|
183
|
+
fail({ code: "BAD_USAGE", message: "Service key input was empty." });
|
|
184
|
+
}
|
|
185
|
+
const anonEnv = flagValue(parsed, "--anon-key-env");
|
|
186
|
+
const anonKey = anonEnv ? process.env[anonEnv] : undefined;
|
|
187
|
+
if (anonEnv && !anonKey) {
|
|
188
|
+
fail({ code: "BAD_ENV", message: `Environment variable ${anonEnv} is empty or unset.`, details: { env: anonEnv } });
|
|
189
|
+
}
|
|
190
|
+
const existing = getProject(id);
|
|
191
|
+
saveProject(id, {
|
|
192
|
+
anon_key: anonKey ?? existing?.anon_key ?? "",
|
|
193
|
+
service_key: serviceKey,
|
|
194
|
+
site_url: existing?.site_url,
|
|
195
|
+
deployed_at: existing?.deployed_at,
|
|
196
|
+
last_deployment_id: existing?.last_deployment_id,
|
|
197
|
+
source: "manual_import",
|
|
198
|
+
cached_at: new Date().toISOString(),
|
|
199
|
+
});
|
|
200
|
+
console.log(JSON.stringify({ imported: true, ...redactedEntry(id, getProject(id)) }, null, 2));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function exportKey(args) {
|
|
204
|
+
const { projectId, parsed, rest } = parseProjectKeyFlags(args, ["--reveal"]);
|
|
205
|
+
if (rest.length > 0) {
|
|
206
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys export: ${rest[0]}` });
|
|
207
|
+
}
|
|
208
|
+
const id = requireProjectFlag(projectId, "run402 credentials project-keys export --project <id> --reveal");
|
|
209
|
+
if (!parsed.includes("--reveal")) {
|
|
210
|
+
fail({
|
|
211
|
+
code: "REVEAL_REQUIRED",
|
|
212
|
+
message: "Exporting full project keys requires --reveal.",
|
|
213
|
+
hint: "Use `run402 credentials project-keys status --project <id>` for redacted output.",
|
|
214
|
+
details: { project_id: id, ...provenance() },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const entry = requireCachedProject(id);
|
|
218
|
+
console.log(JSON.stringify({ project_id: id, ...entry, ...provenance(), revealed: true }, null, 2));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function remove(args) {
|
|
222
|
+
const { projectId, rest } = parseProjectKeyFlags(args);
|
|
223
|
+
if (rest.length > 0) {
|
|
224
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys remove: ${rest[0]}` });
|
|
225
|
+
}
|
|
226
|
+
const id = requireProjectFlag(projectId, "run402 credentials project-keys remove --project <id>");
|
|
227
|
+
const existed = Boolean(getProject(id));
|
|
228
|
+
removeProject(id, projectCredentialsFile());
|
|
229
|
+
console.log(JSON.stringify({ project_id: id, removed: existed, ...provenance() }, null, 2));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function runProjectKeys(sub, args) {
|
|
233
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
234
|
+
console.log(PROJECT_KEYS_HELP);
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
|
|
238
|
+
console.log(PROJECT_KEYS_HELP);
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
241
|
+
switch (sub) {
|
|
242
|
+
case "list": await list(args); break;
|
|
243
|
+
case "status": await status(args); break;
|
|
244
|
+
case "import": await importKey(args); break;
|
|
245
|
+
case "export": await exportKey(args); break;
|
|
246
|
+
case "remove": await remove(args); break;
|
|
247
|
+
default:
|
|
248
|
+
fail({
|
|
249
|
+
code: "UNKNOWN_SUBCOMMAND",
|
|
250
|
+
message: `Unknown credentials project-keys subcommand: ${sub}`,
|
|
251
|
+
hint: "Run `run402 credentials project-keys --help` for usage.",
|
|
252
|
+
details: { command: "credentials project-keys", subcommand: sub },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function run(sub, args = []) {
|
|
258
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
259
|
+
console.log(HELP);
|
|
260
|
+
process.exit(0);
|
|
261
|
+
}
|
|
262
|
+
if (sub === "project-keys") {
|
|
263
|
+
const [projectKeySub, ...rest] = Array.isArray(args) ? args : [];
|
|
264
|
+
await runProjectKeys(projectKeySub, rest);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
fail({
|
|
268
|
+
code: "UNKNOWN_SUBCOMMAND",
|
|
269
|
+
message: `Unknown credentials subcommand: ${sub}`,
|
|
270
|
+
hint: "Run `run402 credentials --help` for usage.",
|
|
271
|
+
details: { command: "credentials", subcommand: sub },
|
|
272
|
+
});
|
|
273
|
+
}
|
package/lib/domains.mjs
CHANGED
|
@@ -9,14 +9,15 @@ Usage:
|
|
|
9
9
|
run402 domains <subcommand> [args...]
|
|
10
10
|
|
|
11
11
|
Subcommands:
|
|
12
|
-
add <domain> <subdomain_name> [--project <id>]
|
|
13
|
-
list [--project <id>]
|
|
14
|
-
status <domain> [--project <id>]
|
|
15
|
-
delete <domain> --confirm [--project <id>]
|
|
12
|
+
add <domain> <subdomain_name> [--project <id>] [--auth principal|service-key]
|
|
13
|
+
list [--project <id>] [--auth principal|service-key]
|
|
14
|
+
status <domain> [--project <id>] [--auth principal|service-key]
|
|
15
|
+
delete <domain> --confirm [--project <id>] [--auth principal|service-key]
|
|
16
16
|
|
|
17
17
|
Examples:
|
|
18
18
|
run402 domains add example.com myapp
|
|
19
|
-
|
|
19
|
+
run402 domains add example.com myapp --project prj_123
|
|
20
|
+
run402 domains list --project prj_123 --auth service-key
|
|
20
21
|
run402 domains list
|
|
21
22
|
run402 domains status example.com
|
|
22
23
|
run402 domains delete example.com --confirm
|
|
@@ -31,7 +32,7 @@ const SUB_HELP = {
|
|
|
31
32
|
add: `run402 domains add — Register a custom domain for a project
|
|
32
33
|
|
|
33
34
|
Usage:
|
|
34
|
-
run402 domains add <domain> <subdomain_name> [--project <id>]
|
|
35
|
+
run402 domains add <domain> <subdomain_name> [--project <id>] [--auth principal|service-key]
|
|
35
36
|
|
|
36
37
|
Arguments:
|
|
37
38
|
<domain> Custom domain (e.g. example.com)
|
|
@@ -39,6 +40,8 @@ Arguments:
|
|
|
39
40
|
|
|
40
41
|
Options:
|
|
41
42
|
--project <id> Project ID (defaults to the active project)
|
|
43
|
+
--auth <mode> principal (default, server-authoritative) or service-key
|
|
44
|
+
(uses local project-key cache)
|
|
42
45
|
|
|
43
46
|
Notes:
|
|
44
47
|
- After adding, configure DNS as shown in the response
|
|
@@ -52,10 +55,14 @@ Examples:
|
|
|
52
55
|
list: `run402 domains list — List custom domains for a project
|
|
53
56
|
|
|
54
57
|
Usage:
|
|
55
|
-
run402 domains list [--project <id>]
|
|
58
|
+
run402 domains list [--project <id>] [--auth principal|service-key]
|
|
56
59
|
|
|
57
|
-
Arguments:
|
|
58
|
-
|
|
60
|
+
Arguments:
|
|
61
|
+
<id> Project ID (defaults to the active project)
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
--auth <mode> principal (default, server-authoritative) or service-key
|
|
65
|
+
(uses local project-key cache)
|
|
59
66
|
|
|
60
67
|
Examples:
|
|
61
68
|
run402 domains list
|
|
@@ -64,13 +71,15 @@ Examples:
|
|
|
64
71
|
status: `run402 domains status — Check DNS/SSL status of a custom domain
|
|
65
72
|
|
|
66
73
|
Usage:
|
|
67
|
-
run402 domains status <domain> [--project <id>]
|
|
74
|
+
run402 domains status <domain> [--project <id>] [--auth principal|service-key]
|
|
68
75
|
|
|
69
76
|
Arguments:
|
|
70
77
|
<domain> Custom domain to check
|
|
71
78
|
|
|
72
79
|
Options:
|
|
73
80
|
--project <id> Project ID (defaults to the active project)
|
|
81
|
+
--auth <mode> principal (default, server-authoritative) or service-key
|
|
82
|
+
(uses local project-key cache)
|
|
74
83
|
|
|
75
84
|
Examples:
|
|
76
85
|
run402 domains status example.com
|
|
@@ -79,7 +88,7 @@ Examples:
|
|
|
79
88
|
delete: `run402 domains delete — Release a custom domain
|
|
80
89
|
|
|
81
90
|
Usage:
|
|
82
|
-
run402 domains delete <domain> --confirm [--project <id>]
|
|
91
|
+
run402 domains delete <domain> --confirm [--project <id>] [--auth principal|service-key]
|
|
83
92
|
|
|
84
93
|
Arguments:
|
|
85
94
|
<domain> Custom domain to release
|
|
@@ -89,6 +98,8 @@ Options:
|
|
|
89
98
|
project and clears its DNS/SSL configuration
|
|
90
99
|
(irreversible)
|
|
91
100
|
--project <id> Project ID (defaults to the active project)
|
|
101
|
+
--auth <mode> principal (default, server-authoritative) or service-key
|
|
102
|
+
(uses local project-key cache)
|
|
92
103
|
|
|
93
104
|
Examples:
|
|
94
105
|
run402 domains delete example.com --confirm
|
|
@@ -97,17 +108,26 @@ Examples:
|
|
|
97
108
|
|
|
98
109
|
function parseProjectFlag(args, extraKnown = []) {
|
|
99
110
|
const parsedArgs = normalizeArgv(args);
|
|
100
|
-
const valueFlags = ["--project"];
|
|
111
|
+
const valueFlags = ["--project", "--auth"];
|
|
101
112
|
assertKnownFlags(parsedArgs, [...valueFlags, ...extraKnown, "--help", "-h"], valueFlags);
|
|
113
|
+
const auth = flagValue(parsedArgs, "--auth") ?? "principal";
|
|
114
|
+
if (auth !== "principal" && auth !== "service-key") {
|
|
115
|
+
fail({
|
|
116
|
+
code: "BAD_FLAG",
|
|
117
|
+
message: "--auth must be one of: principal, service-key.",
|
|
118
|
+
details: { flag: "--auth", value: auth, allowed: ["principal", "service-key"] },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
102
121
|
return {
|
|
103
122
|
project: flagValue(parsedArgs, "--project"),
|
|
123
|
+
authMode: auth === "service-key" ? "service_key" : "principal",
|
|
104
124
|
rest: positionalArgs(parsedArgs, valueFlags),
|
|
105
125
|
args: parsedArgs,
|
|
106
126
|
};
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
async function add(args) {
|
|
110
|
-
const { project, rest } = parseProjectFlag(args);
|
|
130
|
+
const { project, authMode, rest } = parseProjectFlag(args);
|
|
111
131
|
const domain = rest[0];
|
|
112
132
|
const subdomainName = rest[1];
|
|
113
133
|
if (!domain || !subdomainName) {
|
|
@@ -122,7 +142,7 @@ async function add(args) {
|
|
|
122
142
|
}
|
|
123
143
|
const projectId = resolveProjectId(project);
|
|
124
144
|
try {
|
|
125
|
-
const data = await getSdk().domains.add(projectId, { domain, subdomainName });
|
|
145
|
+
const data = await getSdk().domains.add(projectId, { domain, subdomainName, authMode });
|
|
126
146
|
console.log(JSON.stringify(data, null, 2));
|
|
127
147
|
} catch (err) {
|
|
128
148
|
reportSdkError(err);
|
|
@@ -131,7 +151,7 @@ async function add(args) {
|
|
|
131
151
|
|
|
132
152
|
async function list(args) {
|
|
133
153
|
const argList = Array.isArray(args) ? args : [];
|
|
134
|
-
const { project, rest } = parseProjectFlag(argList);
|
|
154
|
+
const { project, authMode, rest } = parseProjectFlag(argList);
|
|
135
155
|
if (rest.length > 0) {
|
|
136
156
|
fail({
|
|
137
157
|
code: "BAD_USAGE",
|
|
@@ -141,7 +161,7 @@ async function list(args) {
|
|
|
141
161
|
}
|
|
142
162
|
const projectId = resolveProjectId(project);
|
|
143
163
|
try {
|
|
144
|
-
const data = await getSdk().domains.list(projectId);
|
|
164
|
+
const data = await getSdk().domains.list(projectId, { authMode });
|
|
145
165
|
console.log(JSON.stringify(data, null, 2));
|
|
146
166
|
} catch (err) {
|
|
147
167
|
reportSdkError(err);
|
|
@@ -149,7 +169,7 @@ async function list(args) {
|
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
async function status(args) {
|
|
152
|
-
const { project, rest } = parseProjectFlag(args);
|
|
172
|
+
const { project, authMode, rest } = parseProjectFlag(args);
|
|
153
173
|
const domain = rest[0];
|
|
154
174
|
if (!domain) {
|
|
155
175
|
fail({
|
|
@@ -163,7 +183,7 @@ async function status(args) {
|
|
|
163
183
|
}
|
|
164
184
|
const projectId = resolveProjectId(project);
|
|
165
185
|
try {
|
|
166
|
-
const data = await getSdk().domains.status(projectId, domain);
|
|
186
|
+
const data = await getSdk().domains.status(projectId, domain, { authMode });
|
|
167
187
|
console.log(JSON.stringify(data, null, 2));
|
|
168
188
|
} catch (err) {
|
|
169
189
|
reportSdkError(err);
|
|
@@ -171,7 +191,7 @@ async function status(args) {
|
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
async function deleteDomain(args) {
|
|
174
|
-
const { project, rest, args: parsedArgs } = parseProjectFlag(args, ["--confirm"]);
|
|
194
|
+
const { project, authMode, rest, args: parsedArgs } = parseProjectFlag(args, ["--confirm"]);
|
|
175
195
|
const domain = rest[0];
|
|
176
196
|
if (!domain) {
|
|
177
197
|
fail({
|
|
@@ -192,7 +212,7 @@ async function deleteDomain(args) {
|
|
|
192
212
|
}
|
|
193
213
|
const projectId = resolveProjectId(project);
|
|
194
214
|
try {
|
|
195
|
-
await getSdk().domains.remove(domain, { projectId });
|
|
215
|
+
await getSdk().domains.remove(domain, { projectId, authMode });
|
|
196
216
|
console.log(JSON.stringify({ domain, project_id: projectId, released: true }));
|
|
197
217
|
} catch (err) {
|
|
198
218
|
reportSdkError(err);
|
package/lib/next-actions.mjs
CHANGED
|
@@ -46,6 +46,13 @@ export function createProjectAction() {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export function selectProjectAction() {
|
|
50
|
+
return nextAction("edit_request", {
|
|
51
|
+
command: "run402 projects use <project_id>",
|
|
52
|
+
why: "Select a server-visible project, or pass --project <project_id> on the command.",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
export function setTierAction(tier = "prototype") {
|
|
50
57
|
return nextAction("renew_tier", {
|
|
51
58
|
command: `run402 tier set ${tier}`,
|
package/lib/projects.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import {
|
|
2
|
+
import { allowanceAuthHeaders, resolveProjectId, getActiveProjectId, isCoreApiTarget } from "./config.mjs";
|
|
3
3
|
import { loadLiveOperatorSession } from "../core-dist/operator-session.js";
|
|
4
4
|
import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
|
|
5
5
|
import { withAutoApprove } from "./operator.mjs";
|
|
@@ -19,8 +19,7 @@ Subcommands:
|
|
|
19
19
|
list [--org <id>] [--all] List your projects from the server (name, site_url, custom domains, org_id, active marker)
|
|
20
20
|
rename <id> --name <label> Rename a project (fix an auto-generated name)
|
|
21
21
|
get [id] Authoritative server read: status, org, tier, active deploy, mailbox, usage vs limits (live; no keys)
|
|
22
|
-
|
|
23
|
-
keys [id] Print anon_key and service_key as JSON (local keystore only)
|
|
22
|
+
current Show the active project pointer and validation status
|
|
24
23
|
sql [id] "<query>" [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
|
|
25
24
|
rest [id] <table> [params] Query a table via the REST API (PostgREST)
|
|
26
25
|
usage [id] Show compute/storage usage for a project
|
|
@@ -45,7 +44,7 @@ Examples:
|
|
|
45
44
|
run402 projects list --all
|
|
46
45
|
run402 projects rename prj_abc123 --name "My Site"
|
|
47
46
|
run402 projects get prj_abc123
|
|
48
|
-
run402 projects
|
|
47
|
+
run402 projects current
|
|
49
48
|
run402 projects sql prj_abc123 "SELECT * FROM users LIMIT 5"
|
|
50
49
|
run402 projects sql prj_abc123 "SELECT * FROM users WHERE id = $1" --params '[42]'
|
|
51
50
|
run402 projects sql prj_abc123 --file setup.sql
|
|
@@ -56,7 +55,6 @@ Examples:
|
|
|
56
55
|
run402 projects validate-expose prj_abc123 --file manifest.json
|
|
57
56
|
run402 projects apply-expose prj_abc123 --file manifest.json
|
|
58
57
|
run402 projects get-expose prj_abc123
|
|
59
|
-
run402 projects keys prj_abc123
|
|
60
58
|
run402 projects delete prj_abc123 --confirm
|
|
61
59
|
|
|
62
60
|
Global options (any command):
|
|
@@ -69,7 +67,9 @@ Notes:
|
|
|
69
67
|
- Most commands that take <id> default to the active project when omitted
|
|
70
68
|
(set it with 'run402 projects use <id>'). Project IDs start with 'prj_';
|
|
71
69
|
any first positional that doesn't is treated as the next argument instead.
|
|
72
|
-
- 'list'
|
|
70
|
+
- 'list', 'get', and 'use' are SERVER-authoritative project identity flows.
|
|
71
|
+
Local project keys live under 'run402 credentials project-keys ...'.
|
|
72
|
+
- 'list' is a SERVER read, not the local key cache: it shows every project the
|
|
73
73
|
active wallet can reach (membership-scoped), with name, site_url, custom
|
|
74
74
|
domains, and org_id. '--org <id>' filters to one org; '--all' reads the
|
|
75
75
|
cross-wallet inventory for every wallet controlling your operator email
|
|
@@ -129,7 +129,7 @@ Examples:
|
|
|
129
129
|
run402 projects list --all
|
|
130
130
|
run402 projects list --wallet work
|
|
131
131
|
`,
|
|
132
|
-
|
|
132
|
+
rename: `run402 projects rename — Rename a project
|
|
133
133
|
|
|
134
134
|
Usage:
|
|
135
135
|
run402 projects rename <id> --name <label>
|
|
@@ -147,6 +147,16 @@ Notes:
|
|
|
147
147
|
|
|
148
148
|
Examples:
|
|
149
149
|
run402 projects rename prj_abc123 --name "My Site"
|
|
150
|
+
`,
|
|
151
|
+
current: `run402 projects current — Inspect active project selection
|
|
152
|
+
|
|
153
|
+
Usage:
|
|
154
|
+
run402 projects current
|
|
155
|
+
|
|
156
|
+
Notes:
|
|
157
|
+
- The active project pointer is local non-secret profile state, not proof that
|
|
158
|
+
local project keys exist.
|
|
159
|
+
- The command attempts an authoritative server read and reports validation.
|
|
150
160
|
`,
|
|
151
161
|
provision: `run402 projects provision — Provision a new Postgres project
|
|
152
162
|
|
|
@@ -541,28 +551,55 @@ async function get(projectId) {
|
|
|
541
551
|
}
|
|
542
552
|
}
|
|
543
553
|
|
|
544
|
-
async function
|
|
554
|
+
async function current() {
|
|
555
|
+
const projectId = getActiveProjectId();
|
|
556
|
+
const out = {
|
|
557
|
+
active_project_id: projectId ?? null,
|
|
558
|
+
source: "profile_state",
|
|
559
|
+
validation: { checked: false, ok: false },
|
|
560
|
+
};
|
|
561
|
+
if (!projectId) {
|
|
562
|
+
console.log(JSON.stringify(out, null, 2));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
545
565
|
try {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
rest_url: `${API}/rest/v1`,
|
|
550
|
-
anon_key: data.anon_key,
|
|
551
|
-
service_key: data.service_key,
|
|
552
|
-
site_url: data.site_url || null,
|
|
553
|
-
}, null, 2));
|
|
566
|
+
const detail = await getSdk().projects.get(projectId);
|
|
567
|
+
out.validation = { checked: true, ok: true };
|
|
568
|
+
out.project = detail;
|
|
554
569
|
} catch (err) {
|
|
555
|
-
|
|
556
|
-
|
|
570
|
+
out.validation = {
|
|
571
|
+
checked: true,
|
|
572
|
+
ok: false,
|
|
573
|
+
code: err?.code ?? null,
|
|
574
|
+
message: err?.message ?? String(err),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
console.log(JSON.stringify(out, null, 2));
|
|
557
578
|
}
|
|
558
579
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
580
|
+
function commandMoved(name) {
|
|
581
|
+
fail({
|
|
582
|
+
code: "COMMAND_MOVED",
|
|
583
|
+
message: `run402 projects ${name} moved to the explicit local credential-cache surface.`,
|
|
584
|
+
hint: "Use `run402 credentials project-keys status/export --project <id>`.",
|
|
585
|
+
details: {
|
|
586
|
+
old_command: `projects ${name}`,
|
|
587
|
+
new_command: name === "keys" ? "credentials project-keys export --reveal" : "credentials project-keys status",
|
|
588
|
+
source: "local_cache",
|
|
589
|
+
},
|
|
590
|
+
next_actions: [
|
|
591
|
+
{
|
|
592
|
+
type: "run_command",
|
|
593
|
+
command: "run402 credentials project-keys status --project <id>",
|
|
594
|
+
why: "Inspect cached key presence without revealing secrets.",
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
type: "run_command",
|
|
598
|
+
command: "run402 credentials project-keys export --project <id> --reveal",
|
|
599
|
+
why: "Reveal cached project keys only when you explicitly need secret material.",
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
});
|
|
566
603
|
}
|
|
567
604
|
|
|
568
605
|
async function sqlCmd(projectId, args = []) {
|
|
@@ -794,10 +831,11 @@ export async function run(sub, args) {
|
|
|
794
831
|
case "provision": await provision(args); break;
|
|
795
832
|
case "use": await use(args[0]); break;
|
|
796
833
|
case "list": await list(args); break;
|
|
834
|
+
case "current": await current(); break;
|
|
797
835
|
case "rename": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.rename.values }); await rename(projectId, rest); break; }
|
|
798
836
|
case "get": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await get(projectId); break; }
|
|
799
|
-
case "info":
|
|
800
|
-
case "keys":
|
|
837
|
+
case "info": commandMoved("info"); break;
|
|
838
|
+
case "keys": commandMoved("keys"); break;
|
|
801
839
|
case "sql": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB.sql.values, rejectBareFirstWhenFlagPresent: ["--file"] }); await sqlCmd(projectId, rest); break; }
|
|
802
840
|
case "rest": { const { projectId, rest: restArgs } = resolvePositionalProject(args); await rest(projectId, restArgs[0], restArgs[1]); break; }
|
|
803
841
|
case "usage": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await usage(projectId); break; }
|
package/lib/sdk-errors.mjs
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Maps SDK `Run402Error` subclasses into the CLI's canonical error envelope:
|
|
5
5
|
* `{status: "error", code, message, retryable, safe_to_retry, ...}` on stderr,
|
|
6
6
|
* `process.exit(1)`. Preserves specific behaviors:
|
|
7
|
-
* - `
|
|
8
|
-
*
|
|
9
|
-
* local-registry miss from a gateway 404.
|
|
7
|
+
* - `ProjectCredentialNotFound` → preserves
|
|
8
|
+
* `code: "PROJECT_CREDENTIAL_NOT_FOUND"` and local-cache provenance.
|
|
10
9
|
* - HTML / non-JSON error bodies → `body_preview` field (first 500 chars),
|
|
11
10
|
* matching GH-84 behavior.
|
|
12
11
|
* - Network errors → `{status: "error", message: "..."}`.
|
|
@@ -66,6 +65,18 @@ export function parseFlagJson(name, value) {
|
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
export function reportSdkError(err) {
|
|
68
|
+
if (err?.name === "ProjectCredentialNotFound" || err?.code === "PROJECT_CREDENTIAL_NOT_FOUND") {
|
|
69
|
+
fail({
|
|
70
|
+
code: "PROJECT_CREDENTIAL_NOT_FOUND",
|
|
71
|
+
message: err?.message || "Local project credentials are not cached.",
|
|
72
|
+
hint: "This is a local credential-cache miss, not proof that the project does not exist. Use a principal-auth command, or import keys with `run402 credentials project-keys import --project <id> --service-key-stdin` for credential-required operations.",
|
|
73
|
+
details: err?.details,
|
|
74
|
+
next_actions: err?.nextActions,
|
|
75
|
+
retryable: err?.retryable ?? false,
|
|
76
|
+
safe_to_retry: err?.safeToRetry ?? true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
if (err?.name === "ProjectNotFound") {
|
|
70
81
|
const id = err.projectId || "";
|
|
71
82
|
const hint = id && !String(id).startsWith("prj_")
|
|
@@ -117,6 +128,16 @@ export function reportSdkError(err) {
|
|
|
117
128
|
"Returned as 403 even when the project does not exist, so verify the project id too.";
|
|
118
129
|
}
|
|
119
130
|
|
|
131
|
+
if (typeof payload.code === "string" && payload.code.startsWith("PROJECT_CREDENTIAL_") && payload.hint === undefined) {
|
|
132
|
+
if (payload.code === "PROJECT_CREDENTIAL_PROJECT_MISMATCH") {
|
|
133
|
+
payload.hint =
|
|
134
|
+
"The supplied service key belongs to a different project than the explicit project id. Select the matching project key, or omit service-key mode and use the default principal-auth path when the command supports it.";
|
|
135
|
+
} else if (payload.code === "PROJECT_CREDENTIAL_INVALID" || payload.code === "PROJECT_CREDENTIAL_EXPIRED") {
|
|
136
|
+
payload.hint =
|
|
137
|
+
"The project credential was present but rejected by the server. Re-import or rotate the project key, or use a principal-auth command when available.";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
120
141
|
// Keep `status: "error"` as the outer envelope even if the response body
|
|
121
142
|
// happened to contain its own `status` field (e.g. `{"status":"degraded"}`
|
|
122
143
|
// from /health 503 responses). Downstream scripts match on this sentinel.
|