run402 3.7.14 → 3.8.1
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 +4 -2
- 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/deploy-v2.mjs +11 -0
- package/lib/doctor.mjs +18 -2
- 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/lib/up.mjs +19 -0
- package/lib/up.test.mjs +29 -0
- package/lib/update-check.mjs +722 -0
- package/lib/update-check.test.mjs +438 -0
- 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 +16 -2
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +9 -3
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +9 -0
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +40 -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/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +46 -15
- package/sdk/dist/node/actions-node.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 +11 -2
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +26 -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
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@ For the full CLI reference (every flag, every subcommand) see **<https://docs.ru
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install -g run402
|
|
10
|
+
npm install -g run402@latest
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Or run without installing:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx run402 <command>
|
|
16
|
+
npx -y run402@latest <command>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## 30-second start
|
|
@@ -29,6 +29,8 @@ That's a real Postgres database + a deployed static site, paid for autonomously
|
|
|
29
29
|
|
|
30
30
|
Every command prints **JSON to stdout**, **JSON errors to stderr**, and exits **0 on success / 1 on failure**. Designed for shells, scripts, and agent loops — pipe everything to `jq`.
|
|
31
31
|
|
|
32
|
+
Stale CLI notices are advisory and never pollute success stdout. When a cached npm check says a newer `run402` exists, normal commands may emit a structured `cli.update_available` JSON object on stderr, or an NDJSON event in `--json-stream`. Run `run402 doctor --refresh` for the explicit live check; it reports whether this invocation is local, global, npx/npm exec, pnpm/yarn/bun, or custom, and includes an `upgrade_client` action with both `command` and `argv`.
|
|
33
|
+
|
|
32
34
|
## Common commands
|
|
33
35
|
|
|
34
36
|
### One-command app deploy
|
package/cli.mjs
CHANGED
|
@@ -24,6 +24,7 @@ Commands:
|
|
|
24
24
|
init mpp Set up with MPP payment rail (Tempo Moderato testnet)
|
|
25
25
|
status Show full account state (allowance, balance, tier, projects)
|
|
26
26
|
wallets Manage multiple named wallets (list, new, use, rename, bind, import)
|
|
27
|
+
credentials Manage local credential material (project-keys)
|
|
27
28
|
allowance Manage your agent allowance (create, fund, balance, status)
|
|
28
29
|
tier Manage tier subscription (status, set)
|
|
29
30
|
projects Manage projects (provision, list, query, inspect, delete)
|
|
@@ -154,6 +155,11 @@ switch (cmd) {
|
|
|
154
155
|
await run(sub, rest);
|
|
155
156
|
break;
|
|
156
157
|
}
|
|
158
|
+
case "credentials": {
|
|
159
|
+
const { run } = await import("./lib/credentials.mjs");
|
|
160
|
+
await run(sub, rest);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
157
163
|
case "allowance": {
|
|
158
164
|
const { run } = await import("./lib/allowance.mjs");
|
|
159
165
|
await run(sub, rest);
|
package/core-dist/config.js
CHANGED
|
@@ -119,8 +119,17 @@ export function getConfigDir() {
|
|
|
119
119
|
return profile === DEFAULT_PROFILE ? base : join(base, "profiles", profile);
|
|
120
120
|
}
|
|
121
121
|
export function getKeystorePath() {
|
|
122
|
+
return getProjectCredentialsPath();
|
|
123
|
+
}
|
|
124
|
+
export function getLegacyProjectsPath() {
|
|
122
125
|
return join(getConfigDir(), "projects.json");
|
|
123
126
|
}
|
|
127
|
+
export function getProjectCredentialsPath() {
|
|
128
|
+
return join(getConfigDir(), "credentials", "project-keys.v1.json");
|
|
129
|
+
}
|
|
130
|
+
export function getProfileStatePath() {
|
|
131
|
+
return join(getConfigDir(), "state.json");
|
|
132
|
+
}
|
|
124
133
|
export function getApiTargetConfigPath() {
|
|
125
134
|
return join(getConfigDir(), "target.json");
|
|
126
135
|
}
|
package/core-dist/keystore.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync, rmdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync, rmdirSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { getLegacyProjectsPath, getProjectCredentialsPath } from "./config.js";
|
|
5
|
+
import { clearActiveProjectId as clearProfileActiveProjectId, getActiveProjectId as getProfileActiveProjectId, recordMigration, setActiveProjectId as setProfileActiveProjectId, } from "./profile-state.js";
|
|
5
6
|
function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
|
|
6
7
|
const lockDir = path + ".lock";
|
|
7
8
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -29,56 +30,107 @@ function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
|
|
|
29
30
|
}
|
|
30
31
|
throw new Error(`Could not acquire keystore lock after ${retries} retries: ${lockDir}`);
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (const item of parsed) {
|
|
46
|
-
if (item.project_id) {
|
|
47
|
-
projects[item.project_id] = {
|
|
48
|
-
anon_key: item.anon_key,
|
|
49
|
-
service_key: item.service_key,
|
|
50
|
-
...(item.site_url && { site_url: item.site_url }),
|
|
51
|
-
...(item.deployed_at && { deployed_at: item.deployed_at }),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
33
|
+
function normalizeParsedKeyStore(parsed) {
|
|
34
|
+
if (Array.isArray(parsed)) {
|
|
35
|
+
const projects = {};
|
|
36
|
+
for (const item of parsed) {
|
|
37
|
+
if (item.project_id) {
|
|
38
|
+
projects[item.project_id] = {
|
|
39
|
+
anon_key: item.anon_key,
|
|
40
|
+
service_key: item.service_key,
|
|
41
|
+
...(item.site_url && { site_url: item.site_url }),
|
|
42
|
+
...(item.deployed_at && { deployed_at: item.deployed_at }),
|
|
43
|
+
cached_at: new Date().toISOString(),
|
|
44
|
+
source: "legacy_projects_json",
|
|
45
|
+
};
|
|
54
46
|
}
|
|
55
|
-
return { projects };
|
|
56
47
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
return { version: 1, source: "local_cache", projects };
|
|
49
|
+
}
|
|
50
|
+
if (parsed && typeof parsed === "object" && "projects" in parsed) {
|
|
51
|
+
const obj = parsed;
|
|
52
|
+
const rawProjects = obj.projects && typeof obj.projects === "object" && !Array.isArray(obj.projects)
|
|
53
|
+
? obj.projects
|
|
54
|
+
: {};
|
|
55
|
+
const projects = {};
|
|
56
|
+
for (const [id, proj] of Object.entries(rawProjects)) {
|
|
57
|
+
const rec = { ...proj };
|
|
58
|
+
delete rec.tier;
|
|
59
|
+
delete rec.lease_expires_at;
|
|
60
|
+
delete rec.expires_at;
|
|
61
|
+
projects[id] = rec;
|
|
69
62
|
}
|
|
70
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
version: 1,
|
|
65
|
+
source: "local_cache",
|
|
66
|
+
...(typeof obj.active_project_id === "string" && { active_project_id: obj.active_project_id }),
|
|
67
|
+
...(typeof obj.previous_active_project_id === "string" && { previous_active_project_id: obj.previous_active_project_id }),
|
|
68
|
+
projects,
|
|
69
|
+
...(typeof obj.migrated_from === "string" && { migrated_from: obj.migrated_from }),
|
|
70
|
+
...(typeof obj.migrated_at === "string" && { migrated_at: obj.migrated_at }),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { version: 1, source: "local_cache", projects: {} };
|
|
74
|
+
}
|
|
75
|
+
function loadParsedKeyStore(path) {
|
|
76
|
+
try {
|
|
77
|
+
return normalizeParsedKeyStore(JSON.parse(readFileSync(path, "utf-8")));
|
|
71
78
|
}
|
|
72
79
|
catch {
|
|
73
|
-
return { projects: {} };
|
|
80
|
+
return { version: 1, source: "local_cache", projects: {} };
|
|
74
81
|
}
|
|
75
82
|
}
|
|
83
|
+
function migrateLegacyProjectsJson(targetPath) {
|
|
84
|
+
const legacyPath = getLegacyProjectsPath();
|
|
85
|
+
if (existsSync(targetPath) || !existsSync(legacyPath))
|
|
86
|
+
return;
|
|
87
|
+
const legacy = loadParsedKeyStore(legacyPath);
|
|
88
|
+
const migratedAt = new Date().toISOString();
|
|
89
|
+
const cache = {
|
|
90
|
+
version: 1,
|
|
91
|
+
source: "local_cache",
|
|
92
|
+
projects: legacy.projects,
|
|
93
|
+
migrated_from: legacyPath,
|
|
94
|
+
migrated_at: migratedAt,
|
|
95
|
+
};
|
|
96
|
+
saveKeyStore(cache, targetPath);
|
|
97
|
+
if (legacy.active_project_id) {
|
|
98
|
+
setProfileActiveProjectId(legacy.active_project_id);
|
|
99
|
+
}
|
|
100
|
+
recordMigration("projects_json_import", {
|
|
101
|
+
legacy_path: legacyPath,
|
|
102
|
+
cache_path: targetPath,
|
|
103
|
+
project_count: Object.keys(legacy.projects).length,
|
|
104
|
+
imported_at: migratedAt,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load the project-key credential cache from disk.
|
|
109
|
+
* Auto-imports legacy `projects.json` formats into the new cache path when using
|
|
110
|
+
* the default location:
|
|
111
|
+
* - Array format (CLI legacy): [{project_id, ...}] → {projects: {id: {...}}}
|
|
112
|
+
* - Object format: {active_project_id, projects} → credentials cache + state.json
|
|
113
|
+
* - Old metadata fields: tier/expires_at/lease_expires_at are stripped
|
|
114
|
+
*/
|
|
115
|
+
export function loadKeyStore(path) {
|
|
116
|
+
const p = path ?? getProjectCredentialsPath();
|
|
117
|
+
if (!path)
|
|
118
|
+
migrateLegacyProjectsJson(p);
|
|
119
|
+
return loadParsedKeyStore(p);
|
|
120
|
+
}
|
|
76
121
|
export function saveKeyStore(store, path) {
|
|
77
|
-
const p = path ??
|
|
122
|
+
const p = path ?? getProjectCredentialsPath();
|
|
78
123
|
const dir = dirname(p);
|
|
79
124
|
mkdirSync(dir, { recursive: true });
|
|
80
|
-
const
|
|
81
|
-
|
|
125
|
+
const cache = {
|
|
126
|
+
version: 1,
|
|
127
|
+
source: "local_cache",
|
|
128
|
+
...(store.migrated_from ? { migrated_from: store.migrated_from } : {}),
|
|
129
|
+
...(store.migrated_at ? { migrated_at: store.migrated_at } : {}),
|
|
130
|
+
projects: store.projects,
|
|
131
|
+
};
|
|
132
|
+
const tmp = join(dir, `.project-keys.${randomBytes(4).toString("hex")}.tmp`);
|
|
133
|
+
writeFileSync(tmp, JSON.stringify(cache, null, 2), { mode: 0o600 });
|
|
82
134
|
renameSync(tmp, p);
|
|
83
135
|
chmodSync(p, 0o600);
|
|
84
136
|
}
|
|
@@ -87,55 +139,38 @@ export function getProject(projectId, path) {
|
|
|
87
139
|
return store.projects[projectId];
|
|
88
140
|
}
|
|
89
141
|
export function saveProject(projectId, project, path) {
|
|
90
|
-
const p = path ??
|
|
142
|
+
const p = path ?? getProjectCredentialsPath();
|
|
91
143
|
withFileLock(p, () => {
|
|
92
144
|
const store = loadKeyStore(p);
|
|
93
|
-
store.projects[projectId] = project;
|
|
145
|
+
store.projects[projectId] = { ...project, cached_at: project.cached_at ?? new Date().toISOString() };
|
|
94
146
|
saveKeyStore(store, p);
|
|
95
147
|
});
|
|
96
148
|
}
|
|
97
149
|
export function updateProject(projectId, update, path) {
|
|
98
|
-
const p = path ??
|
|
150
|
+
const p = path ?? getProjectCredentialsPath();
|
|
99
151
|
withFileLock(p, () => {
|
|
100
152
|
const store = loadKeyStore(p);
|
|
101
153
|
const existing = store.projects[projectId];
|
|
102
154
|
if (existing) {
|
|
103
|
-
store.projects[projectId] = { ...existing, ...update };
|
|
155
|
+
store.projects[projectId] = { ...existing, ...update, cached_at: existing.cached_at ?? new Date().toISOString() };
|
|
104
156
|
saveKeyStore(store, p);
|
|
105
157
|
}
|
|
106
158
|
});
|
|
107
159
|
}
|
|
108
160
|
export function removeProject(projectId, path) {
|
|
109
|
-
const p = path ??
|
|
161
|
+
const p = path ?? getProjectCredentialsPath();
|
|
110
162
|
withFileLock(p, () => {
|
|
111
163
|
const store = loadKeyStore(p);
|
|
112
164
|
delete store.projects[projectId];
|
|
113
|
-
if (store.active_project_id === projectId) {
|
|
114
|
-
const fallback = store.previous_active_project_id;
|
|
115
|
-
if (fallback && fallback !== projectId && store.projects[fallback]) {
|
|
116
|
-
store.active_project_id = fallback;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
delete store.active_project_id;
|
|
120
|
-
}
|
|
121
|
-
delete store.previous_active_project_id;
|
|
122
|
-
}
|
|
123
165
|
saveKeyStore(store, p);
|
|
124
166
|
});
|
|
167
|
+
if (!path)
|
|
168
|
+
clearProfileActiveProjectId(projectId);
|
|
125
169
|
}
|
|
126
170
|
export function getActiveProjectId(path) {
|
|
127
|
-
|
|
128
|
-
return store.active_project_id;
|
|
171
|
+
return getProfileActiveProjectId(path);
|
|
129
172
|
}
|
|
130
173
|
export function setActiveProjectId(projectId, path) {
|
|
131
|
-
|
|
132
|
-
withFileLock(p, () => {
|
|
133
|
-
const store = loadKeyStore(p);
|
|
134
|
-
if (store.active_project_id && store.active_project_id !== projectId) {
|
|
135
|
-
store.previous_active_project_id = store.active_project_id;
|
|
136
|
-
}
|
|
137
|
-
store.active_project_id = projectId;
|
|
138
|
-
saveKeyStore(store, p);
|
|
139
|
-
});
|
|
174
|
+
setProfileActiveProjectId(projectId, path);
|
|
140
175
|
}
|
|
141
176
|
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync, rmdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getActiveProfile, getApiBase, getProfileStatePath } from "./config.js";
|
|
5
|
+
function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
|
|
6
|
+
const lockDir = path + ".lock";
|
|
7
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
8
|
+
for (let i = 0; i < retries; i++) {
|
|
9
|
+
try {
|
|
10
|
+
mkdirSync(lockDir, { mode: 0o700 });
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
const code = e.code;
|
|
14
|
+
if (code !== "EEXIST")
|
|
15
|
+
throw e;
|
|
16
|
+
const until = Date.now() + delayMs;
|
|
17
|
+
while (Date.now() < until) { /* spin */ }
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return fn();
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
try {
|
|
25
|
+
rmdirSync(lockDir);
|
|
26
|
+
}
|
|
27
|
+
catch { /* best-effort */ }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Could not acquire profile-state lock after ${retries} retries: ${lockDir}`);
|
|
31
|
+
}
|
|
32
|
+
export function loadProfileState(path) {
|
|
33
|
+
const p = path ?? getProfileStatePath();
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
36
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
37
|
+
return {};
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function saveProfileState(state, path) {
|
|
45
|
+
const p = path ?? getProfileStatePath();
|
|
46
|
+
const dir = dirname(p);
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
const tmp = join(dir, `.state.${randomBytes(4).toString("hex")}.tmp`);
|
|
49
|
+
writeFileSync(tmp, JSON.stringify({ version: 1, ...state }, null, 2), { mode: 0o600 });
|
|
50
|
+
renameSync(tmp, p);
|
|
51
|
+
chmodSync(p, 0o600);
|
|
52
|
+
}
|
|
53
|
+
export function defaultActiveProjectScope(scope = {}) {
|
|
54
|
+
return {
|
|
55
|
+
api_base: scope.api_base ?? getApiBase(),
|
|
56
|
+
profile: scope.profile ?? getActiveProfile(),
|
|
57
|
+
principal: scope.principal ?? null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function activeProjectScopeKey(scope = {}) {
|
|
61
|
+
const resolved = defaultActiveProjectScope(scope);
|
|
62
|
+
return JSON.stringify({
|
|
63
|
+
api_base: resolved.api_base,
|
|
64
|
+
profile: resolved.profile,
|
|
65
|
+
principal: resolved.principal ?? "unknown",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function getActiveProjectId(path, scope = {}) {
|
|
69
|
+
const state = loadProfileState(path);
|
|
70
|
+
const key = activeProjectScopeKey(scope);
|
|
71
|
+
return state.active_projects?.[key]?.project_id ?? state.active_project_id;
|
|
72
|
+
}
|
|
73
|
+
export function setActiveProjectId(projectId, path, scope = {}) {
|
|
74
|
+
const p = path ?? getProfileStatePath();
|
|
75
|
+
withFileLock(p, () => {
|
|
76
|
+
const state = loadProfileState(p);
|
|
77
|
+
const key = activeProjectScopeKey(scope);
|
|
78
|
+
const resolved = defaultActiveProjectScope(scope);
|
|
79
|
+
const current = state.active_projects?.[key]?.project_id ?? state.active_project_id;
|
|
80
|
+
const previous = current && current !== projectId ? current : undefined;
|
|
81
|
+
state.active_projects = state.active_projects ?? {};
|
|
82
|
+
state.active_projects[key] = {
|
|
83
|
+
project_id: projectId,
|
|
84
|
+
...(previous ? { previous_project_id: previous } : {}),
|
|
85
|
+
api_base: resolved.api_base,
|
|
86
|
+
principal: resolved.principal ?? null,
|
|
87
|
+
profile: resolved.profile,
|
|
88
|
+
updated_at: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
state.active_project_id = projectId;
|
|
91
|
+
if (previous)
|
|
92
|
+
state.previous_active_project_id = previous;
|
|
93
|
+
else
|
|
94
|
+
delete state.previous_active_project_id;
|
|
95
|
+
saveProfileState(state, p);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export function clearActiveProjectId(projectId, path, scope = {}) {
|
|
99
|
+
const p = path ?? getProfileStatePath();
|
|
100
|
+
withFileLock(p, () => {
|
|
101
|
+
const state = loadProfileState(p);
|
|
102
|
+
const key = activeProjectScopeKey(scope);
|
|
103
|
+
const scoped = state.active_projects?.[key];
|
|
104
|
+
if (scoped?.project_id === projectId && state.active_projects) {
|
|
105
|
+
if (scoped.previous_project_id) {
|
|
106
|
+
state.active_projects[key] = {
|
|
107
|
+
...scoped,
|
|
108
|
+
project_id: scoped.previous_project_id,
|
|
109
|
+
previous_project_id: undefined,
|
|
110
|
+
updated_at: new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
delete state.active_projects[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (state.active_project_id === projectId) {
|
|
118
|
+
if (state.previous_active_project_id && state.previous_active_project_id !== projectId) {
|
|
119
|
+
state.active_project_id = state.previous_active_project_id;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
delete state.active_project_id;
|
|
123
|
+
}
|
|
124
|
+
delete state.previous_active_project_id;
|
|
125
|
+
}
|
|
126
|
+
saveProfileState(state, p);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
export function recordMigration(marker, value, path) {
|
|
130
|
+
const p = path ?? getProfileStatePath();
|
|
131
|
+
withFileLock(p, () => {
|
|
132
|
+
const state = loadProfileState(p);
|
|
133
|
+
state.migrations = { ...(state.migrations ?? {}), [marker]: value };
|
|
134
|
+
saveProfileState(state, p);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=profile-state.js.map
|
package/lib/config.mjs
CHANGED
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
getApiBase,
|
|
8
8
|
getApiBaseSource,
|
|
9
9
|
getApiTargetKind,
|
|
10
|
+
getActiveProfile,
|
|
10
11
|
getConfigDir,
|
|
11
|
-
|
|
12
|
+
getLegacyProjectsPath,
|
|
13
|
+
getProfileStatePath,
|
|
14
|
+
getProjectCredentialsPath,
|
|
12
15
|
getAllowancePath,
|
|
13
16
|
configureApiBase,
|
|
14
17
|
isCoreApiTarget,
|
|
@@ -18,18 +21,22 @@ import { readAllowance as coreReadAllowance, saveAllowance as coreSaveAllowance
|
|
|
18
21
|
import { loadKeyStore, getProject, saveProject, updateProject, removeProject, saveKeyStore, getActiveProjectId, setActiveProjectId } from "../core-dist/keystore.js";
|
|
19
22
|
import { getAllowanceAuthHeaders as coreGetAllowanceAuthHeaders } from "../core-dist/allowance-auth.js";
|
|
20
23
|
import { fail } from "./sdk-errors.mjs";
|
|
21
|
-
import { initializeWalletAction,
|
|
24
|
+
import { initializeWalletAction, selectProjectAction } from "./next-actions.mjs";
|
|
22
25
|
|
|
23
26
|
// Wallet-dependent paths are exposed as getters (preferred — they always
|
|
24
27
|
// reflect the active profile, even if some future code path imports this module
|
|
25
28
|
// before wallet resolution). Production code (init/doctor/allowance) uses these.
|
|
26
29
|
export function configDir() { return getConfigDir(); }
|
|
27
30
|
export function allowanceFile() { return getAllowancePath(); }
|
|
28
|
-
export function
|
|
31
|
+
export function projectCredentialsFile() { return getProjectCredentialsPath(); }
|
|
32
|
+
export function profileStateFile() { return getProfileStatePath(); }
|
|
33
|
+
export function legacyProjectsFile() { return getLegacyProjectsPath(); }
|
|
34
|
+
export function projectsFile() { return projectCredentialsFile(); }
|
|
29
35
|
export function apiBase() { return getApiBase(); }
|
|
30
36
|
export function apiBaseSource() { return getApiBaseSource(); }
|
|
31
37
|
export function apiTargetKind() { return getApiTargetKind(); }
|
|
32
38
|
export function coreTarget() { return isCoreApiTarget(); }
|
|
39
|
+
export function activeProfile() { return getActiveProfile(); }
|
|
33
40
|
|
|
34
41
|
// Snapshot constants, retained for backward compatibility (tests, the OpenClaw
|
|
35
42
|
// config re-export). These are evaluated when this module is first imported.
|
|
@@ -39,7 +46,9 @@ export function coreTarget() { return isCoreApiTarget(); }
|
|
|
39
46
|
// prefer the getters above.
|
|
40
47
|
export const CONFIG_DIR = getConfigDir();
|
|
41
48
|
export const ALLOWANCE_FILE = getAllowancePath();
|
|
42
|
-
export const PROJECTS_FILE =
|
|
49
|
+
export const PROJECTS_FILE = getProjectCredentialsPath();
|
|
50
|
+
export const PROJECT_CREDENTIALS_FILE = getProjectCredentialsPath();
|
|
51
|
+
export const PROFILE_STATE_FILE = getProfileStatePath();
|
|
43
52
|
|
|
44
53
|
// API base is independent of the active wallet, so a module-load snapshot is safe.
|
|
45
54
|
export const API = getApiBase();
|
|
@@ -89,41 +98,42 @@ export function findProject(id) {
|
|
|
89
98
|
const p = getProject(id);
|
|
90
99
|
if (!p) {
|
|
91
100
|
const idStr = id ?? "";
|
|
92
|
-
const hint = idStr && !String(idStr).startsWith("prj_")
|
|
93
|
-
? `project IDs start with "prj_". Check that the argument order is <project_id> <name>.`
|
|
94
|
-
: undefined;
|
|
95
101
|
fail({
|
|
96
|
-
code: "
|
|
97
|
-
message: `
|
|
98
|
-
hint,
|
|
99
|
-
details: { project_id: idStr, source: "
|
|
100
|
-
next_actions: [
|
|
102
|
+
code: "PROJECT_CREDENTIAL_NOT_FOUND",
|
|
103
|
+
message: `No local project credentials cached for ${idStr}.`,
|
|
104
|
+
hint: "Use a principal-auth command, or import project keys with `run402 credentials project-keys import --project <id> --service-key-stdin`.",
|
|
105
|
+
details: { project_id: idStr, source: "local_cache", cache_path: projectCredentialsFile(), wallet: activeProfile(), profile: activeProfile() },
|
|
106
|
+
next_actions: [{
|
|
107
|
+
type: "run_command",
|
|
108
|
+
command: `run402 credentials project-keys status --project ${idStr || "<id>"}`,
|
|
109
|
+
why: "Inspect the local project-key cache without revealing secrets.",
|
|
110
|
+
}],
|
|
101
111
|
});
|
|
102
112
|
}
|
|
103
113
|
return p;
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
export function resolveProject(id) {
|
|
107
|
-
const projectId = id || getActiveProjectId();
|
|
117
|
+
const projectId = id || process.env.RUN402_PROJECT_ID || getActiveProjectId();
|
|
108
118
|
if (!projectId) {
|
|
109
119
|
fail({
|
|
110
|
-
code: "
|
|
120
|
+
code: "PROJECT_REQUIRED",
|
|
111
121
|
message: "no project specified and no active project set.",
|
|
112
|
-
hint: "
|
|
113
|
-
next_actions: [
|
|
122
|
+
hint: "Pass --project <id>, set RUN402_PROJECT_ID, or run: run402 projects use <id>",
|
|
123
|
+
next_actions: [selectProjectAction()],
|
|
114
124
|
});
|
|
115
125
|
}
|
|
116
126
|
return findProject(projectId);
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
export function resolveProjectId(id) {
|
|
120
|
-
const projectId = id || getActiveProjectId();
|
|
130
|
+
const projectId = id || process.env.RUN402_PROJECT_ID || getActiveProjectId();
|
|
121
131
|
if (!projectId) {
|
|
122
132
|
fail({
|
|
123
|
-
code: "
|
|
133
|
+
code: "PROJECT_REQUIRED",
|
|
124
134
|
message: "no project specified and no active project set.",
|
|
125
|
-
hint: "
|
|
126
|
-
next_actions: [
|
|
135
|
+
hint: "Pass --project <id>, set RUN402_PROJECT_ID, or run: run402 projects use <id>",
|
|
136
|
+
next_actions: [selectProjectAction()],
|
|
127
137
|
});
|
|
128
138
|
}
|
|
129
139
|
return projectId;
|
|
@@ -134,6 +144,7 @@ export {
|
|
|
134
144
|
configureApiBase,
|
|
135
145
|
isCoreApiTarget,
|
|
136
146
|
readApiTargetConfig,
|
|
147
|
+
getProject,
|
|
137
148
|
loadKeyStore,
|
|
138
149
|
saveProject,
|
|
139
150
|
updateProject,
|