run402 1.4.0 → 1.5.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/lib/agent.mjs +1 -1
- package/lib/apps.mjs +7 -7
- package/lib/deploy.mjs +1 -1
- package/lib/functions.mjs +4 -4
- package/lib/image.mjs +1 -1
- package/lib/message.mjs +1 -1
- package/lib/projects.mjs +8 -8
- package/lib/secrets.mjs +3 -3
- package/lib/sites.mjs +2 -2
- package/lib/subdomains.mjs +3 -3
- package/lib/wallet.mjs +4 -4
- package/package.json +10 -4
package/lib/agent.mjs
CHANGED
|
@@ -47,7 +47,7 @@ async function contact(args) {
|
|
|
47
47
|
if (email) body.email = email;
|
|
48
48
|
if (webhook) body.webhook = webhook;
|
|
49
49
|
|
|
50
|
-
const res = await fetchPaid(`${API}/v1/
|
|
50
|
+
const res = await fetchPaid(`${API}/agent/v1/contact`, {
|
|
51
51
|
method: "PUT",
|
|
52
52
|
headers: { "Content-Type": "application/json" },
|
|
53
53
|
body: JSON.stringify(body),
|
package/lib/apps.mjs
CHANGED
|
@@ -31,7 +31,7 @@ Examples:
|
|
|
31
31
|
`;
|
|
32
32
|
|
|
33
33
|
async function browse(args) {
|
|
34
|
-
let url = `${API}/v1
|
|
34
|
+
let url = `${API}/apps/v1`;
|
|
35
35
|
const tags = [];
|
|
36
36
|
for (let i = 0; i < args.length; i++) {
|
|
37
37
|
if (args[i] === "--tag" && args[i + 1]) tags.push(args[++i]);
|
|
@@ -71,7 +71,7 @@ async function fork(versionId, name, args) {
|
|
|
71
71
|
const body = { version_id: versionId, name };
|
|
72
72
|
if (opts.subdomain) body.subdomain = opts.subdomain;
|
|
73
73
|
|
|
74
|
-
const res = await fetchPaid(`${API}/v1
|
|
74
|
+
const res = await fetchPaid(`${API}/fork/v1/${opts.tier}`, {
|
|
75
75
|
method: "POST",
|
|
76
76
|
headers: { "Content-Type": "application/json" },
|
|
77
77
|
body: JSON.stringify(body),
|
|
@@ -109,7 +109,7 @@ async function publish(projectId, args) {
|
|
|
109
109
|
if (opts.visibility) body.visibility = opts.visibility;
|
|
110
110
|
if (opts.forkAllowed !== undefined) body.fork_allowed = opts.forkAllowed;
|
|
111
111
|
|
|
112
|
-
const res = await fetch(`${API}/
|
|
112
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/publish`, {
|
|
113
113
|
method: "POST",
|
|
114
114
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
115
115
|
body: JSON.stringify(body),
|
|
@@ -121,7 +121,7 @@ async function publish(projectId, args) {
|
|
|
121
121
|
|
|
122
122
|
async function versions(projectId) {
|
|
123
123
|
const p = findProject(projectId);
|
|
124
|
-
const res = await fetch(`${API}/
|
|
124
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions`, {
|
|
125
125
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
126
126
|
});
|
|
127
127
|
const data = await res.json();
|
|
@@ -131,7 +131,7 @@ async function versions(projectId) {
|
|
|
131
131
|
|
|
132
132
|
async function inspect(versionId) {
|
|
133
133
|
if (!versionId) { console.error(JSON.stringify({ status: "error", message: "Missing version ID" })); process.exit(1); }
|
|
134
|
-
const res = await fetch(`${API}/v1
|
|
134
|
+
const res = await fetch(`${API}/apps/v1/${versionId}`);
|
|
135
135
|
const data = await res.json();
|
|
136
136
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
137
137
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -147,7 +147,7 @@ async function update(projectId, versionId, args) {
|
|
|
147
147
|
if (args[i] === "--fork-allowed") body.fork_allowed = true;
|
|
148
148
|
if (args[i] === "--no-fork") body.fork_allowed = false;
|
|
149
149
|
}
|
|
150
|
-
const res = await fetch(`${API}/
|
|
150
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions/${versionId}`, {
|
|
151
151
|
method: "PATCH",
|
|
152
152
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
153
153
|
body: JSON.stringify(body),
|
|
@@ -159,7 +159,7 @@ async function update(projectId, versionId, args) {
|
|
|
159
159
|
|
|
160
160
|
async function deleteVersion(projectId, versionId) {
|
|
161
161
|
const p = findProject(projectId);
|
|
162
|
-
const res = await fetch(`${API}/
|
|
162
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions/${versionId}`, {
|
|
163
163
|
method: "DELETE",
|
|
164
164
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
165
165
|
});
|
package/lib/deploy.mjs
CHANGED
|
@@ -84,7 +84,7 @@ export async function run(args) {
|
|
|
84
84
|
client.register("eip155:84532", new ExactEvmScheme(signer));
|
|
85
85
|
const fetchPaid = wrapFetchWithPayment(fetch, client);
|
|
86
86
|
|
|
87
|
-
const res = await fetchPaid(`${API}/v1
|
|
87
|
+
const res = await fetchPaid(`${API}/deploy/v1/${opts.tier}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(manifest) });
|
|
88
88
|
const result = await res.json();
|
|
89
89
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...result })); process.exit(1); }
|
|
90
90
|
saveProject(result);
|
package/lib/functions.mjs
CHANGED
|
@@ -65,7 +65,7 @@ async function deploy(projectId, name, args) {
|
|
|
65
65
|
if (opts.deps) body.deps = opts.deps;
|
|
66
66
|
|
|
67
67
|
const fetchPaid = await setupPaidFetch();
|
|
68
|
-
const res = await fetchPaid(`${API}/
|
|
68
|
+
const res = await fetchPaid(`${API}/projects/v1/admin/${projectId}/functions`, {
|
|
69
69
|
method: "POST",
|
|
70
70
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
71
71
|
body: JSON.stringify(body),
|
|
@@ -102,7 +102,7 @@ async function logs(projectId, name, args) {
|
|
|
102
102
|
for (let i = 0; i < args.length; i++) {
|
|
103
103
|
if (args[i] === "--tail" && args[i + 1]) tail = parseInt(args[++i]);
|
|
104
104
|
}
|
|
105
|
-
const res = await fetch(`${API}/
|
|
105
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`, {
|
|
106
106
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
107
107
|
});
|
|
108
108
|
const data = await res.json();
|
|
@@ -112,7 +112,7 @@ async function logs(projectId, name, args) {
|
|
|
112
112
|
|
|
113
113
|
async function list(projectId) {
|
|
114
114
|
const p = findProject(projectId);
|
|
115
|
-
const res = await fetch(`${API}/
|
|
115
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions`, {
|
|
116
116
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
117
117
|
});
|
|
118
118
|
const data = await res.json();
|
|
@@ -122,7 +122,7 @@ async function list(projectId) {
|
|
|
122
122
|
|
|
123
123
|
async function deleteFunction(projectId, name) {
|
|
124
124
|
const p = findProject(projectId);
|
|
125
|
-
const res = await fetch(`${API}/
|
|
125
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}`, {
|
|
126
126
|
method: "DELETE",
|
|
127
127
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
128
128
|
});
|
package/lib/image.mjs
CHANGED
|
@@ -66,7 +66,7 @@ export async function run(sub, args) {
|
|
|
66
66
|
client.register("eip155:84532", new ExactEvmScheme(signer));
|
|
67
67
|
const fetchPaid = wrapFetchWithPayment(fetch, client);
|
|
68
68
|
|
|
69
|
-
const res = await fetchPaid(`${API}/
|
|
69
|
+
const res = await fetchPaid(`${API}/generate-image/v1`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: opts.prompt, aspect: opts.aspect }) });
|
|
70
70
|
const data = await res.json();
|
|
71
71
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
72
72
|
|
package/lib/message.mjs
CHANGED
|
@@ -35,7 +35,7 @@ async function send(text) {
|
|
|
35
35
|
client.register("eip155:84532", new ExactEvmScheme(signer));
|
|
36
36
|
const fetchPaid = wrapFetchWithPayment(fetch, client);
|
|
37
37
|
|
|
38
|
-
const res = await fetchPaid(`${API}/v1
|
|
38
|
+
const res = await fetchPaid(`${API}/message/v1`, {
|
|
39
39
|
method: "POST",
|
|
40
40
|
headers: { "Content-Type": "application/json" },
|
|
41
41
|
body: JSON.stringify({ message: text }),
|
package/lib/projects.mjs
CHANGED
|
@@ -59,7 +59,7 @@ async function setupPaidFetch() {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function quote() {
|
|
62
|
-
const res = await fetch(`${API}/v1
|
|
62
|
+
const res = await fetch(`${API}/projects/v1`);
|
|
63
63
|
const data = await res.json();
|
|
64
64
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
65
65
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -74,7 +74,7 @@ async function provision(args) {
|
|
|
74
74
|
const fetchPaid = await setupPaidFetch();
|
|
75
75
|
const body = { tier: opts.tier };
|
|
76
76
|
if (opts.name) body.name = opts.name;
|
|
77
|
-
const res = await fetchPaid(`${API}/v1
|
|
77
|
+
const res = await fetchPaid(`${API}/projects/v1`, {
|
|
78
78
|
method: "POST",
|
|
79
79
|
headers: { "Content-Type": "application/json" },
|
|
80
80
|
body: JSON.stringify(body),
|
|
@@ -98,7 +98,7 @@ async function provision(args) {
|
|
|
98
98
|
async function rls(projectId, template, tablesJson) {
|
|
99
99
|
const p = findProject(projectId);
|
|
100
100
|
const tables = JSON.parse(tablesJson);
|
|
101
|
-
const res = await fetch(`${API}/
|
|
101
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/rls`, {
|
|
102
102
|
method: "POST",
|
|
103
103
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
104
104
|
body: JSON.stringify({ template, tables }),
|
|
@@ -116,7 +116,7 @@ async function list() {
|
|
|
116
116
|
|
|
117
117
|
async function sqlCmd(projectId, query) {
|
|
118
118
|
const p = findProject(projectId);
|
|
119
|
-
const res = await fetch(`${API}/
|
|
119
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/sql`, { method: "POST", headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "text/plain" }, body: query });
|
|
120
120
|
console.log(JSON.stringify(await res.json(), null, 2));
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -128,7 +128,7 @@ async function rest(projectId, table, queryParams) {
|
|
|
128
128
|
|
|
129
129
|
async function usage(projectId) {
|
|
130
130
|
const p = findProject(projectId);
|
|
131
|
-
const res = await fetch(`${API}/
|
|
131
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/usage`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
|
|
132
132
|
const data = await res.json();
|
|
133
133
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
134
134
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -136,7 +136,7 @@ async function usage(projectId) {
|
|
|
136
136
|
|
|
137
137
|
async function schema(projectId) {
|
|
138
138
|
const p = findProject(projectId);
|
|
139
|
-
const res = await fetch(`${API}/
|
|
139
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/schema`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
|
|
140
140
|
const data = await res.json();
|
|
141
141
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
142
142
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -144,7 +144,7 @@ async function schema(projectId) {
|
|
|
144
144
|
|
|
145
145
|
async function renew(projectId) {
|
|
146
146
|
const fetchPaid = await setupPaidFetch();
|
|
147
|
-
const res = await fetchPaid(`${API}/v1
|
|
147
|
+
const res = await fetchPaid(`${API}/projects/v1/${projectId}/renew`, { method: "POST", headers: { "Content-Type": "application/json" } });
|
|
148
148
|
const data = await res.json();
|
|
149
149
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
150
150
|
const projects = loadProjects();
|
|
@@ -155,7 +155,7 @@ async function renew(projectId) {
|
|
|
155
155
|
|
|
156
156
|
async function deleteProject(projectId) {
|
|
157
157
|
const p = findProject(projectId);
|
|
158
|
-
const res = await fetch(`${API}/v1
|
|
158
|
+
const res = await fetch(`${API}/projects/v1/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
|
|
159
159
|
if (res.status === 204 || res.ok) {
|
|
160
160
|
saveProjects(loadProjects().filter(pr => pr.project_id !== projectId));
|
|
161
161
|
console.log(JSON.stringify({ status: "ok", message: `Project ${projectId} deleted.` }));
|
package/lib/secrets.mjs
CHANGED
|
@@ -22,7 +22,7 @@ Notes:
|
|
|
22
22
|
|
|
23
23
|
async function set(projectId, key, value) {
|
|
24
24
|
const p = findProject(projectId);
|
|
25
|
-
const res = await fetch(`${API}/
|
|
25
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets`, {
|
|
26
26
|
method: "POST",
|
|
27
27
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
28
28
|
body: JSON.stringify({ key, value }),
|
|
@@ -34,7 +34,7 @@ async function set(projectId, key, value) {
|
|
|
34
34
|
|
|
35
35
|
async function list(projectId) {
|
|
36
36
|
const p = findProject(projectId);
|
|
37
|
-
const res = await fetch(`${API}/
|
|
37
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets`, {
|
|
38
38
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
39
39
|
});
|
|
40
40
|
const data = await res.json();
|
|
@@ -44,7 +44,7 @@ async function list(projectId) {
|
|
|
44
44
|
|
|
45
45
|
async function deleteSecret(projectId, key) {
|
|
46
46
|
const p = findProject(projectId);
|
|
47
|
-
const res = await fetch(`${API}/
|
|
47
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets/${encodeURIComponent(key)}`, {
|
|
48
48
|
method: "DELETE",
|
|
49
49
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
50
50
|
});
|
package/lib/sites.mjs
CHANGED
|
@@ -77,7 +77,7 @@ async function deploy(args) {
|
|
|
77
77
|
client.register("eip155:84532", new ExactEvmScheme(signer));
|
|
78
78
|
const fetchPaid = wrapFetchWithPayment(fetch, client);
|
|
79
79
|
|
|
80
|
-
const res = await fetchPaid(`${API}/v1
|
|
80
|
+
const res = await fetchPaid(`${API}/deployments/v1`, {
|
|
81
81
|
method: "POST",
|
|
82
82
|
headers: { "Content-Type": "application/json" },
|
|
83
83
|
body: JSON.stringify(body),
|
|
@@ -93,7 +93,7 @@ async function status(args) {
|
|
|
93
93
|
if (!args[i].startsWith("-")) { deploymentId = args[i]; break; }
|
|
94
94
|
}
|
|
95
95
|
if (!deploymentId) { console.error(JSON.stringify({ status: "error", message: "Missing deployment ID" })); process.exit(1); }
|
|
96
|
-
const res = await fetch(`${API}/v1
|
|
96
|
+
const res = await fetch(`${API}/deployments/v1/${deploymentId}`);
|
|
97
97
|
const data = await res.json();
|
|
98
98
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
99
99
|
console.log(JSON.stringify(data, null, 2));
|
package/lib/subdomains.mjs
CHANGED
|
@@ -31,7 +31,7 @@ async function claim(deploymentId, name, args) {
|
|
|
31
31
|
const p = findProject(opts.project);
|
|
32
32
|
headers["Authorization"] = `Bearer ${p.service_key}`;
|
|
33
33
|
}
|
|
34
|
-
const res = await fetch(`${API}/v1
|
|
34
|
+
const res = await fetch(`${API}/subdomains/v1`, {
|
|
35
35
|
method: "POST",
|
|
36
36
|
headers,
|
|
37
37
|
body: JSON.stringify({ name, deployment_id: deploymentId }),
|
|
@@ -51,7 +51,7 @@ async function deleteSubdomain(name, args) {
|
|
|
51
51
|
const p = findProject(opts.project);
|
|
52
52
|
headers["Authorization"] = `Bearer ${p.service_key}`;
|
|
53
53
|
}
|
|
54
|
-
const res = await fetch(`${API}/v1
|
|
54
|
+
const res = await fetch(`${API}/subdomains/v1/${encodeURIComponent(name)}`, {
|
|
55
55
|
method: "DELETE",
|
|
56
56
|
headers,
|
|
57
57
|
});
|
|
@@ -65,7 +65,7 @@ async function deleteSubdomain(name, args) {
|
|
|
65
65
|
|
|
66
66
|
async function list(projectId) {
|
|
67
67
|
const p = findProject(projectId);
|
|
68
|
-
const res = await fetch(`${API}/v1
|
|
68
|
+
const res = await fetch(`${API}/subdomains/v1`, {
|
|
69
69
|
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
70
70
|
});
|
|
71
71
|
const data = await res.json();
|
package/lib/wallet.mjs
CHANGED
|
@@ -68,7 +68,7 @@ async function fund() {
|
|
|
68
68
|
const client = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
69
69
|
const before = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => 0);
|
|
70
70
|
|
|
71
|
-
const res = await fetch(`${API}/v1
|
|
71
|
+
const res = await fetch(`${API}/faucet/v1`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address: w.address }) });
|
|
72
72
|
const data = await res.json();
|
|
73
73
|
if (!res.ok) {
|
|
74
74
|
console.log(JSON.stringify({ status: "error", ...data }));
|
|
@@ -111,7 +111,7 @@ async function balance() {
|
|
|
111
111
|
const [mainnetUsdc, sepoliaUsdc, billingRes] = await Promise.all([
|
|
112
112
|
readUsdcBalance(mainnetClient, USDC_MAINNET, w.address).catch(() => null),
|
|
113
113
|
readUsdcBalance(sepoliaClient, USDC_SEPOLIA, w.address).catch(() => null),
|
|
114
|
-
fetch(`${API}/v1/
|
|
114
|
+
fetch(`${API}/billing/v1/accounts/${w.address.toLowerCase()}`),
|
|
115
115
|
]);
|
|
116
116
|
|
|
117
117
|
const billing = billingRes.ok ? await billingRes.json() : null;
|
|
@@ -140,7 +140,7 @@ async function checkout(args) {
|
|
|
140
140
|
if (args[i] === "--amount" && args[i + 1]) amount = parseInt(args[++i], 10);
|
|
141
141
|
}
|
|
142
142
|
if (!amount) { console.error(JSON.stringify({ status: "error", message: "Missing --amount <usd_micros> (e.g. --amount 5000000 for $5)" })); process.exit(1); }
|
|
143
|
-
const res = await fetch(`${API}/v1/
|
|
143
|
+
const res = await fetch(`${API}/billing/v1/checkouts`, {
|
|
144
144
|
method: "POST",
|
|
145
145
|
headers: { "Content-Type": "application/json" },
|
|
146
146
|
body: JSON.stringify({ wallet: w.address.toLowerCase(), amount_usd_micros: amount }),
|
|
@@ -157,7 +157,7 @@ async function history(args) {
|
|
|
157
157
|
for (let i = 0; i < args.length; i++) {
|
|
158
158
|
if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i], 10);
|
|
159
159
|
}
|
|
160
|
-
const res = await fetch(`${API}/v1/
|
|
160
|
+
const res = await fetch(`${API}/billing/v1/accounts/${w.address.toLowerCase()}/history?limit=${limit}`);
|
|
161
161
|
const data = await res.json();
|
|
162
162
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
163
163
|
console.log(JSON.stringify(data, null, 2));
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 micropayments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"run402": "
|
|
7
|
+
"run402": "cli.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"cli.mjs",
|
|
@@ -22,8 +22,14 @@
|
|
|
22
22
|
"homepage": "https://run402.com",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/kychee-com/run402.git",
|
|
25
|
+
"url": "git+https://github.com/kychee-com/run402.git",
|
|
26
26
|
"directory": "cli"
|
|
27
27
|
},
|
|
28
|
-
"keywords": [
|
|
28
|
+
"keywords": [
|
|
29
|
+
"run402",
|
|
30
|
+
"x402",
|
|
31
|
+
"postgres",
|
|
32
|
+
"ai-agent",
|
|
33
|
+
"openclaw"
|
|
34
|
+
]
|
|
29
35
|
}
|