synsci 1.2.4 → 1.2.6
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/bin/synsci.mjs +190 -57
- 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 {
|
|
28
|
+
try {
|
|
29
|
+
return realpathSync(fileURLToPath(import.meta.url))
|
|
30
|
+
} catch {
|
|
31
|
+
return ""
|
|
32
|
+
}
|
|
29
33
|
})()
|
|
30
34
|
|
|
31
35
|
const BOLD = "\x1b[1m"
|
|
@@ -40,22 +44,26 @@ const SHOW_CURSOR = "\x1b[?25h"
|
|
|
40
44
|
const CLEAR_LINE = "\x1b[2K\r"
|
|
41
45
|
|
|
42
46
|
const LOGO = [
|
|
43
|
-
" ██████╗
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"██║
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"███████╗ ██████╗██╗███████╗███╗ ██╗
|
|
50
|
-
"██╔════╝██╔════╝██║██╔════╝████╗
|
|
51
|
-
"███████╗██║ ██║█████╗ ██╔██╗ ██║██║ █████╗ ",
|
|
52
|
-
"╚════██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔══╝ ",
|
|
53
|
-
"███████║╚██████╗██║███████╗██║
|
|
54
|
-
"╚══════╝ ╚═════╝╚═╝╚══════╝╚═╝ ╚═══╝
|
|
47
|
+
"███████╗██╗ ██╗███╗ ██╗████████╗██╗ ██╗███████╗████████╗██╗ ██████╗",
|
|
48
|
+
"██╔════╝╚██╗ ██╔╝████╗ ██║╚══██╔══╝██║ ██║██╔════╝╚══██╔══╝██║██╔════╝",
|
|
49
|
+
"███████╗ ╚████╔╝ ██╔██╗ ██║ ██║ ███████║█████╗ ██║ ██║██║ ",
|
|
50
|
+
"╚════██║ ╚██╔╝ ██║╚██╗██║ ██║ ██╔══██║██╔══╝ ██║ ██║██║ ",
|
|
51
|
+
"███████║ ██║ ██║ ╚████║ ██║ ██║ ██║███████╗ ██║ ██║╚██████╗",
|
|
52
|
+
"╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝",
|
|
53
|
+
"███████╗ ██████╗██╗███████╗███╗ ██╗ ██████╗███████╗███████╗",
|
|
54
|
+
"██╔════╝██╔════╝██║██╔════╝████╗ ██║██╔════╝██╔════╝██╔════╝",
|
|
55
|
+
"███████╗██║ ██║█████╗ ██╔██╗ ██║██║ █████╗ ███████╗",
|
|
56
|
+
"╚════██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔══╝ ╚════██║",
|
|
57
|
+
"███████║╚██████╗██║███████╗██║ ╚████║╚██████╗███████╗███████║",
|
|
58
|
+
"╚══════╝ ╚═════╝╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚══════╝",
|
|
55
59
|
]
|
|
56
60
|
|
|
57
|
-
function ok(msg) {
|
|
58
|
-
|
|
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) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 {
|
|
77
|
-
|
|
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 {
|
|
82
|
-
|
|
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 {
|
|
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)
|
|
103
|
-
|
|
104
|
-
|
|
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 =
|
|
110
|
-
encoding: "utf-8",
|
|
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 {
|
|
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 {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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,29 +190,73 @@ 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 {
|
|
193
|
+
} catch {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
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
|
|
204
|
+
const raw = runQuiet("atlas --version")
|
|
205
|
+
return raw ? raw.replace(/[^0-9.]/g, "") : null
|
|
140
206
|
}
|
|
141
207
|
|
|
142
|
-
|
|
143
|
-
|
|
208
|
+
// Install the Atlas CLI, or bring an existing install up to date. Returns
|
|
209
|
+
// true when a working `atlas` binary is on PATH afterwards.
|
|
210
|
+
async function installOrUpdateAtlas() {
|
|
211
|
+
const current = atlasVersion()
|
|
212
|
+
if (current) {
|
|
213
|
+
const s = spinner("Checking Atlas CLI for updates...")
|
|
214
|
+
const latest = runQuiet("npm view @synsci/atlas version")
|
|
215
|
+
if (!latest || current === latest) {
|
|
216
|
+
s.ok(`atlas ${current} ${DIM}(up to date)${RESET}`)
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
s.update(`Upgrading Atlas CLI ${current} → ${latest}...`)
|
|
220
|
+
if (runQuiet("npm i -g @synsci/atlas@latest") !== null) {
|
|
221
|
+
s.ok(`Upgraded Atlas CLI to ${latest}`)
|
|
222
|
+
} else {
|
|
223
|
+
s.warn(`Atlas CLI upgrade failed, continuing with ${current}`)
|
|
224
|
+
}
|
|
225
|
+
return true
|
|
226
|
+
}
|
|
227
|
+
const s = spinner("Installing Atlas CLI...")
|
|
228
|
+
if (runQuiet("npm i -g @synsci/atlas@latest") !== null && atlasVersion()) {
|
|
229
|
+
s.ok("Installed Atlas CLI")
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
s.warn(`Atlas CLI install failed, you can retry later: ${CYAN}npm i -g @synsci/atlas${RESET}`)
|
|
233
|
+
return false
|
|
144
234
|
}
|
|
145
235
|
|
|
146
236
|
async function ask(question) {
|
|
147
237
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
148
238
|
return new Promise((resolve) => {
|
|
149
|
-
rl.question(question, (answer) => {
|
|
239
|
+
rl.question(question, (answer) => {
|
|
240
|
+
rl.close()
|
|
241
|
+
resolve(answer.trim())
|
|
242
|
+
})
|
|
150
243
|
})
|
|
151
244
|
}
|
|
152
245
|
|
|
153
246
|
async function main() {
|
|
154
247
|
process.on("exit", () => process.stdout.write(SHOW_CURSOR))
|
|
155
|
-
process.on("SIGINT", () => {
|
|
248
|
+
process.on("SIGINT", () => {
|
|
249
|
+
process.stdout.write(SHOW_CURSOR)
|
|
250
|
+
process.exit(130)
|
|
251
|
+
})
|
|
156
252
|
|
|
157
253
|
// --- Logo ---
|
|
158
254
|
console.log()
|
|
159
255
|
for (const line of LOGO) console.log(` ${CYAN}${line}${RESET}`)
|
|
160
256
|
console.log()
|
|
161
|
-
console.log(
|
|
257
|
+
console.log(
|
|
258
|
+
` ${BOLD}Synthetic Sciences${RESET} ${DIM}OpenScience, the open-source AI research workspace · Atlas, the research platform${RESET}`,
|
|
259
|
+
)
|
|
162
260
|
console.log()
|
|
163
261
|
|
|
164
262
|
// --- Step 1: Install or upgrade the OpenScience CLI ---
|
|
@@ -186,7 +284,7 @@ async function main() {
|
|
|
186
284
|
} else {
|
|
187
285
|
s.update(`Upgrading ${current} → ${latest}...`)
|
|
188
286
|
try {
|
|
189
|
-
|
|
287
|
+
execCli(cliPath, ["upgrade"], { stdio: "pipe" })
|
|
190
288
|
s.ok(`Upgraded to ${latest}`)
|
|
191
289
|
} catch {
|
|
192
290
|
s.warn(`Upgrade failed, continuing with ${current}`)
|
|
@@ -214,6 +312,13 @@ async function main() {
|
|
|
214
312
|
if (!cliPath) throw new Error("openscience not on PATH after install")
|
|
215
313
|
s.ok("Installed OpenScience")
|
|
216
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
|
+
}
|
|
217
322
|
// Global npm installs commonly fail on permissions. Fall back to the
|
|
218
323
|
// standalone installer, which lands in ~/.openscience/bin without sudo
|
|
219
324
|
// (resolveCli already checks that location).
|
|
@@ -236,31 +341,56 @@ async function main() {
|
|
|
236
341
|
console.log()
|
|
237
342
|
console.log(` ${BOLD}How do you want to run it?${RESET}`)
|
|
238
343
|
console.log()
|
|
239
|
-
console.log(
|
|
240
|
-
|
|
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
|
+
)
|
|
241
353
|
console.log()
|
|
242
354
|
|
|
243
|
-
const setup = await ask(` ${DIM}❯${RESET} Choose [1/2]: `)
|
|
355
|
+
const setup = await ask(` ${DIM}❯${RESET} Choose [1/2/3]: `)
|
|
244
356
|
console.log()
|
|
245
357
|
|
|
246
|
-
if (setup === "
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
358
|
+
if (setup === "3") {
|
|
359
|
+
// Atlas-CLI-only path: install/upgrade, verify, hand off to `atlas`.
|
|
360
|
+
// No workspace launch — this option exists for people who want the
|
|
361
|
+
// research CLI on a box without opening the browser app.
|
|
362
|
+
const installed = await installOrUpdateAtlas()
|
|
363
|
+
if (!installed) process.exit(1)
|
|
364
|
+
const s = spinner("Running atlas doctor...")
|
|
365
|
+
if (runQuiet("atlas doctor") !== null) {
|
|
366
|
+
s.ok("atlas doctor passed")
|
|
367
|
+
} else {
|
|
368
|
+
s.warn(`atlas doctor reported issues — run ${CYAN}atlas doctor${RESET} for details`)
|
|
369
|
+
}
|
|
370
|
+
if (isConnected()) {
|
|
371
|
+
ok("Connected to Atlas")
|
|
255
372
|
} else {
|
|
256
|
-
|
|
373
|
+
console.log()
|
|
374
|
+
console.log(
|
|
375
|
+
` ${DIM}Connect your Atlas account for managed credentials:${RESET} ${CYAN}openscience connect login${RESET}`,
|
|
376
|
+
)
|
|
257
377
|
}
|
|
378
|
+
console.log()
|
|
379
|
+
console.log(` ${BOLD}Next steps${RESET}`)
|
|
380
|
+
console.log(` ${CYAN}atlas --help${RESET} ${DIM}see everything the CLI can do${RESET}`)
|
|
381
|
+
console.log(` ${CYAN}atlas doctor${RESET} ${DIM}check credentials and environment${RESET}`)
|
|
382
|
+
console.log()
|
|
383
|
+
process.exit(0)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (setup === "2") {
|
|
387
|
+
await installOrUpdateAtlas()
|
|
258
388
|
if (isConnected()) {
|
|
259
389
|
ok("Connected to Atlas")
|
|
260
390
|
} else {
|
|
261
391
|
console.log()
|
|
262
392
|
try {
|
|
263
|
-
|
|
393
|
+
execCli(cliPath, ["connect", "login"], { stdio: "inherit" })
|
|
264
394
|
} catch {}
|
|
265
395
|
}
|
|
266
396
|
} else {
|
|
@@ -273,7 +403,10 @@ async function main() {
|
|
|
273
403
|
console.log(` ${DIM}Opening the workspace in your browser…${RESET}`)
|
|
274
404
|
console.log()
|
|
275
405
|
|
|
276
|
-
const
|
|
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" })
|
|
277
410
|
child.on("close", (code) => process.exit(code ?? 0))
|
|
278
411
|
}
|
|
279
412
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synsci",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
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
|
+
}
|