synsci 1.2.5 → 1.2.7

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 (2) hide show
  1. package/bin/synsci.mjs +121 -34
  2. package/package.json +2 -2
package/bin/synsci.mjs CHANGED
@@ -25,7 +25,11 @@ import { createInterface } from "node:readline"
25
25
  import { fileURLToPath } from "node:url"
26
26
 
27
27
  const SELF_PATH = (() => {
28
- try { return realpathSync(fileURLToPath(import.meta.url)) } catch { return "" }
28
+ try {
29
+ return realpathSync(fileURLToPath(import.meta.url))
30
+ } catch {
31
+ return ""
32
+ }
29
33
  })()
30
34
 
31
35
  const BOLD = "\x1b[1m"
@@ -54,8 +58,12 @@ const LOGO = [
54
58
  "╚══════╝ ╚═════╝╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚══════╝",
55
59
  ]
56
60
 
57
- function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`) }
58
- function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`) }
61
+ function ok(msg) {
62
+ console.log(` ${GREEN}✓${RESET} ${msg}`)
63
+ }
64
+ function warn(msg) {
65
+ console.log(` ${YELLOW}⚠${RESET} ${msg}`)
66
+ }
59
67
 
60
68
  function spinner(msg) {
61
69
  const frames = ["◒", "◐", "◓", "◑"]
@@ -65,21 +73,50 @@ function spinner(msg) {
65
73
  process.stdout.write(`${CLEAR_LINE} ${CYAN}${frames[i++ % frames.length]}${RESET} ${msg}`)
66
74
  }, 80)
67
75
  return {
68
- ok(result) { clearInterval(id); process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`); ok(result) },
69
- warn(result) { clearInterval(id); process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`); warn(result) },
70
- fail(result) { clearInterval(id); process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`); console.log(` ${RED}✗${RESET} ${result}`) },
71
- update(m) { msg = m },
76
+ ok(result) {
77
+ clearInterval(id)
78
+ process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`)
79
+ ok(result)
80
+ },
81
+ warn(result) {
82
+ clearInterval(id)
83
+ process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`)
84
+ warn(result)
85
+ },
86
+ fail(result) {
87
+ clearInterval(id)
88
+ process.stdout.write(`${CLEAR_LINE}${SHOW_CURSOR}`)
89
+ console.log(` ${RED}✗${RESET} ${result}`)
90
+ },
91
+ update(m) {
92
+ msg = m
93
+ },
72
94
  }
73
95
  }
74
96
 
75
97
  function runQuiet(cmd) {
76
- try { return execSync(cmd, { encoding: "utf-8", stdio: "pipe" }).trim() }
77
- catch { return null }
98
+ try {
99
+ return execSync(cmd, { encoding: "utf-8", stdio: "pipe" }).trim()
100
+ } catch {
101
+ return null
102
+ }
103
+ }
104
+
105
+ // Windows global installs expose a .cmd shim, which can't be exec'd
106
+ // directly — it needs a shell (and the path quoted for it).
107
+ const isCmdShim = (p) => process.platform === "win32" && p.toLowerCase().endsWith(".cmd")
108
+
109
+ function execCli(file, args = [], opts = {}) {
110
+ if (isCmdShim(file)) return execSync(['"' + file + '"', ...args].join(" "), opts)
111
+ return execFileSync(file, args, opts)
78
112
  }
79
113
 
80
114
  function runFileQuiet(file, args = []) {
81
- try { return execFileSync(file, args, { encoding: "utf-8", stdio: "pipe" }).trim() }
82
- catch { return null }
115
+ try {
116
+ return execCli(file, args, { encoding: "utf-8", stdio: "pipe" }).trim()
117
+ } catch {
118
+ return null
119
+ }
83
120
  }
84
121
 
85
122
  function isLauncherPath(p) {
@@ -88,7 +125,9 @@ function isLauncherPath(p) {
88
125
  if (SELF_PATH && real === SELF_PATH) return true
89
126
  if (real.includes("/_npx/")) return true
90
127
  return false
91
- } catch { return false }
128
+ } catch {
129
+ return false
130
+ }
92
131
  }
93
132
 
94
133
  // Returns the absolute path to the real @synsci/openscience binary (`openscience`).
@@ -97,20 +136,29 @@ function isLauncherPath(p) {
97
136
  // `--version` so half-broken installs are skipped instead of accepted.
98
137
  function resolveCli() {
99
138
  const candidates = []
100
- // 1. Global npm prefix (where `npm i -g @synsci/openscience` puts it)
139
+ // 1. Global npm prefix (where `npm i -g @synsci/openscience` puts it).
140
+ // On Windows the global bin dir is the prefix itself and the entry is an
141
+ // openscience.cmd shim; on POSIX it's <prefix>/bin/openscience.
101
142
  const prefix = runQuiet("npm prefix -g")
102
- if (prefix) candidates.push(join(prefix, "bin", "openscience"))
103
- // 2. ~/.openscience/bin/openscience (curl-installer location)
104
- candidates.push(join(homedir(), ".openscience", "bin", "openscience"))
143
+ if (prefix) {
144
+ if (process.platform === "win32") candidates.push(join(prefix, "openscience.cmd"))
145
+ else candidates.push(join(prefix, "bin", "openscience"))
146
+ }
147
+ // 2. ~/.openscience/bin/openscience (curl-installer location, POSIX only)
148
+ if (process.platform !== "win32") candidates.push(join(homedir(), ".openscience", "bin", "openscience"))
105
149
 
106
150
  for (const cand of candidates) {
107
151
  if (!existsSync(cand) || isLauncherPath(cand)) continue
108
152
  try {
109
- const ver = execFileSync(cand, ["--version"], {
110
- encoding: "utf-8", stdio: "pipe", timeout: 5000,
153
+ const ver = execCli(cand, ["--version"], {
154
+ encoding: "utf-8",
155
+ stdio: "pipe",
156
+ timeout: 5000,
111
157
  }).trim()
112
158
  if (/^\d/.test(ver)) return cand
113
- } catch { /* unrunnable candidate, try next */ }
159
+ } catch {
160
+ /* unrunnable candidate, try next */
161
+ }
114
162
  }
115
163
  return null
116
164
  }
@@ -122,10 +170,16 @@ function resolveCli() {
122
170
  // still prints JSON, so read stdout either way.
123
171
  function hasDeprecatedCli() {
124
172
  let out = ""
125
- try { out = execSync("npm ls -g @synsci/cli --depth=0 --json", { encoding: "utf-8", stdio: "pipe" }) }
126
- catch (e) { out = e && typeof e.stdout === "string" ? e.stdout : "" }
127
- try { return Boolean(JSON.parse(out).dependencies["@synsci/cli"]) }
128
- catch { return false }
173
+ try {
174
+ out = execSync("npm ls -g @synsci/cli --depth=0 --json", { encoding: "utf-8", stdio: "pipe" })
175
+ } catch (e) {
176
+ out = e && typeof e.stdout === "string" ? e.stdout : ""
177
+ }
178
+ try {
179
+ return Boolean(JSON.parse(out).dependencies["@synsci/cli"])
180
+ } catch {
181
+ return false
182
+ }
129
183
  }
130
184
 
131
185
  function isConnected() {
@@ -136,10 +190,17 @@ function isConnected() {
136
190
  const data = JSON.parse(readFileSync(sessionPath, "utf-8"))
137
191
  if (!data.access_token || !data.expires_at) return false
138
192
  return new Date(data.expires_at) > new Date()
139
- } catch { return false }
193
+ } catch {
194
+ return false
195
+ }
140
196
  }
141
197
 
142
198
  function atlasVersion() {
199
+ // `atlas` on PATH may be Ariga Atlas or the MongoDB Atlas CLI, whose
200
+ // --version output also survives the digit filter. Only trust the command
201
+ // when the global @synsci/atlas package is present to own it.
202
+ const owned = runQuiet("npm ls -g @synsci/atlas --depth=0")
203
+ if (!owned || !owned.includes("@synsci/atlas")) return null
143
204
  const raw = runQuiet("atlas --version")
144
205
  return raw ? raw.replace(/[^0-9.]/g, "") : null
145
206
  }
@@ -175,19 +236,27 @@ async function installOrUpdateAtlas() {
175
236
  async function ask(question) {
176
237
  const rl = createInterface({ input: process.stdin, output: process.stdout })
177
238
  return new Promise((resolve) => {
178
- rl.question(question, (answer) => { rl.close(); resolve(answer.trim()) })
239
+ rl.question(question, (answer) => {
240
+ rl.close()
241
+ resolve(answer.trim())
242
+ })
179
243
  })
180
244
  }
181
245
 
182
246
  async function main() {
183
247
  process.on("exit", () => process.stdout.write(SHOW_CURSOR))
184
- process.on("SIGINT", () => { process.stdout.write(SHOW_CURSOR); process.exit(130) })
248
+ process.on("SIGINT", () => {
249
+ process.stdout.write(SHOW_CURSOR)
250
+ process.exit(130)
251
+ })
185
252
 
186
253
  // --- Logo ---
187
254
  console.log()
188
255
  for (const line of LOGO) console.log(` ${CYAN}${line}${RESET}`)
189
256
  console.log()
190
- console.log(` ${BOLD}Synthetic Sciences${RESET} ${DIM}OpenScience, the open-source AI research workspace · Atlas, the research platform${RESET}`)
257
+ console.log(
258
+ ` ${BOLD}Synthetic Sciences${RESET} ${DIM}OpenScience, the open-source AI research workspace · Atlas, the research platform${RESET}`,
259
+ )
191
260
  console.log()
192
261
 
193
262
  // --- Step 1: Install or upgrade the OpenScience CLI ---
@@ -215,7 +284,7 @@ async function main() {
215
284
  } else {
216
285
  s.update(`Upgrading ${current} → ${latest}...`)
217
286
  try {
218
- execFileSync(cliPath, ["upgrade"], { stdio: "pipe" })
287
+ execCli(cliPath, ["upgrade"], { stdio: "pipe" })
219
288
  s.ok(`Upgraded to ${latest}`)
220
289
  } catch {
221
290
  s.warn(`Upgrade failed, continuing with ${current}`)
@@ -243,6 +312,13 @@ async function main() {
243
312
  if (!cliPath) throw new Error("openscience not on PATH after install")
244
313
  s.ok("Installed OpenScience")
245
314
  } catch {
315
+ // The standalone installer is a bash script; on native Windows there's
316
+ // no bash to pipe it into, so don't suggest a fallback that can't run.
317
+ if (process.platform === "win32") {
318
+ s.fail("Install failed")
319
+ console.log(`\n Try manually: ${CYAN}npm i -g @synsci/openscience${RESET}\n`)
320
+ process.exit(1)
321
+ }
246
322
  // Global npm installs commonly fail on permissions. Fall back to the
247
323
  // standalone installer, which lands in ~/.openscience/bin without sudo
248
324
  // (resolveCli already checks that location).
@@ -265,9 +341,15 @@ async function main() {
265
341
  console.log()
266
342
  console.log(` ${BOLD}How do you want to run it?${RESET}`)
267
343
  console.log()
268
- console.log(` ${BOLD}1${RESET} ${CYAN}OpenScience${RESET} ${DIM}free and open source, bring your own API keys, no account${RESET}`)
269
- console.log(` ${BOLD}2${RESET} ${CYAN}OpenScience + Atlas${RESET} ${DIM}managed models, wallet billing, research graph & compute${RESET}`)
270
- console.log(` ${BOLD}3${RESET} ${CYAN}Atlas CLI${RESET} ${DIM}just the Atlas research CLI — maps, runs, and compute from the terminal${RESET}`)
344
+ console.log(
345
+ ` ${BOLD}1${RESET} ${CYAN}OpenScience${RESET} ${DIM}free and open source, bring your own API keys, no account${RESET}`,
346
+ )
347
+ console.log(
348
+ ` ${BOLD}2${RESET} ${CYAN}OpenScience + Atlas${RESET} ${DIM}managed models, wallet billing, research graph & compute${RESET}`,
349
+ )
350
+ console.log(
351
+ ` ${BOLD}3${RESET} ${CYAN}Atlas CLI${RESET} ${DIM}just the Atlas research CLI — maps, runs, and compute from the terminal${RESET}`,
352
+ )
271
353
  console.log()
272
354
 
273
355
  const setup = await ask(` ${DIM}❯${RESET} Choose [1/2/3]: `)
@@ -289,7 +371,9 @@ async function main() {
289
371
  ok("Connected to Atlas")
290
372
  } else {
291
373
  console.log()
292
- console.log(` ${DIM}Connect your Atlas account for managed credentials:${RESET} ${CYAN}openscience connect login${RESET}`)
374
+ console.log(
375
+ ` ${DIM}Connect your Atlas account for managed credentials:${RESET} ${CYAN}openscience connect login${RESET}`,
376
+ )
293
377
  }
294
378
  console.log()
295
379
  console.log(` ${BOLD}Next steps${RESET}`)
@@ -306,7 +390,7 @@ async function main() {
306
390
  } else {
307
391
  console.log()
308
392
  try {
309
- execFileSync(cliPath, ["connect", "login"], { stdio: "inherit" })
393
+ execCli(cliPath, ["connect", "login"], { stdio: "inherit" })
310
394
  } catch {}
311
395
  }
312
396
  } else {
@@ -319,7 +403,10 @@ async function main() {
319
403
  console.log(` ${DIM}Opening the workspace in your browser…${RESET}`)
320
404
  console.log()
321
405
 
322
- const child = spawn(cliPath, ["web", ...process.argv.slice(2)], { stdio: "inherit" })
406
+ const webArgs = ["web", ...process.argv.slice(2)]
407
+ const child = isCmdShim(cliPath)
408
+ ? spawn(['"' + cliPath + '"', ...webArgs].join(" "), { stdio: "inherit", shell: true })
409
+ : spawn(cliPath, webArgs, { stdio: "inherit" })
323
410
  child.on("close", (code) => process.exit(code ?? 0))
324
411
  }
325
412
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synsci",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Install wizard for OpenScience, the open-source AI research workspace (optionally with Atlas)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -25,4 +25,4 @@
25
25
  "url": "git+https://github.com/synthetic-sciences/openscience.git"
26
26
  },
27
27
  "homepage": "https://syntheticsciences.ai"
28
- }
28
+ }