@synap-core/cli 1.2.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/README.md +26 -0
- package/dist/commands/agents.d.ts +31 -0
- package/dist/commands/agents.js +478 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/connect.d.ts +18 -1
- package/dist/commands/connect.js +154 -74
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/connections.d.ts +9 -0
- package/dist/commands/connections.js +161 -0
- package/dist/commands/connections.js.map +1 -0
- package/dist/commands/data.d.ts +43 -0
- package/dist/commands/data.js +387 -0
- package/dist/commands/data.js.map +1 -0
- package/dist/commands/finish.js +41 -8
- package/dist/commands/finish.js.map +1 -1
- package/dist/commands/infra.d.ts +21 -0
- package/dist/commands/infra.js +262 -0
- package/dist/commands/infra.js.map +1 -0
- package/dist/commands/init.js +188 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/knowledge.d.ts +36 -0
- package/dist/commands/knowledge.js +123 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/openclaw.d.ts +2 -0
- package/dist/commands/openclaw.js +300 -23
- package/dist/commands/openclaw.js.map +1 -1
- package/dist/commands/pods.d.ts +17 -0
- package/dist/commands/pods.js +371 -0
- package/dist/commands/pods.js.map +1 -0
- package/dist/commands/status.d.ts +14 -1
- package/dist/commands/status.js +78 -220
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts +11 -2
- package/dist/commands/update.js +116 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +370 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/agents-config.d.ts +20 -0
- package/dist/lib/agents-config.js +45 -0
- package/dist/lib/agents-config.js.map +1 -0
- package/dist/lib/auth.d.ts +4 -0
- package/dist/lib/auth.js +4 -0
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/browser-auth.d.ts +35 -0
- package/dist/lib/browser-auth.js +170 -0
- package/dist/lib/browser-auth.js.map +1 -0
- package/dist/lib/hub-client.d.ts +17 -0
- package/dist/lib/hub-client.js +115 -0
- package/dist/lib/hub-client.js.map +1 -0
- package/dist/lib/openclaw.js +30 -19
- package/dist/lib/openclaw.js.map +1 -1
- package/dist/lib/pod.d.ts +32 -1
- package/dist/lib/pod.js +121 -9
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/skills-installer.d.ts +18 -0
- package/dist/lib/skills-installer.js +97 -0
- package/dist/lib/skills-installer.js.map +1 -0
- package/dist/lib/targets.d.ts +65 -0
- package/dist/lib/targets.js +673 -0
- package/dist/lib/targets.js.map +1 -0
- package/package.json +5 -3
- package/skills/README.md +91 -0
- package/skills/synap/README.md +76 -0
- package/skills/synap/SKILL.md +882 -0
- package/skills/synap/capture.md +170 -0
- package/skills/synap/governance.md +206 -0
- package/skills/synap/linking.md +128 -0
- package/skills/synap/scripts/orient.sh +28 -0
- package/skills/synap-schema/SKILL.md +231 -0
- package/skills/synap-schema/property-types.md +228 -0
- package/skills/synap-ui/SKILL.md +295 -0
- package/skills/synap-ui/bento-recipes.md +608 -0
- package/skills/synap-ui/view-types.md +259 -0
- package/skills/synap-ui/widget-catalog.md +305 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* synap infra
|
|
3
|
+
*
|
|
4
|
+
* Infrastructure management via Dokploy — view servers, trigger deploys,
|
|
5
|
+
* tail logs, and sync server state into Synap entities.
|
|
6
|
+
*
|
|
7
|
+
* synap infra — overview: servers + services status
|
|
8
|
+
* synap infra status — same as above
|
|
9
|
+
* synap infra deploy <app> — trigger a redeploy for a named app
|
|
10
|
+
* synap infra logs <app> — tail logs for a service
|
|
11
|
+
* synap infra sync — pull Dokploy state into Synap entities
|
|
12
|
+
* synap infra open — open Dokploy dashboard in browser
|
|
13
|
+
*/
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import ora from "ora";
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
import { log, banner } from "../utils/logger.js";
|
|
18
|
+
function getDokployConfig() {
|
|
19
|
+
const url = process.env.DOKPLOY_URL ?? process.env.DEPLOY_DOMAIN
|
|
20
|
+
? `https://${process.env.DEPLOY_DOMAIN}`
|
|
21
|
+
: "https://deploy.synap.live";
|
|
22
|
+
const apiKey = process.env.DOKPLOY_API_KEY ?? "";
|
|
23
|
+
if (!apiKey)
|
|
24
|
+
return null;
|
|
25
|
+
return { url, apiKey };
|
|
26
|
+
}
|
|
27
|
+
async function dokployFetch(path, options = {}) {
|
|
28
|
+
const config = getDokployConfig();
|
|
29
|
+
if (!config) {
|
|
30
|
+
throw new Error("DOKPLOY_API_KEY not set. Get it from the Dokploy dashboard → Settings → API Keys,\n" +
|
|
31
|
+
" then add it to your .env or export DOKPLOY_API_KEY=<key>");
|
|
32
|
+
}
|
|
33
|
+
const res = await fetch(`${config.url}${path}`, {
|
|
34
|
+
...options,
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
"x-api-key": config.apiKey,
|
|
38
|
+
...options.headers,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const text = await res.text().catch(() => res.statusText);
|
|
43
|
+
throw new Error(`Dokploy ${path}: ${res.status} ${text}`);
|
|
44
|
+
}
|
|
45
|
+
return res.json();
|
|
46
|
+
}
|
|
47
|
+
// ─── Status overview ─────────────────────────────────────────────────────────
|
|
48
|
+
export async function infraStatus() {
|
|
49
|
+
banner();
|
|
50
|
+
const config = getDokployConfig();
|
|
51
|
+
if (!config) {
|
|
52
|
+
log.warn("Dokploy API key not configured.");
|
|
53
|
+
log.blank();
|
|
54
|
+
log.dim("1. Open https://deploy.synap.live → Settings → API Keys → Create key");
|
|
55
|
+
log.dim("2. Add to your shell: export DOKPLOY_API_KEY=<key>");
|
|
56
|
+
log.dim("3. Or add to .env: DOKPLOY_API_KEY=<key>");
|
|
57
|
+
log.blank();
|
|
58
|
+
log.info(`Dashboard: ${chalk.cyan("https://deploy.synap.live")}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const spinner = ora("Fetching infrastructure status...").start();
|
|
62
|
+
try {
|
|
63
|
+
// Dokploy API: GET /api/servers and GET /api/applications
|
|
64
|
+
const [servers, apps] = await Promise.all([
|
|
65
|
+
dokployFetch("/api/server.all").catch(() => []),
|
|
66
|
+
dokployFetch("/api/application.all").catch(() => []),
|
|
67
|
+
]);
|
|
68
|
+
spinner.stop();
|
|
69
|
+
// ── Servers ──
|
|
70
|
+
log.heading("Servers");
|
|
71
|
+
if (servers.length === 0) {
|
|
72
|
+
log.dim(" No servers connected. Add one in the Dokploy dashboard.");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
for (const s of servers) {
|
|
76
|
+
const dot = s.status === "active"
|
|
77
|
+
? chalk.green("●")
|
|
78
|
+
: s.status === "inactive"
|
|
79
|
+
? chalk.red("●")
|
|
80
|
+
: chalk.yellow("●");
|
|
81
|
+
console.log(` ${dot} ${chalk.bold(s.name)} ${chalk.dim(s.ip)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
log.blank();
|
|
85
|
+
// ── Services ──
|
|
86
|
+
log.heading("Services");
|
|
87
|
+
if (apps.length === 0) {
|
|
88
|
+
log.dim(" No services deployed yet.");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const byServer = new Map();
|
|
92
|
+
for (const app of apps) {
|
|
93
|
+
const key = app.serverId ?? "local";
|
|
94
|
+
if (!byServer.has(key))
|
|
95
|
+
byServer.set(key, []);
|
|
96
|
+
byServer.get(key).push(app);
|
|
97
|
+
}
|
|
98
|
+
for (const [serverId, list] of byServer) {
|
|
99
|
+
const server = servers.find((s) => s.id === serverId);
|
|
100
|
+
if (server)
|
|
101
|
+
log.dim(` ── ${server.name} ──`);
|
|
102
|
+
for (const app of list) {
|
|
103
|
+
const status = app.status === "running"
|
|
104
|
+
? chalk.green(app.status)
|
|
105
|
+
: app.status === "error"
|
|
106
|
+
? chalk.red(app.status)
|
|
107
|
+
: chalk.yellow(app.status);
|
|
108
|
+
console.log(` ${chalk.bold(app.name)} ${status} ${chalk.dim(app.env ?? "")}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
log.blank();
|
|
113
|
+
log.dim(`Dashboard: ${chalk.cyan(config.url)}`);
|
|
114
|
+
log.dim("To deploy: synap infra deploy <app-name>");
|
|
115
|
+
log.dim("To tail logs: synap infra logs <app-name>");
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
spinner.fail("Failed to reach Dokploy");
|
|
119
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
120
|
+
log.error(msg);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ─── Deploy ──────────────────────────────────────────────────────────────────
|
|
124
|
+
export async function infraDeploy(appName) {
|
|
125
|
+
const spinner = ora(`Triggering deploy for ${chalk.bold(appName)}...`).start();
|
|
126
|
+
try {
|
|
127
|
+
// List apps, find by name
|
|
128
|
+
const apps = await dokployFetch("/api/application.all");
|
|
129
|
+
const match = apps.find((a) => a.name.toLowerCase() === appName.toLowerCase() ||
|
|
130
|
+
a.appName?.toLowerCase() === appName.toLowerCase());
|
|
131
|
+
if (!match) {
|
|
132
|
+
spinner.fail(`App not found: ${appName}`);
|
|
133
|
+
log.blank();
|
|
134
|
+
log.dim("Available apps:");
|
|
135
|
+
for (const a of apps)
|
|
136
|
+
log.dim(` • ${a.name}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await dokployFetch(`/api/application.deploy`, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
body: JSON.stringify({ applicationId: match.id }),
|
|
142
|
+
});
|
|
143
|
+
spinner.succeed(`Deploy triggered for ${chalk.bold(match.name)}`);
|
|
144
|
+
log.dim(`Watch progress: synap infra logs ${appName}`);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
spinner.fail("Deploy failed");
|
|
148
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ─── Logs ────────────────────────────────────────────────────────────────────
|
|
152
|
+
export async function infraLogs(appName, opts) {
|
|
153
|
+
try {
|
|
154
|
+
const apps = await dokployFetch("/api/application.all");
|
|
155
|
+
const match = apps.find((a) => a.name.toLowerCase() === appName.toLowerCase() ||
|
|
156
|
+
a.appName?.toLowerCase() === appName.toLowerCase());
|
|
157
|
+
if (!match) {
|
|
158
|
+
log.error(`App not found: ${appName}`);
|
|
159
|
+
log.blank();
|
|
160
|
+
log.dim("Available apps:");
|
|
161
|
+
for (const a of apps)
|
|
162
|
+
log.dim(` • ${a.name}`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Use docker logs via container name (Dokploy uses appName as container name)
|
|
166
|
+
const containerName = match.appName ?? match.name;
|
|
167
|
+
const tail = opts.lines ?? 100;
|
|
168
|
+
const followFlag = opts.follow ? "-f" : "";
|
|
169
|
+
const cmd = `docker logs --tail ${tail} ${followFlag} ${containerName}`;
|
|
170
|
+
log.dim(`> ${cmd}`);
|
|
171
|
+
log.blank();
|
|
172
|
+
execSync(cmd, { stdio: "inherit" });
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ─── Sync ─────────────────────────────────────────────────────────────────────
|
|
179
|
+
export async function infraSync(podUrl, apiKey) {
|
|
180
|
+
const spinner = ora("Syncing Dokploy state into Synap entities...").start();
|
|
181
|
+
try {
|
|
182
|
+
const [servers, apps] = await Promise.all([
|
|
183
|
+
dokployFetch("/api/server.all"),
|
|
184
|
+
dokployFetch("/api/application.all"),
|
|
185
|
+
]);
|
|
186
|
+
spinner.text = `Upserting ${servers.length} servers + ${apps.length} deployments...`;
|
|
187
|
+
// Push to Synap Hub Protocol
|
|
188
|
+
const headers = {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
Authorization: `Bearer ${apiKey}`,
|
|
191
|
+
};
|
|
192
|
+
let created = 0;
|
|
193
|
+
let updated = 0;
|
|
194
|
+
for (const s of servers) {
|
|
195
|
+
const res = await fetch(`${podUrl}/api/hub/entities/upsert`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers,
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
profileSlug: "server",
|
|
200
|
+
dedupeKey: `dokploy:server:${s.id}`,
|
|
201
|
+
title: s.name,
|
|
202
|
+
properties: {
|
|
203
|
+
ip: s.ip,
|
|
204
|
+
status: s.status === "active" ? "online" : "offline",
|
|
205
|
+
region: s.region ?? "",
|
|
206
|
+
dokployServerId: s.id,
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
if (res.ok) {
|
|
211
|
+
const { created: c } = await res.json();
|
|
212
|
+
c ? created++ : updated++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const app of apps) {
|
|
216
|
+
const res = await fetch(`${podUrl}/api/hub/entities/upsert`, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers,
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
profileSlug: "deployment",
|
|
221
|
+
dedupeKey: `dokploy:app:${app.id}`,
|
|
222
|
+
title: app.name,
|
|
223
|
+
properties: {
|
|
224
|
+
deployStatus: app.status,
|
|
225
|
+
env: app.env ?? "production",
|
|
226
|
+
url: app.domain ? `https://${app.domain}` : "",
|
|
227
|
+
image: app.image ?? "",
|
|
228
|
+
dokployAppId: app.id,
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
if (res.ok) {
|
|
233
|
+
const { created: c } = await res.json();
|
|
234
|
+
c ? created++ : updated++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
spinner.succeed(`Sync complete — ${chalk.green(created)} created, ${chalk.blue(updated)} updated`);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
spinner.fail("Sync failed");
|
|
241
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ─── Open dashboard ──────────────────────────────────────────────────────────
|
|
245
|
+
export function infraOpen() {
|
|
246
|
+
const config = getDokployConfig();
|
|
247
|
+
const url = config?.url ?? "https://deploy.synap.live";
|
|
248
|
+
try {
|
|
249
|
+
const platform = process.platform;
|
|
250
|
+
const cmd = platform === "darwin"
|
|
251
|
+
? "open"
|
|
252
|
+
: platform === "win32"
|
|
253
|
+
? "start"
|
|
254
|
+
: "xdg-open";
|
|
255
|
+
execSync(`${cmd} ${url}`);
|
|
256
|
+
log.success(`Opening ${url}`);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
log.info(`Dashboard: ${chalk.cyan(url)}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=infra.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infra.js","sourceRoot":"","sources":["../../src/commands/infra.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -19,6 +19,33 @@ import { seedAgentEntities } from "../lib/seed.js";
|
|
|
19
19
|
import { login, isLoggedIn, listPods, getStoredToken, waitForPodCallback } from "../lib/auth.js";
|
|
20
20
|
export async function init(opts) {
|
|
21
21
|
banner();
|
|
22
|
+
// ── Pre-flight: "no pod anywhere" gate ──────────────────────────────────
|
|
23
|
+
// Keep this CLI laptop/agent-focused. If the user ran `synap init` without
|
|
24
|
+
// any signal of an existing pod, point them at the two supported ways to
|
|
25
|
+
// get one (self-host via ./synap install on a server, or a managed pod on
|
|
26
|
+
// synap.live) and exit. This replaces silent failure / interactive
|
|
27
|
+
// provisioning when there's nothing to connect to.
|
|
28
|
+
//
|
|
29
|
+
// A --pod-url flag that's explicitly unreachable is a separate diagnostic
|
|
30
|
+
// (handled below once checkPodHealth has confirmed).
|
|
31
|
+
if (!opts.podUrl) {
|
|
32
|
+
if (await hasAnyPodSignal()) {
|
|
33
|
+
// fall through into the normal flow
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
printNoPodInstructions();
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Explicit URL was passed — probe it before letting downstream assume
|
|
42
|
+
// reachability. Fail loud with a helpful hint instead of silent 500s.
|
|
43
|
+
const probe = await checkPodHealth(opts.podUrl);
|
|
44
|
+
if (!probe.healthy) {
|
|
45
|
+
printUnreachablePodInstructions(opts.podUrl);
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
22
49
|
// ── Auto-detect environment ─────────────────────────────────────────────
|
|
23
50
|
const oc = detectOpenClaw();
|
|
24
51
|
const isServer = detectServer();
|
|
@@ -524,10 +551,36 @@ async function podInstallLocalStep(opts) {
|
|
|
524
551
|
if (resources.ramFree < 1500) {
|
|
525
552
|
log.warn(`Low RAM (${resources.ramFree}MB free). Synap needs ~1.5GB. Consider managed hosting.`);
|
|
526
553
|
}
|
|
554
|
+
const { installDomain } = await prompts({
|
|
555
|
+
type: "text",
|
|
556
|
+
name: "installDomain",
|
|
557
|
+
message: "Domain for this pod (use localhost for local setup):",
|
|
558
|
+
initial: "localhost",
|
|
559
|
+
});
|
|
560
|
+
if (!installDomain)
|
|
561
|
+
return null;
|
|
562
|
+
let installEmail = "";
|
|
563
|
+
if (installDomain !== "localhost") {
|
|
564
|
+
const emailPrompt = await prompts({
|
|
565
|
+
type: "text",
|
|
566
|
+
name: "installEmail",
|
|
567
|
+
message: "Email for Let's Encrypt SSL certificates:",
|
|
568
|
+
});
|
|
569
|
+
installEmail = emailPrompt.installEmail ?? "";
|
|
570
|
+
if (!installEmail) {
|
|
571
|
+
log.error("Email is required for non-localhost domains.");
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const escapedDomain = String(installDomain).replace(/'/g, "'\\''");
|
|
576
|
+
const escapedEmail = String(installEmail).replace(/'/g, "'\\''");
|
|
577
|
+
const installCmd = installDomain === "localhost"
|
|
578
|
+
? `curl -fsSL https://raw.githubusercontent.com/Synap-core/backend/main/install.sh | bash -s -- --domain '${escapedDomain}'`
|
|
579
|
+
: `curl -fsSL https://raw.githubusercontent.com/Synap-core/backend/main/install.sh | bash -s -- --domain '${escapedDomain}' --email '${escapedEmail}'`;
|
|
527
580
|
log.blank();
|
|
528
581
|
log.info("Install Synap pod with:");
|
|
529
582
|
log.blank();
|
|
530
|
-
console.log(chalk.cyan(
|
|
583
|
+
console.log(chalk.cyan(` ${installCmd}`));
|
|
531
584
|
log.blank();
|
|
532
585
|
const { proceed } = await prompts({
|
|
533
586
|
type: "confirm",
|
|
@@ -537,7 +590,7 @@ async function podInstallLocalStep(opts) {
|
|
|
537
590
|
});
|
|
538
591
|
if (proceed) {
|
|
539
592
|
try {
|
|
540
|
-
execSync(
|
|
593
|
+
execSync(installCmd, { stdio: "inherit" });
|
|
541
594
|
return "http://localhost:4000";
|
|
542
595
|
}
|
|
543
596
|
catch {
|
|
@@ -816,8 +869,53 @@ export async function isStep(podUrl, apiKey, openclawFound) {
|
|
|
816
869
|
// 3. Configure OpenClaw provider (if found and IS is active)
|
|
817
870
|
if (openclawFound && isActive) {
|
|
818
871
|
const ocRuntime = detectOpenClaw();
|
|
819
|
-
|
|
820
|
-
|
|
872
|
+
if (ocRuntime.runtime === "docker") {
|
|
873
|
+
// Docker path: write via openclaw config set
|
|
874
|
+
const containerName = ocRuntime.containerName ?? "openclaw";
|
|
875
|
+
const { configureOc } = await prompts({
|
|
876
|
+
type: "confirm",
|
|
877
|
+
name: "configureOc",
|
|
878
|
+
message: "Configure Synap IS as OpenClaw AI provider?",
|
|
879
|
+
initial: true,
|
|
880
|
+
});
|
|
881
|
+
if (configureOc) {
|
|
882
|
+
const synapProvider = {
|
|
883
|
+
baseUrl: `${podUrl}/v1`,
|
|
884
|
+
api: "openai-completions",
|
|
885
|
+
apiKey,
|
|
886
|
+
models: [
|
|
887
|
+
{ id: "synap/auto", name: "Synap Auto", contextWindow: 200000, maxTokens: 8192 },
|
|
888
|
+
{ id: "synap/balanced", name: "Synap Balanced", contextWindow: 131072, maxTokens: 8192 },
|
|
889
|
+
{ id: "synap/advanced", name: "Synap Advanced", contextWindow: 200000, maxTokens: 8192 },
|
|
890
|
+
],
|
|
891
|
+
};
|
|
892
|
+
const spinner = ora("Writing Synap IS provider to OpenClaw config...").start();
|
|
893
|
+
try {
|
|
894
|
+
execSync(`docker exec ${containerName} openclaw config set models.providers.synap ${JSON.stringify(JSON.stringify(synapProvider))}`, { stdio: "pipe", timeout: 15000 });
|
|
895
|
+
spinner.succeed("Synap IS registered as OpenClaw provider");
|
|
896
|
+
log.dim("Available models: synap/auto, synap/balanced, synap/advanced");
|
|
897
|
+
// Restart to apply (Docker has no hot reload)
|
|
898
|
+
try {
|
|
899
|
+
execSync(`docker restart ${containerName}`, { stdio: "pipe", timeout: 30000 });
|
|
900
|
+
log.dim("Container restarted to pick up new config");
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
log.warn("Restart failed — run manually: docker restart openclaw");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
catch (err) {
|
|
907
|
+
const stderr = err.stderr?.toString().trim();
|
|
908
|
+
spinner.fail("Failed to set Synap IS provider");
|
|
909
|
+
if (stderr)
|
|
910
|
+
log.dim(stderr);
|
|
911
|
+
log.dim("Run manually:");
|
|
912
|
+
log.dim(` docker exec ${containerName} openclaw config set models.providers.synap.baseUrl ${podUrl}/v1`);
|
|
913
|
+
log.dim(` docker exec ${containerName} openclaw config set models.providers.synap.api openai-completions`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
// Local install path: write to config file directly
|
|
821
919
|
const { configureOc } = await prompts({
|
|
822
920
|
type: "confirm",
|
|
823
921
|
name: "configureOc",
|
|
@@ -840,11 +938,6 @@ export async function isStep(podUrl, apiKey, openclawFound) {
|
|
|
840
938
|
log.success("Synap IS configured as OpenClaw provider — restart OpenClaw to apply");
|
|
841
939
|
}
|
|
842
940
|
}
|
|
843
|
-
else {
|
|
844
|
-
// Docker runtime — can't write config from host, tell user the command
|
|
845
|
-
log.success("IS is active. To configure it as OpenClaw provider:");
|
|
846
|
-
log.dim(` docker exec ${ocRuntime.containerName ?? "openclaw"} openclaw config set models.providers.synap.baseUrl ${podUrl}/v1`);
|
|
847
|
-
}
|
|
848
941
|
}
|
|
849
942
|
}
|
|
850
943
|
function printSummary(podUrl, openclawConnected) {
|
|
@@ -880,7 +973,7 @@ async function loginAndSelectPod() {
|
|
|
880
973
|
// Check if already logged in
|
|
881
974
|
const authStatus = await isLoggedIn();
|
|
882
975
|
if (!authStatus.valid) {
|
|
883
|
-
log.info("Opening browser to sign in...");
|
|
976
|
+
log.info("Opening browser to sign in and select your pod...");
|
|
884
977
|
const spinner = ora("Waiting for browser authentication...").start();
|
|
885
978
|
const creds = await login();
|
|
886
979
|
if (!creds) {
|
|
@@ -889,6 +982,17 @@ async function loginAndSelectPod() {
|
|
|
889
982
|
return null;
|
|
890
983
|
}
|
|
891
984
|
spinner.succeed(`Authenticated as ${creds.email}`);
|
|
985
|
+
// If the web flow already performed pod selection, use that result directly.
|
|
986
|
+
if (creds.podUrl && creds.podId) {
|
|
987
|
+
const healthSpinner = ora("Checking pod health...").start();
|
|
988
|
+
const status = await checkPodHealth(creds.podUrl);
|
|
989
|
+
if (status.healthy) {
|
|
990
|
+
healthSpinner.succeed(`Pod ready at ${creds.podUrl}`);
|
|
991
|
+
return { url: creds.podUrl, podId: creds.podId };
|
|
992
|
+
}
|
|
993
|
+
healthSpinner.warn(`Pod selected (${creds.podUrl}) but not yet reachable — may still be provisioning`);
|
|
994
|
+
return { url: creds.podUrl, podId: creds.podId };
|
|
995
|
+
}
|
|
892
996
|
}
|
|
893
997
|
else {
|
|
894
998
|
log.success(`Already logged in as ${authStatus.email}`);
|
|
@@ -896,6 +1000,10 @@ async function loginAndSelectPod() {
|
|
|
896
1000
|
const token = getStoredToken();
|
|
897
1001
|
if (!token)
|
|
898
1002
|
return null;
|
|
1003
|
+
// Short-circuit if credentials already carry a pod from a previous web-based selection.
|
|
1004
|
+
if (token.podUrl && token.podId) {
|
|
1005
|
+
return { url: token.podUrl, podId: token.podId };
|
|
1006
|
+
}
|
|
899
1007
|
// List pods
|
|
900
1008
|
const podsSpinner = ora("Fetching your pods...").start();
|
|
901
1009
|
try {
|
|
@@ -974,6 +1082,76 @@ function detectServer() {
|
|
|
974
1082
|
return false;
|
|
975
1083
|
}
|
|
976
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Does the environment carry ANY signal that a Synap pod exists somewhere?
|
|
1087
|
+
* Used by `init` to decide whether to run the full flow or short-circuit
|
|
1088
|
+
* with a "no pod detected" instruction message.
|
|
1089
|
+
*
|
|
1090
|
+
* Treat these as signals (in cheap-to-expensive order):
|
|
1091
|
+
* 1. SYNAP_POD_URL env var
|
|
1092
|
+
* 2. A stored ~/.synap/pod-config.json from a previous run
|
|
1093
|
+
* 3. A running pod detected on this machine
|
|
1094
|
+
* 4. An OpenClaw install that already has synap.podUrl configured
|
|
1095
|
+
*/
|
|
1096
|
+
async function hasAnyPodSignal() {
|
|
1097
|
+
if (process.env.SYNAP_POD_URL)
|
|
1098
|
+
return true;
|
|
1099
|
+
if (getLocalPodConfig()?.podUrl)
|
|
1100
|
+
return true;
|
|
1101
|
+
// OpenClaw may already carry a pod URL in its config — respect that.
|
|
1102
|
+
try {
|
|
1103
|
+
const oc = detectOpenClaw();
|
|
1104
|
+
const ocSynap = oc.config?.synap;
|
|
1105
|
+
if (typeof ocSynap?.podUrl === "string" && ocSynap.podUrl.length > 0)
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
// ignore — detection is best-effort
|
|
1110
|
+
}
|
|
1111
|
+
// Last: probe the local machine. This is the expensive step.
|
|
1112
|
+
const local = await detectLocalPod();
|
|
1113
|
+
return Boolean(local);
|
|
1114
|
+
}
|
|
1115
|
+
function printNoPodInstructions() {
|
|
1116
|
+
const line = "─".repeat(63);
|
|
1117
|
+
console.log(`
|
|
1118
|
+
┌${line}┐
|
|
1119
|
+
│ No Synap pod detected. │
|
|
1120
|
+
│ │
|
|
1121
|
+
│ You need a running pod before using @synap/cli init. │
|
|
1122
|
+
│ │
|
|
1123
|
+
│ Options: │
|
|
1124
|
+
│ │
|
|
1125
|
+
│ 1. Self-host on a server (free): │
|
|
1126
|
+
│ On your server, run: │
|
|
1127
|
+
│ curl -fsSL https://synap.live/install.sh | bash │
|
|
1128
|
+
│ Then come back and run: │
|
|
1129
|
+
│ npx @synap-core/cli init --pod-url https://your-pod... │
|
|
1130
|
+
│ │
|
|
1131
|
+
│ 2. Use a hosted pod ($): │
|
|
1132
|
+
│ Sign up at https://synap.live │
|
|
1133
|
+
│ Then run: │
|
|
1134
|
+
│ npx @synap-core/cli init --pod-url <your-pod-url> │
|
|
1135
|
+
│ │
|
|
1136
|
+
│ 3. Already running locally? Pass --pod-url: │
|
|
1137
|
+
│ npx @synap-core/cli init --pod-url http://localhost:4000 │
|
|
1138
|
+
└${line}┘
|
|
1139
|
+
`);
|
|
1140
|
+
}
|
|
1141
|
+
function printUnreachablePodInstructions(podUrl) {
|
|
1142
|
+
console.log("");
|
|
1143
|
+
log.error(`Pod at ${podUrl} is not reachable.`);
|
|
1144
|
+
log.blank();
|
|
1145
|
+
log.info("Checklist:");
|
|
1146
|
+
log.dim(" - Is the pod URL correct? (scheme + host + port)");
|
|
1147
|
+
log.dim(" - Is the pod container running? On the server: ./synap health");
|
|
1148
|
+
log.dim(" - Is there a firewall or reverse proxy in the way?");
|
|
1149
|
+
log.dim(" - For localhost setups, did you start the stack? ./synap start");
|
|
1150
|
+
log.blank();
|
|
1151
|
+
log.info("Once the pod is reachable, re-run:");
|
|
1152
|
+
log.dim(` npx @synap-core/cli init --pod-url ${podUrl}`);
|
|
1153
|
+
log.blank();
|
|
1154
|
+
}
|
|
977
1155
|
/**
|
|
978
1156
|
* Probe the local machine for a running Synap pod.
|
|
979
1157
|
* Returns the best URL candidate if found, null otherwise.
|