@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.
Files changed (74) hide show
  1. package/README.md +26 -0
  2. package/dist/commands/agents.d.ts +31 -0
  3. package/dist/commands/agents.js +478 -0
  4. package/dist/commands/agents.js.map +1 -0
  5. package/dist/commands/connect.d.ts +18 -1
  6. package/dist/commands/connect.js +154 -74
  7. package/dist/commands/connect.js.map +1 -1
  8. package/dist/commands/connections.d.ts +9 -0
  9. package/dist/commands/connections.js +161 -0
  10. package/dist/commands/connections.js.map +1 -0
  11. package/dist/commands/data.d.ts +43 -0
  12. package/dist/commands/data.js +387 -0
  13. package/dist/commands/data.js.map +1 -0
  14. package/dist/commands/finish.js +41 -8
  15. package/dist/commands/finish.js.map +1 -1
  16. package/dist/commands/infra.d.ts +21 -0
  17. package/dist/commands/infra.js +262 -0
  18. package/dist/commands/infra.js.map +1 -0
  19. package/dist/commands/init.js +188 -10
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/knowledge.d.ts +36 -0
  22. package/dist/commands/knowledge.js +123 -0
  23. package/dist/commands/knowledge.js.map +1 -0
  24. package/dist/commands/openclaw.d.ts +2 -0
  25. package/dist/commands/openclaw.js +300 -23
  26. package/dist/commands/openclaw.js.map +1 -1
  27. package/dist/commands/pods.d.ts +17 -0
  28. package/dist/commands/pods.js +371 -0
  29. package/dist/commands/pods.js.map +1 -0
  30. package/dist/commands/status.d.ts +14 -1
  31. package/dist/commands/status.js +78 -220
  32. package/dist/commands/status.js.map +1 -1
  33. package/dist/commands/update.d.ts +11 -2
  34. package/dist/commands/update.js +116 -5
  35. package/dist/commands/update.js.map +1 -1
  36. package/dist/index.js +370 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/agents-config.d.ts +20 -0
  39. package/dist/lib/agents-config.js +45 -0
  40. package/dist/lib/agents-config.js.map +1 -0
  41. package/dist/lib/auth.d.ts +4 -0
  42. package/dist/lib/auth.js +4 -0
  43. package/dist/lib/auth.js.map +1 -1
  44. package/dist/lib/browser-auth.d.ts +35 -0
  45. package/dist/lib/browser-auth.js +170 -0
  46. package/dist/lib/browser-auth.js.map +1 -0
  47. package/dist/lib/hub-client.d.ts +17 -0
  48. package/dist/lib/hub-client.js +115 -0
  49. package/dist/lib/hub-client.js.map +1 -0
  50. package/dist/lib/openclaw.js +30 -19
  51. package/dist/lib/openclaw.js.map +1 -1
  52. package/dist/lib/pod.d.ts +32 -1
  53. package/dist/lib/pod.js +121 -9
  54. package/dist/lib/pod.js.map +1 -1
  55. package/dist/lib/skills-installer.d.ts +18 -0
  56. package/dist/lib/skills-installer.js +97 -0
  57. package/dist/lib/skills-installer.js.map +1 -0
  58. package/dist/lib/targets.d.ts +65 -0
  59. package/dist/lib/targets.js +673 -0
  60. package/dist/lib/targets.js.map +1 -0
  61. package/package.json +5 -3
  62. package/skills/README.md +91 -0
  63. package/skills/synap/README.md +76 -0
  64. package/skills/synap/SKILL.md +882 -0
  65. package/skills/synap/capture.md +170 -0
  66. package/skills/synap/governance.md +206 -0
  67. package/skills/synap/linking.md +128 -0
  68. package/skills/synap/scripts/orient.sh +28 -0
  69. package/skills/synap-schema/SKILL.md +231 -0
  70. package/skills/synap-schema/property-types.md +228 -0
  71. package/skills/synap-ui/SKILL.md +295 -0
  72. package/skills/synap-ui/bento-recipes.md +608 -0
  73. package/skills/synap-ui/view-types.md +259 -0
  74. 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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"}
@@ -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(" curl -fsSL https://raw.githubusercontent.com/Synap-core/backend/main/install.sh | bash"));
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("curl -fsSL https://raw.githubusercontent.com/Synap-core/backend/main/install.sh | bash", { stdio: "inherit" });
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
- // Only write local config if OpenClaw has a local config file to write to
820
- if (ocRuntime.runtime !== "docker") {
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.