run402 3.5.4 → 3.5.6
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/core-dist/config.js +103 -4
- package/lib/config.mjs +27 -2
- package/lib/deploy-v2.mjs +30 -7
- package/lib/init.mjs +106 -1
- package/lib/projects.mjs +13 -6
- package/lib/status.mjs +25 -3
- package/package.json +1 -1
- package/sdk/core-dist/config.js +103 -4
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +281 -21
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +16 -1
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +1 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +7 -0
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +5 -3
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +16 -2
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +2 -0
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +1 -0
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/target-profile.d.ts +40 -0
- package/sdk/dist/node/target-profile.d.ts.map +1 -0
- package/sdk/dist/node/target-profile.js +98 -0
- package/sdk/dist/node/target-profile.js.map +1 -0
package/core-dist/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { existsSync, renameSync, mkdirSync, chmodSync } from "node:fs";
|
|
4
|
-
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
export const DEFAULT_API_BASE = "https://api.run402.com";
|
|
5
6
|
/**
|
|
6
7
|
* Validate a user-supplied API base URL. Throws a clear error message that
|
|
7
8
|
* names the env var when the URL is malformed or uses a scheme other than
|
|
@@ -33,7 +34,12 @@ function validateApiBase(envVar, raw, fallback) {
|
|
|
33
34
|
}
|
|
34
35
|
export function getApiBase() {
|
|
35
36
|
const validated = validateApiBase("RUN402_API_BASE", process.env.RUN402_API_BASE, DEFAULT_API_BASE);
|
|
36
|
-
return validated ?? DEFAULT_API_BASE;
|
|
37
|
+
return validated ?? getConfiguredApiBase() ?? DEFAULT_API_BASE;
|
|
38
|
+
}
|
|
39
|
+
export function getApiBaseSource() {
|
|
40
|
+
if (process.env.RUN402_API_BASE !== undefined)
|
|
41
|
+
return "env";
|
|
42
|
+
return getConfiguredApiBase() ? "profile" : "default";
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* API base for the deploy-v2 routes. Defaults to the same value as
|
|
@@ -115,6 +121,99 @@ export function getConfigDir() {
|
|
|
115
121
|
export function getKeystorePath() {
|
|
116
122
|
return join(getConfigDir(), "projects.json");
|
|
117
123
|
}
|
|
124
|
+
export function getApiTargetConfigPath() {
|
|
125
|
+
return join(getConfigDir(), "target.json");
|
|
126
|
+
}
|
|
127
|
+
export function readApiTargetConfig(path) {
|
|
128
|
+
const p = path ?? getApiTargetConfigPath();
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
131
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
132
|
+
return null;
|
|
133
|
+
const cfg = parsed;
|
|
134
|
+
if (cfg.api_base !== undefined && typeof cfg.api_base !== "string")
|
|
135
|
+
return null;
|
|
136
|
+
if (cfg.target_kind !== undefined &&
|
|
137
|
+
cfg.target_kind !== "cloud" &&
|
|
138
|
+
cfg.target_kind !== "core" &&
|
|
139
|
+
cfg.target_kind !== "unknown") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return cfg;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function atomicWrite(path, content, mode) {
|
|
149
|
+
const dir = dirname(path);
|
|
150
|
+
mkdirSync(dir, { recursive: true });
|
|
151
|
+
const tmp = join(dir, `.target.${randomBytes(4).toString("hex")}.tmp`);
|
|
152
|
+
writeFileSync(tmp, content, { mode });
|
|
153
|
+
renameSync(tmp, path);
|
|
154
|
+
try {
|
|
155
|
+
chmodSync(path, mode);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* best-effort on non-POSIX */
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function saveApiTargetConfig(config, path) {
|
|
162
|
+
const p = path ?? getApiTargetConfigPath();
|
|
163
|
+
atomicWrite(p, JSON.stringify(config, null, 2), 0o600);
|
|
164
|
+
}
|
|
165
|
+
export function configureApiBase(apiBase, options = {}) {
|
|
166
|
+
if (apiBase === "") {
|
|
167
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
168
|
+
}
|
|
169
|
+
const validated = validateApiBase("api_base", apiBase, DEFAULT_API_BASE);
|
|
170
|
+
if (!validated) {
|
|
171
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
172
|
+
}
|
|
173
|
+
const config = {
|
|
174
|
+
api_base: validated.replace(/\/+$/, ""),
|
|
175
|
+
target_kind: options.target_kind ?? "unknown",
|
|
176
|
+
updated_at: options.updated_at ?? new Date().toISOString(),
|
|
177
|
+
...(options.health_status ? { health_status: options.health_status } : {}),
|
|
178
|
+
...(options.health_error ? { health_error: options.health_error } : {}),
|
|
179
|
+
};
|
|
180
|
+
saveApiTargetConfig(config);
|
|
181
|
+
return config;
|
|
182
|
+
}
|
|
183
|
+
export function getConfiguredApiBase() {
|
|
184
|
+
const cfg = readApiTargetConfig();
|
|
185
|
+
if (!cfg?.api_base)
|
|
186
|
+
return null;
|
|
187
|
+
const validated = validateApiBase("api_base", cfg.api_base, DEFAULT_API_BASE);
|
|
188
|
+
return validated ? validated.replace(/\/+$/, "") : null;
|
|
189
|
+
}
|
|
190
|
+
export function getApiTargetKind() {
|
|
191
|
+
const cfg = readApiTargetConfig();
|
|
192
|
+
return cfg?.target_kind ?? "unknown";
|
|
193
|
+
}
|
|
194
|
+
function stripTrailingSlashes(value) {
|
|
195
|
+
return value.replace(/\/+$/, "");
|
|
196
|
+
}
|
|
197
|
+
function isExplicitHttpCoreTarget(apiBase) {
|
|
198
|
+
if (stripTrailingSlashes(apiBase) === stripTrailingSlashes(DEFAULT_API_BASE))
|
|
199
|
+
return false;
|
|
200
|
+
try {
|
|
201
|
+
return new URL(apiBase).protocol === "http:";
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function isCoreApiTarget() {
|
|
208
|
+
const apiBase = getApiBase();
|
|
209
|
+
const cfg = readApiTargetConfig();
|
|
210
|
+
if (cfg?.target_kind === "core" && cfg.api_base) {
|
|
211
|
+
return stripTrailingSlashes(apiBase) === stripTrailingSlashes(cfg.api_base);
|
|
212
|
+
}
|
|
213
|
+
if (process.env.RUN402_API_BASE !== undefined && isExplicitHttpCoreTarget(apiBase))
|
|
214
|
+
return true;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
118
217
|
export function getAllowancePath() {
|
|
119
218
|
if (process.env.RUN402_ALLOWANCE_PATH)
|
|
120
219
|
return process.env.RUN402_ALLOWANCE_PATH;
|
package/lib/config.mjs
CHANGED
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
* Adds CLI-specific behavior: process.exit() on errors.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getApiBase,
|
|
8
|
+
getApiBaseSource,
|
|
9
|
+
getApiTargetKind,
|
|
10
|
+
getConfigDir,
|
|
11
|
+
getKeystorePath,
|
|
12
|
+
getAllowancePath,
|
|
13
|
+
configureApiBase,
|
|
14
|
+
isCoreApiTarget,
|
|
15
|
+
readApiTargetConfig,
|
|
16
|
+
} from "../core-dist/config.js";
|
|
7
17
|
import { readAllowance as coreReadAllowance, saveAllowance as coreSaveAllowance } from "../core-dist/allowance.js";
|
|
8
18
|
import { loadKeyStore, getProject, saveProject, updateProject, removeProject, saveKeyStore, getActiveProjectId, setActiveProjectId } from "../core-dist/keystore.js";
|
|
9
19
|
import { getAllowanceAuthHeaders as coreGetAllowanceAuthHeaders } from "../core-dist/allowance-auth.js";
|
|
@@ -16,6 +26,10 @@ import { initializeWalletAction, createProjectAction } from "./next-actions.mjs"
|
|
|
16
26
|
export function configDir() { return getConfigDir(); }
|
|
17
27
|
export function allowanceFile() { return getAllowancePath(); }
|
|
18
28
|
export function projectsFile() { return getKeystorePath(); }
|
|
29
|
+
export function apiBase() { return getApiBase(); }
|
|
30
|
+
export function apiBaseSource() { return getApiBaseSource(); }
|
|
31
|
+
export function apiTargetKind() { return getApiTargetKind(); }
|
|
32
|
+
export function coreTarget() { return isCoreApiTarget(); }
|
|
19
33
|
|
|
20
34
|
// Snapshot constants, retained for backward compatibility (tests, the OpenClaw
|
|
21
35
|
// config re-export). These are evaluated when this module is first imported.
|
|
@@ -116,4 +130,15 @@ export function resolveProjectId(id) {
|
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// Re-export core keystore functions for direct use
|
|
119
|
-
export {
|
|
133
|
+
export {
|
|
134
|
+
configureApiBase,
|
|
135
|
+
isCoreApiTarget,
|
|
136
|
+
readApiTargetConfig,
|
|
137
|
+
loadKeyStore,
|
|
138
|
+
saveProject,
|
|
139
|
+
updateProject,
|
|
140
|
+
removeProject,
|
|
141
|
+
saveKeyStore,
|
|
142
|
+
getActiveProjectId,
|
|
143
|
+
setActiveProjectId,
|
|
144
|
+
};
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "#sdk/node";
|
|
33
33
|
import { getSdk } from "./sdk.mjs";
|
|
34
34
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
35
|
-
import { API, allowanceAuthHeaders, getActiveProjectId, resolveProjectId } from "./config.mjs";
|
|
35
|
+
import { API, allowanceAuthHeaders, getActiveProjectId, resolveProjectId, isCoreApiTarget } from "./config.mjs";
|
|
36
36
|
import { normalizeArgv } from "./argparse.mjs";
|
|
37
37
|
import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
|
|
38
38
|
import { withAutoApprove } from "./operator.mjs";
|
|
@@ -124,6 +124,11 @@ Routes:
|
|
|
124
124
|
Routes activate atomically with the release. Direct /functions/v1/:name remains API-key protected.
|
|
125
125
|
Runtime route failure codes: ROUTE_MANIFEST_LOAD_FAILED, ROUTED_INVOKE_WORKER_SECRET_MISSING, ROUTED_INVOKE_AUTH_FAILED, ROUTED_ROUTE_STALE, ROUTE_METHOD_NOT_ALLOWED, ROUTED_RESPONSE_TOO_LARGE.
|
|
126
126
|
|
|
127
|
+
Function capabilities:
|
|
128
|
+
functions.replace.<name>.capabilities is an array of runtime capability strings.
|
|
129
|
+
Framework adapters use it for contracts such as "astro.ssr.v1"; omit it for
|
|
130
|
+
ordinary user-authored functions unless a documented helper requires it.
|
|
131
|
+
|
|
127
132
|
Internationalization (routed functions):
|
|
128
133
|
"i18n": { "defaultLocale": "en", "locales": ["en", "es", "fr"], "detect": ["cookie:wl_locale", "accept-language"] }
|
|
129
134
|
Omit i18n to carry forward from base release; pass "i18n": null to clear the slice on the new release.
|
|
@@ -664,7 +669,16 @@ async function mergeAstroReleaseSlice(spec, dirArg) {
|
|
|
664
669
|
* the manifest normalizer is the authority on shape validity, not this
|
|
665
670
|
* best-effort extractor.
|
|
666
671
|
*/
|
|
667
|
-
function
|
|
672
|
+
function isAstroSsrManifestFunction(entry) {
|
|
673
|
+
return (
|
|
674
|
+
entry &&
|
|
675
|
+
typeof entry === "object" &&
|
|
676
|
+
Array.isArray(entry.capabilities) &&
|
|
677
|
+
entry.capabilities.includes("astro.ssr.v1")
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export function collectManifestSourceFiles(spec, baseDir) {
|
|
668
682
|
const out = new Set();
|
|
669
683
|
if (!spec || typeof spec !== "object") return [];
|
|
670
684
|
|
|
@@ -689,13 +703,20 @@ function collectManifestSourceFiles(spec, baseDir) {
|
|
|
689
703
|
if (!map || typeof map !== "object") return;
|
|
690
704
|
for (const entry of Object.values(map)) resolveEntryPath(entry);
|
|
691
705
|
};
|
|
706
|
+
const eachFunctionValue = (map) => {
|
|
707
|
+
if (!map || typeof map !== "object") return;
|
|
708
|
+
for (const entry of Object.values(map)) {
|
|
709
|
+
if (isAstroSsrManifestFunction(entry)) continue;
|
|
710
|
+
resolveEntryPath(entry);
|
|
711
|
+
}
|
|
712
|
+
};
|
|
692
713
|
|
|
693
714
|
const fns = spec.functions;
|
|
694
715
|
if (fns && typeof fns === "object") {
|
|
695
|
-
|
|
716
|
+
eachFunctionValue(fns.replace);
|
|
696
717
|
if (fns.patch && typeof fns.patch === "object") {
|
|
697
|
-
|
|
698
|
-
|
|
718
|
+
eachFunctionValue(fns.patch.put);
|
|
719
|
+
eachFunctionValue(fns.patch.set);
|
|
699
720
|
}
|
|
700
721
|
}
|
|
701
722
|
|
|
@@ -896,9 +917,10 @@ async function applyCmd(args) {
|
|
|
896
917
|
credentials: githubActionsCredentials({ projectId: releaseSpec.project, apiBase: API }),
|
|
897
918
|
disablePaidFetch: true,
|
|
898
919
|
};
|
|
899
|
-
} else if (!loadLiveControlPlaneSession()) {
|
|
920
|
+
} else if (!isCoreApiTarget() && !loadLiveControlPlaneSession()) {
|
|
900
921
|
// Aggressive early exit when no allowance is configured — unless a
|
|
901
|
-
// wallet-less human is deploying via their operator (control-plane) session
|
|
922
|
+
// wallet-less human is deploying via their operator (control-plane) session
|
|
923
|
+
// or the active target is a self-hosted Core Gateway.
|
|
902
924
|
allowanceAuthHeaders("/apply/v1/plans");
|
|
903
925
|
}
|
|
904
926
|
|
|
@@ -909,6 +931,7 @@ async function applyCmd(args) {
|
|
|
909
931
|
idempotencyKey,
|
|
910
932
|
allowWarnings: opts.allowWarnings,
|
|
911
933
|
allowWarningCodes: opts.allowWarningCodes,
|
|
934
|
+
target: isCoreApiTarget() ? "core" : "cloud",
|
|
912
935
|
}),
|
|
913
936
|
);
|
|
914
937
|
console.log(JSON.stringify(result, null, 2));
|
package/lib/init.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readAllowance, saveAllowance, loadKeyStore, configDir } from "./config.mjs";
|
|
1
|
+
import { readAllowance, saveAllowance, loadKeyStore, configDir, configureApiBase } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { fail } from "./sdk-errors.mjs";
|
|
4
4
|
import { setTierAction, deployAction } from "./next-actions.mjs";
|
|
@@ -15,6 +15,9 @@ const HELP = `run402 init — Set up allowance, funding, and check tier status
|
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
17
|
run402 init Set up with x402 (Base Sepolia) — default
|
|
18
|
+
run402 init --api-base <url>
|
|
19
|
+
Configure a Run402 Core/API target for the active
|
|
20
|
+
profile without setting up Cloud payment.
|
|
18
21
|
run402 init mpp Set up with MPP (Tempo Moderato)
|
|
19
22
|
run402 init <rail> --switch-rail
|
|
20
23
|
Switch the persisted payment rail to <rail>.
|
|
@@ -23,6 +26,9 @@ Usage:
|
|
|
23
26
|
silently flipping billing networks.
|
|
24
27
|
|
|
25
28
|
Options:
|
|
29
|
+
--api-base <url> Configure the active profile to use this API base. Use this
|
|
30
|
+
for a self-hosted Run402 Core Gateway, e.g.
|
|
31
|
+
http://my-core:4020.
|
|
26
32
|
--switch-rail Confirm switching the persisted payment rail. Re-running
|
|
27
33
|
init with the SAME rail as the existing allowance is always
|
|
28
34
|
idempotent and does not need this flag.
|
|
@@ -46,6 +52,61 @@ Run this once to get started, or again to check your setup.
|
|
|
46
52
|
|
|
47
53
|
function short(addr) { return addr.slice(0, 6) + "..." + addr.slice(-4); }
|
|
48
54
|
|
|
55
|
+
function parseApiBaseFlag(args) {
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
if (arg === "--api-base") {
|
|
59
|
+
const value = args[i + 1];
|
|
60
|
+
if (value === undefined || String(value).startsWith("--")) {
|
|
61
|
+
fail({
|
|
62
|
+
code: "BAD_USAGE",
|
|
63
|
+
message: "--api-base requires a value.",
|
|
64
|
+
details: { flag: "--api-base" },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { value, args: [...args.slice(0, i), ...args.slice(i + 2)] };
|
|
68
|
+
}
|
|
69
|
+
if (typeof arg === "string" && arg.startsWith("--api-base=")) {
|
|
70
|
+
const value = arg.slice("--api-base=".length);
|
|
71
|
+
if (!value) {
|
|
72
|
+
fail({
|
|
73
|
+
code: "BAD_USAGE",
|
|
74
|
+
message: "--api-base requires a non-empty value.",
|
|
75
|
+
details: { flag: "--api-base" },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return { value, args: [...args.slice(0, i), ...args.slice(i + 1)] };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { value: null, args };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sameOrigin(a, b) {
|
|
85
|
+
try {
|
|
86
|
+
return new URL(a).origin === new URL(b).origin;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function detectTarget(apiBase) {
|
|
93
|
+
try {
|
|
94
|
+
const health = await getSdk({ apiBase, disablePaidFetch: true, authMode: "none" }).service.health();
|
|
95
|
+
const kind = health && typeof health === "object" && health.mode === "core"
|
|
96
|
+
? "core"
|
|
97
|
+
: sameOrigin(apiBase, "https://api.run402.com") ? "cloud" : "core";
|
|
98
|
+
return {
|
|
99
|
+
kind,
|
|
100
|
+
health_status: typeof health?.status === "string" ? health.status : "ok",
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
kind: sameOrigin(apiBase, "https://api.run402.com") ? "cloud" : "core",
|
|
105
|
+
health_error: errorMessage(err),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
49
110
|
function errorMessage(err) {
|
|
50
111
|
if (err?.body && typeof err.body === "object") return err.body.message || err.body.error || err.message;
|
|
51
112
|
return err?.message || String(err);
|
|
@@ -67,6 +128,50 @@ export async function run(args = []) {
|
|
|
67
128
|
|
|
68
129
|
if (args.includes("--help") || args.includes("-h")) { console.log(HELP); process.exit(0); }
|
|
69
130
|
|
|
131
|
+
const parsedApiBase = parseApiBaseFlag(args);
|
|
132
|
+
if (parsedApiBase.value) {
|
|
133
|
+
if (parsedApiBase.args.some((arg) => typeof arg === "string" && !arg.startsWith("--"))) {
|
|
134
|
+
fail({
|
|
135
|
+
code: "BAD_USAGE",
|
|
136
|
+
message: "run402 init --api-base cannot be combined with a payment rail.",
|
|
137
|
+
hint: "Run `run402 init --api-base=http://my-core:4020` for Core, or `run402 init` for Run402 Cloud.",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const CONFIG_DIR = configDir();
|
|
141
|
+
const detected = await detectTarget(parsedApiBase.value);
|
|
142
|
+
const config = configureApiBase(parsedApiBase.value, {
|
|
143
|
+
target_kind: detected.kind,
|
|
144
|
+
...(detected.health_status ? { health_status: detected.health_status } : {}),
|
|
145
|
+
...(detected.health_error ? { health_error: detected.health_error } : {}),
|
|
146
|
+
});
|
|
147
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
148
|
+
console.error("");
|
|
149
|
+
console.error(` ${"Config".padEnd(10)} ${CONFIG_DIR}`);
|
|
150
|
+
console.error(` ${"API base".padEnd(10)} ${config.api_base}`);
|
|
151
|
+
console.error(` ${"Target".padEnd(10)} ${config.target_kind}`);
|
|
152
|
+
if (detected.health_status) console.error(` ${"Health".padEnd(10)} ${detected.health_status}`);
|
|
153
|
+
if (detected.health_error) console.error(` ${"Health".padEnd(10)} ${detected.health_error}`);
|
|
154
|
+
console.error("");
|
|
155
|
+
const summary = {
|
|
156
|
+
config_dir: CONFIG_DIR,
|
|
157
|
+
api_base: config.api_base,
|
|
158
|
+
api_base_source: "profile",
|
|
159
|
+
target: {
|
|
160
|
+
kind: config.target_kind,
|
|
161
|
+
...(config.health_status ? { health_status: config.health_status } : {}),
|
|
162
|
+
...(config.health_error ? { health_error: config.health_error } : {}),
|
|
163
|
+
},
|
|
164
|
+
payment_required: config.target_kind === "cloud",
|
|
165
|
+
next_actions: [{
|
|
166
|
+
type: "create_project",
|
|
167
|
+
command: 'run402 projects provision --name "my-app"',
|
|
168
|
+
}],
|
|
169
|
+
next_step: 'run402 projects provision --name "my-app"',
|
|
170
|
+
};
|
|
171
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
70
175
|
// Resolve once for this invocation — reflects the active wallet/profile that
|
|
71
176
|
// cli.mjs published to RUN402_WALLET before this module loaded.
|
|
72
177
|
const CONFIG_DIR = configDir();
|
package/lib/projects.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { loadKeyStore, API, allowanceAuthHeaders, resolveProjectId, getActiveProjectId } from "./config.mjs";
|
|
2
|
+
import { loadKeyStore, API, 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";
|
|
@@ -14,7 +14,7 @@ Usage:
|
|
|
14
14
|
|
|
15
15
|
Subcommands:
|
|
16
16
|
quote Show pricing tiers
|
|
17
|
-
provision [--tier <tier>] [--name <n>] [--org <id>] Provision a new Postgres project
|
|
17
|
+
provision [--tier <tier>] [--name <n>] [--org <id>] Provision a new Postgres project
|
|
18
18
|
use <id> Set the active project (used as default for other commands)
|
|
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)
|
|
@@ -79,7 +79,9 @@ Notes:
|
|
|
79
79
|
project:write grant) on the owning org; it works even if the project was
|
|
80
80
|
never provisioned from this machine.
|
|
81
81
|
- 'rest' uses PostgREST query syntax (table name + optional query string)
|
|
82
|
-
- 'provision' requires a funded allowance
|
|
82
|
+
- 'provision' requires a funded allowance on Run402 Cloud. Against a
|
|
83
|
+
configured Run402 Core target, it creates a local Core project without
|
|
84
|
+
payment.
|
|
83
85
|
- 'apply-expose' declares the full authorization surface (tables, views, RPCs)
|
|
84
86
|
in one convergent call. Tables not listed with expose:true are dark by
|
|
85
87
|
default. Schema: https://run402.com/schemas/manifest.v1.json. Sample:
|
|
@@ -161,7 +163,9 @@ Options:
|
|
|
161
163
|
from --name when omitted; an unnamed provision stays un-keyed.
|
|
162
164
|
|
|
163
165
|
Notes:
|
|
164
|
-
- Payment is automatic via x402; requires a funded allowance
|
|
166
|
+
- Payment is automatic via x402 on Run402 Cloud; requires a funded allowance.
|
|
167
|
+
Against a configured Run402 Core target, no Cloud tier/allowance/payment is
|
|
168
|
+
required.
|
|
165
169
|
- The new project becomes the active project after provisioning
|
|
166
170
|
|
|
167
171
|
Examples:
|
|
@@ -320,7 +324,7 @@ async function provision(args) {
|
|
|
320
324
|
// Aggressive early exit when no agent allowance is configured — but only when
|
|
321
325
|
// there's also no operator (control-plane) session, since a wallet-less human
|
|
322
326
|
// provisions into an org via their operator approval instead of a wallet.
|
|
323
|
-
if (!loadLiveControlPlaneSession()) allowanceAuthHeaders("/projects/v1");
|
|
327
|
+
if (!isCoreApiTarget() && !loadLiveControlPlaneSession()) allowanceAuthHeaders("/projects/v1");
|
|
324
328
|
|
|
325
329
|
const activeBefore = getActiveProjectId();
|
|
326
330
|
try {
|
|
@@ -744,7 +748,10 @@ async function deleteProject(projectId, args = []) {
|
|
|
744
748
|
}
|
|
745
749
|
|
|
746
750
|
const FLAGS_BY_SUB = {
|
|
747
|
-
provision: {
|
|
751
|
+
provision: {
|
|
752
|
+
known: ["--tier", "--name", "--org", "--idempotency-key"],
|
|
753
|
+
values: ["--tier", "--name", "--org", "--idempotency-key"],
|
|
754
|
+
},
|
|
748
755
|
list: { known: ["--org", "--all"], values: ["--org"] },
|
|
749
756
|
rename: { known: ["--name"], values: ["--name"] },
|
|
750
757
|
sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
|
package/lib/status.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
readAllowance,
|
|
3
|
+
loadKeyStore,
|
|
4
|
+
getActiveProjectId,
|
|
5
|
+
apiBase,
|
|
6
|
+
apiBaseSource,
|
|
7
|
+
apiTargetKind,
|
|
8
|
+
} from "./config.mjs";
|
|
2
9
|
import { getSdk } from "./sdk.mjs";
|
|
3
10
|
import { assertKnownFlags, hasHelp, normalizeArgv } from "./argparse.mjs";
|
|
4
11
|
import { getActiveProfile } from "../core-dist/config.js";
|
|
@@ -16,8 +23,10 @@ Displays:
|
|
|
16
23
|
- Tier subscription (name, status, expiry)
|
|
17
24
|
- Projects (from server, with fallback to local keystore)
|
|
18
25
|
- Active project ID
|
|
26
|
+
- Active API target
|
|
19
27
|
|
|
20
|
-
Output is JSON.
|
|
28
|
+
Output is JSON. Run402 Cloud status requires an allowance; Core target status
|
|
29
|
+
can still report local project state without one.
|
|
21
30
|
`;
|
|
22
31
|
|
|
23
32
|
// USDC / pathUSD constants (match allowance.mjs)
|
|
@@ -82,8 +91,20 @@ export async function run(args = []) {
|
|
|
82
91
|
if (hasHelp(args)) { console.log(HELP); process.exit(0); }
|
|
83
92
|
assertKnownFlags(args, ["--help", "-h"]);
|
|
84
93
|
const allowance = readAllowance();
|
|
94
|
+
const target = {
|
|
95
|
+
api_base: apiBase(),
|
|
96
|
+
api_base_source: apiBaseSource(),
|
|
97
|
+
kind: apiTargetKind(),
|
|
98
|
+
};
|
|
85
99
|
if (!allowance) {
|
|
86
|
-
|
|
100
|
+
const store = loadKeyStore();
|
|
101
|
+
console.log(JSON.stringify({
|
|
102
|
+
wallet: null,
|
|
103
|
+
target,
|
|
104
|
+
projects: Object.keys(store.projects).map(id => ({ project_id: id })),
|
|
105
|
+
active_project: getActiveProjectId() || null,
|
|
106
|
+
hint: target.kind === "core" ? "Run: run402 projects provision --name my-app" : "Run: run402 init",
|
|
107
|
+
}));
|
|
87
108
|
return;
|
|
88
109
|
}
|
|
89
110
|
|
|
@@ -145,6 +166,7 @@ export async function run(args = []) {
|
|
|
145
166
|
lease_perpetual: tier?.lease_perpetual ?? null,
|
|
146
167
|
projects,
|
|
147
168
|
active_project: activeId || null,
|
|
169
|
+
target,
|
|
148
170
|
};
|
|
149
171
|
|
|
150
172
|
console.log(JSON.stringify(result, null, 2));
|
package/package.json
CHANGED
package/sdk/core-dist/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { existsSync, renameSync, mkdirSync, chmodSync } from "node:fs";
|
|
4
|
-
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
export const DEFAULT_API_BASE = "https://api.run402.com";
|
|
5
6
|
/**
|
|
6
7
|
* Validate a user-supplied API base URL. Throws a clear error message that
|
|
7
8
|
* names the env var when the URL is malformed or uses a scheme other than
|
|
@@ -33,7 +34,12 @@ function validateApiBase(envVar, raw, fallback) {
|
|
|
33
34
|
}
|
|
34
35
|
export function getApiBase() {
|
|
35
36
|
const validated = validateApiBase("RUN402_API_BASE", process.env.RUN402_API_BASE, DEFAULT_API_BASE);
|
|
36
|
-
return validated ?? DEFAULT_API_BASE;
|
|
37
|
+
return validated ?? getConfiguredApiBase() ?? DEFAULT_API_BASE;
|
|
38
|
+
}
|
|
39
|
+
export function getApiBaseSource() {
|
|
40
|
+
if (process.env.RUN402_API_BASE !== undefined)
|
|
41
|
+
return "env";
|
|
42
|
+
return getConfiguredApiBase() ? "profile" : "default";
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* API base for the deploy-v2 routes. Defaults to the same value as
|
|
@@ -115,6 +121,99 @@ export function getConfigDir() {
|
|
|
115
121
|
export function getKeystorePath() {
|
|
116
122
|
return join(getConfigDir(), "projects.json");
|
|
117
123
|
}
|
|
124
|
+
export function getApiTargetConfigPath() {
|
|
125
|
+
return join(getConfigDir(), "target.json");
|
|
126
|
+
}
|
|
127
|
+
export function readApiTargetConfig(path) {
|
|
128
|
+
const p = path ?? getApiTargetConfigPath();
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
131
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
132
|
+
return null;
|
|
133
|
+
const cfg = parsed;
|
|
134
|
+
if (cfg.api_base !== undefined && typeof cfg.api_base !== "string")
|
|
135
|
+
return null;
|
|
136
|
+
if (cfg.target_kind !== undefined &&
|
|
137
|
+
cfg.target_kind !== "cloud" &&
|
|
138
|
+
cfg.target_kind !== "core" &&
|
|
139
|
+
cfg.target_kind !== "unknown") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return cfg;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function atomicWrite(path, content, mode) {
|
|
149
|
+
const dir = dirname(path);
|
|
150
|
+
mkdirSync(dir, { recursive: true });
|
|
151
|
+
const tmp = join(dir, `.target.${randomBytes(4).toString("hex")}.tmp`);
|
|
152
|
+
writeFileSync(tmp, content, { mode });
|
|
153
|
+
renameSync(tmp, path);
|
|
154
|
+
try {
|
|
155
|
+
chmodSync(path, mode);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* best-effort on non-POSIX */
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function saveApiTargetConfig(config, path) {
|
|
162
|
+
const p = path ?? getApiTargetConfigPath();
|
|
163
|
+
atomicWrite(p, JSON.stringify(config, null, 2), 0o600);
|
|
164
|
+
}
|
|
165
|
+
export function configureApiBase(apiBase, options = {}) {
|
|
166
|
+
if (apiBase === "") {
|
|
167
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
168
|
+
}
|
|
169
|
+
const validated = validateApiBase("api_base", apiBase, DEFAULT_API_BASE);
|
|
170
|
+
if (!validated) {
|
|
171
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
172
|
+
}
|
|
173
|
+
const config = {
|
|
174
|
+
api_base: validated.replace(/\/+$/, ""),
|
|
175
|
+
target_kind: options.target_kind ?? "unknown",
|
|
176
|
+
updated_at: options.updated_at ?? new Date().toISOString(),
|
|
177
|
+
...(options.health_status ? { health_status: options.health_status } : {}),
|
|
178
|
+
...(options.health_error ? { health_error: options.health_error } : {}),
|
|
179
|
+
};
|
|
180
|
+
saveApiTargetConfig(config);
|
|
181
|
+
return config;
|
|
182
|
+
}
|
|
183
|
+
export function getConfiguredApiBase() {
|
|
184
|
+
const cfg = readApiTargetConfig();
|
|
185
|
+
if (!cfg?.api_base)
|
|
186
|
+
return null;
|
|
187
|
+
const validated = validateApiBase("api_base", cfg.api_base, DEFAULT_API_BASE);
|
|
188
|
+
return validated ? validated.replace(/\/+$/, "") : null;
|
|
189
|
+
}
|
|
190
|
+
export function getApiTargetKind() {
|
|
191
|
+
const cfg = readApiTargetConfig();
|
|
192
|
+
return cfg?.target_kind ?? "unknown";
|
|
193
|
+
}
|
|
194
|
+
function stripTrailingSlashes(value) {
|
|
195
|
+
return value.replace(/\/+$/, "");
|
|
196
|
+
}
|
|
197
|
+
function isExplicitHttpCoreTarget(apiBase) {
|
|
198
|
+
if (stripTrailingSlashes(apiBase) === stripTrailingSlashes(DEFAULT_API_BASE))
|
|
199
|
+
return false;
|
|
200
|
+
try {
|
|
201
|
+
return new URL(apiBase).protocol === "http:";
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function isCoreApiTarget() {
|
|
208
|
+
const apiBase = getApiBase();
|
|
209
|
+
const cfg = readApiTargetConfig();
|
|
210
|
+
if (cfg?.target_kind === "core" && cfg.api_base) {
|
|
211
|
+
return stripTrailingSlashes(apiBase) === stripTrailingSlashes(cfg.api_base);
|
|
212
|
+
}
|
|
213
|
+
if (process.env.RUN402_API_BASE !== undefined && isExplicitHttpCoreTarget(apiBase))
|
|
214
|
+
return true;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
118
217
|
export function getAllowancePath() {
|
|
119
218
|
if (process.env.RUN402_ALLOWANCE_PATH)
|
|
120
219
|
return process.env.RUN402_ALLOWANCE_PATH;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAkB3C,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EAQtB,WAAW,EACX,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EAarB,iBAAiB,EAGjB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,YAAY,EAEb,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
|