@aipoch/open-science 1.0.2 → 1.0.3

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/CLI.md CHANGED
@@ -606,6 +606,43 @@ turns only and is rejected while root-agent, subagent, or Notebook work is activ
606
606
  does not block the update. Stale revisions fail with `session_revision_conflict`; invalid or
607
607
  unavailable references fail with `invalid_configuration`.
608
608
 
609
+ ## Session package export and import
610
+
611
+ Export a Session to a `.science` package at an explicit path. The Session must be idle:
612
+
613
+ ```bash
614
+ open-science session export <session-id> --project <id-or-name> --output ~/backups/session.science
615
+ ```
616
+
617
+ `--project` resolves a Project by id or name and selects which Session to export. `--output` must
618
+ be an absolute or relative path ending in `.science`; when it is omitted the command fails instead
619
+ of opening a native save dialog, because the CLI runs without a desktop session. The command
620
+ returns the saved status and written path as JSON with `--json`.
621
+
622
+ Both package commands allow up to one hour by default. Use `--timeout-ms <milliseconds>`
623
+ to adjust the request deadline for large packages or slower disks.
624
+
625
+ Import a `.science` package. Import is non-interactive by design and requires an explicit
626
+ confirmation flag:
627
+
628
+ ```bash
629
+ open-science session import ~/backups/session.science --new-project "Restored session" --non-interactive
630
+ open-science session import ~/backups/session.science --project <id-or-name> --non-interactive
631
+ ```
632
+
633
+ `--non-interactive` confirms the import preview on the caller's behalf; without it the command
634
+ fails rather than waiting for an approval that a headless caller can never supply. Choose exactly
635
+ one destination: `--project` imports into an existing Project, `--new-project` creates one with
636
+ the given name, and omitting both uses the package's own Project name in a new Project. The
637
+ package file is read directly from disk, so no upload or drag-and-drop step is involved.
638
+
639
+ Both commands dispatch the same `sessions:export-package`, `sessions:import-package`, and
640
+ `sessions:package-operation` application commands the desktop app uses, over the authenticated
641
+ loopback HTTP channel. They are unavailable to remote Web sessions by design: the channel is
642
+ installed as a rejecting stub for remote callers, and the handler additionally requires a local
643
+ caller, so arbitrary-path file read and write stay confined to loopback callers that hold the
644
+ service token.
645
+
609
646
  ## Agent routing settings
610
647
 
611
648
  Inspect or atomically update the global Agent framework plus Reviewer and Subagent model routing:
@@ -36,6 +36,8 @@ export function checkBrowserImportAvailability(
36
36
  export type BrowserImportTransfer = {
37
37
  /** Contains a secret; never log or persist it. Browser policy may require a fresh user click. */
38
38
  readonly url: string
39
+ /** Abort requests and timers, invalidate this transfer, and best-effort cancel the receiver. */
40
+ cancel(): Promise<void>
39
41
  /** Acknowledges receipt only, independently of destination selection and final import consent.
40
42
  * Authenticated desktop cancellation throws code 'cancelled', terminal for this transfer.
41
43
  * Keep the Blob and explicitly prepare a new transfer; never automatically retry cancellation. */
@@ -46,16 +48,20 @@ export type BrowserImportTransfer = {
46
48
  onPhase?: (phase: BrowserImportPhase) => void
47
49
  /** Acknowledged bytes only; first callback follows the first successful chunk acknowledgment. */
48
50
  onProgress?: (progress: BrowserImportProgress) => void
49
- /** Default attempts preserves the 1–5 attempt limit. wait-for-launch retries until the
51
+ /** Default attempts preserves the 1–30 attempt limit. wait-for-launch retries until the
50
52
  * overall deadline or cancellation; it observes only this transfer's authenticated handshake. */
51
53
  readinessMode?: BrowserImportReadinessMode
52
54
  /** Readiness only; defaults to 120,000 ms. Capped at 120,000 ms in wait-for-launch,
53
- * 300,000 ms in attempts mode. Timeout permits another send() on the same live transfer. */
54
- readyTimeoutMs?: number
55
- /** 1–5 attempts with 1,000 ms gaps; defaults to 5. Ignored in wait-for-launch mode. */
55
+ * 300,000 ms in attempts mode. Set null only in attempts mode to stop by attempt count
56
+ * without an overall deadline (including across page suspension). Per-request timeouts
57
+ * still apply. Timeout/exhaustion permits another send() on the same live transfer. */
58
+ readyTimeoutMs?: number | null
59
+ /** 1–30 attempts with 1,000 ms gaps; defaults to 5. Ignored in wait-for-launch mode. */
56
60
  readinessAttempts?: number
61
+ /** Delay after each failed attempt; defaults to 1,000 ms. Range: 0–30,000 ms. */
62
+ readinessIntervalMs?: number
57
63
  /** Includes response body reads; defaults to 2,000 ms. Accepts up to 30,000 ms,
58
- * clamped to 2,000 ms in wait-for-launch mode. Failed attempts have 1,000 ms gaps. */
64
+ * clamped to 2,000 ms in wait-for-launch mode. Failed attempts use readinessIntervalMs. */
59
65
  attemptTimeoutMs?: number
60
66
  }): Promise<{ received: true }>
61
67
  }
@@ -178,9 +178,33 @@ export function prepareBrowserImport(file, options = {}) {
178
178
  let sending = false
179
179
  let received = false
180
180
  let cancelled
181
+ const lifetime = new AbortController()
182
+ let cancellation
183
+ // Cancel this secret-bound receiver even while readiness is still pending.
184
+ const cancel = () => {
185
+ if (received) return Promise.resolve()
186
+ if (cancellation) return cancellation
187
+ cancelled = new BrowserImportError('cancelled', 'The transfer was cancelled.')
188
+ lifetime.abort(cancelled)
189
+ cancellation = bounded(undefined, 2000, async (signal) => {
190
+ const headers = await authorize('DELETE', '')
191
+ signal.throwIfAborted()
192
+ await fetch(base, {
193
+ method: 'DELETE',
194
+ mode: 'cors',
195
+ credentials: 'omit',
196
+ redirect: 'error',
197
+ headers,
198
+ signal,
199
+ targetAddressSpace: 'loopback'
200
+ })
201
+ }).catch(() => undefined)
202
+ return cancellation
203
+ }
181
204
 
182
205
  return Object.freeze({
183
206
  url,
207
+ cancel,
184
208
  async send({
185
209
  signal,
186
210
  onProgress,
@@ -188,18 +212,23 @@ export function prepareBrowserImport(file, options = {}) {
188
212
  readinessMode = 'attempts',
189
213
  readyTimeoutMs = 120_000,
190
214
  readinessAttempts = 5,
215
+ readinessIntervalMs = 1000,
191
216
  attemptTimeoutMs = 2000
192
217
  } = {}) {
193
218
  if (!['attempts', 'wait-for-launch'].includes(readinessMode))
194
219
  throw new Error('Invalid readiness mode.')
195
220
  const waitingForLaunch = readinessMode === 'wait-for-launch'
196
- if (!Number.isFinite(readyTimeoutMs) || readyTimeoutMs <= 0)
221
+ const countOnly = !waitingForLaunch && readyTimeoutMs === null
222
+ if (!countOnly && (!Number.isFinite(readyTimeoutMs) || readyTimeoutMs <= 0))
197
223
  throw new Error('Invalid receiver timeout.')
198
224
  if (
199
225
  (!waitingForLaunch &&
200
226
  (!Number.isInteger(readinessAttempts) ||
201
227
  readinessAttempts < 1 ||
202
- readinessAttempts > 5)) ||
228
+ readinessAttempts > 30)) ||
229
+ !Number.isFinite(readinessIntervalMs) ||
230
+ readinessIntervalMs < 0 ||
231
+ readinessIntervalMs > 30_000 ||
203
232
  !Number.isFinite(attemptTimeoutMs) ||
204
233
  attemptTimeoutMs <= 0 ||
205
234
  attemptTimeoutMs > 30_000
@@ -208,49 +237,54 @@ export function prepareBrowserImport(file, options = {}) {
208
237
  if (sending) throw new Error('This browser transfer is already running.')
209
238
  if (cancelled) throw cancelled
210
239
  if (received) return { received: true }
211
- signal?.throwIfAborted()
240
+ signal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal
241
+ signal.throwIfAborted()
212
242
  sending = true
213
243
  let verified = false
214
- const request = async (path, init = {}) => {
215
- const response = await fetch(base + path, {
216
- mode: 'cors',
217
- credentials: 'omit',
218
- cache: 'no-store',
219
- redirect: 'error',
220
- targetAddressSpace: 'loopback',
221
- ...init,
222
- signal: AbortSignal.any(
223
- [signal, init.signal ?? AbortSignal.timeout(30_000)].filter(Boolean)
224
- )
225
- })
226
- if (response.status === 410) {
227
- const value = await response.json().catch(() => {
228
- throw new BrowserImportError(
229
- 'incompatible',
230
- 'Could not verify the local receiver identity.'
231
- )
244
+ const request = async (path, init = {}) =>
245
+ bounded(signal, 30_000, async (requestSignal) => {
246
+ const response = await fetch(base + path, {
247
+ mode: 'cors',
248
+ credentials: 'omit',
249
+ cache: 'no-store',
250
+ redirect: 'error',
251
+ targetAddressSpace: 'loopback',
252
+ ...init,
253
+ signal: AbortSignal.any([requestSignal, init.signal].filter(Boolean))
232
254
  })
233
- if (value?.code !== 'cancelled' || value.proof !== (await sign('cancelled')))
234
- throw new BrowserImportError(
235
- 'incompatible',
236
- 'Could not verify the local receiver identity.'
255
+ const value = await response.json().catch((error) => {
256
+ if (response.status === 410) {
257
+ throw new BrowserImportError(
258
+ 'incompatible',
259
+ 'Could not verify the local receiver identity.'
260
+ )
261
+ }
262
+ throw error
263
+ })
264
+ if (response.status === 410) {
265
+ if (value?.code !== 'cancelled' || value.proof !== (await sign('cancelled')))
266
+ throw new BrowserImportError(
267
+ 'incompatible',
268
+ 'Could not verify the local receiver identity.'
269
+ )
270
+ cancelled = new BrowserImportError(
271
+ 'cancelled',
272
+ 'The transfer was cancelled in Open-Science. Prepare a new transfer to try again.'
237
273
  )
238
- cancelled = new BrowserImportError(
239
- 'cancelled',
240
- 'The transfer was cancelled in Open-Science. Prepare a new transfer to try again.'
241
- )
242
- throw cancelled
243
- }
244
- return response
245
- }
274
+ throw cancelled
275
+ }
276
+ return { ok: response.ok, status: response.status, value }
277
+ })
246
278
  try {
247
279
  onPhase?.('waiting-for-receiver')
248
- // Browser protocol prompts have no observable completion event. Only this secret's proof
249
- // ends launch waiting; the bounded deadline covers requests, body reads and retry gaps.
250
- const budget = Math.min(readyTimeoutMs, waitingForLaunch ? 120_000 : 300_000)
280
+ // Count-only polling survives page suspension without consuming an overall deadline.
281
+ // Numeric/default budgets retain the legacy deadline across requests, bodies and gaps.
282
+ const budget = countOnly
283
+ ? null
284
+ : Math.min(readyTimeoutMs, waitingForLaunch ? 120_000 : 300_000)
251
285
  const attemptBudget = waitingForLaunch ? Math.min(attemptTimeoutMs, 2000) : attemptTimeoutMs
252
- const deadline = Date.now() + budget
253
- const ready = await bounded(signal, budget, async (readySignal) => {
286
+ const deadline = budget === null ? null : Date.now() + budget
287
+ const poll = async (readySignal) => {
254
288
  for (let attempt = 0; waitingForLaunch || attempt < readinessAttempts; attempt++) {
255
289
  readySignal.throwIfAborted()
256
290
  try {
@@ -259,7 +293,7 @@ export function prepareBrowserImport(file, options = {}) {
259
293
  const response = await request(`/ready?challenge=${challenge}`, {
260
294
  signal: attemptSignal
261
295
  })
262
- const value = await response.json()
296
+ const value = response.value
263
297
  if (
264
298
  !value ||
265
299
  typeof value !== 'object' ||
@@ -277,7 +311,7 @@ export function prepareBrowserImport(file, options = {}) {
277
311
  `open-science-import-v1:${challenge}:${value.offset}:${value.accepted ? 1 : 0}`
278
312
  )
279
313
  attemptSignal.throwIfAborted()
280
- if (Date.now() >= deadline)
314
+ if (deadline !== null && Date.now() >= deadline)
281
315
  throw new BrowserImportError(
282
316
  'timeout',
283
317
  'Open-Science did not become ready in time.'
@@ -297,14 +331,16 @@ export function prepareBrowserImport(file, options = {}) {
297
331
  'incompatible',
298
332
  'Could not verify the local receiver identity.'
299
333
  )
300
- if (waitingForLaunch || attempt + 1 < readinessAttempts) await wait(1000, readySignal)
334
+ if (waitingForLaunch || attempt + 1 < readinessAttempts)
335
+ await wait(readinessIntervalMs, readySignal)
301
336
  }
302
337
  }
303
338
  throw new BrowserImportError(
304
339
  'readiness-exhausted',
305
340
  'Open-Science did not become ready. Open the app, allow local network access, and retry.'
306
341
  )
307
- })
342
+ }
343
+ const ready = budget === null ? await poll(signal) : await bounded(signal, budget, poll)
308
344
  verified = true
309
345
  if (
310
346
  !Number.isSafeInteger(ready.offset) ||
@@ -338,7 +374,7 @@ export function prepareBrowserImport(file, options = {}) {
338
374
  }
339
375
  })
340
376
  if (!chunk.ok) throw new Error(`Local package transfer failed (${chunk.status}).`)
341
- const result = await chunk.json()
377
+ const result = chunk.value
342
378
  if (
343
379
  result.offset !== offset + bytes.byteLength ||
344
380
  result.proof !== (await sign(`offset:${result.offset}`))
@@ -351,7 +387,7 @@ export function prepareBrowserImport(file, options = {}) {
351
387
  method: 'POST',
352
388
  headers: await authorize('POST', '/complete')
353
389
  })
354
- const receipt = await complete.json()
390
+ const receipt = complete.value
355
391
  if (
356
392
  complete.status !== 202 ||
357
393
  receipt.received !== true ||
@@ -362,17 +398,7 @@ export function prepareBrowserImport(file, options = {}) {
362
398
  received = true
363
399
  return { received: true }
364
400
  } catch (error) {
365
- if (verified && signal?.aborted) {
366
- await fetch(base, {
367
- method: 'DELETE',
368
- mode: 'cors',
369
- credentials: 'omit',
370
- redirect: 'error',
371
- headers: await authorize('DELETE', ''),
372
- signal: AbortSignal.timeout(2000),
373
- targetAddressSpace: 'loopback'
374
- }).catch(() => undefined)
375
- }
401
+ if (signal?.aborted) await cancel()
376
402
  signal?.throwIfAborted()
377
403
  if (error.code === 'cancelled' || error.code === 'incompatible') throw error
378
404
  if (verified)
package/cli.mjs CHANGED
@@ -62,6 +62,8 @@ Commands:
62
62
  session status <session-id>
63
63
  session config show <session-id>
64
64
  session config update <session-id> --revision <number> [session options]
65
+ session export <session-id> --project <id-or-name> --output <path>
66
+ session import <path> [--project <id-or-name> | --new-project <name>] --non-interactive
65
67
  settings agent-routing show
66
68
  settings agent-routing update [routing options]
67
69
  plan show <session-id>
@@ -109,9 +111,11 @@ Options:
109
111
  --wait Wait for the run to finish
110
112
  --return-on-attention With --wait, return when the Plan needs approval
111
113
  --timeout-ms <ms> Stop waiting after this many milliseconds
114
+ --output <path> Destination path (session export, artifacts download)
115
+ --new-project <name> Import into a new Project with this name (session import)
116
+ --non-interactive Import without an interactive preview confirmation (session import)
112
117
  --cancel-on-timeout Cancel the server run when --timeout-ms expires
113
118
  --jsonl With run --wait, stream one machine-readable event per line
114
- --output <path> Artifact download destination
115
119
  --credential-store <os|file> Settings credential storage (Linux headless; start only)
116
120
  --no-open Do not open the browser after start
117
121
  --no-sandbox Disable Chromium's process sandbox (security risk; start only)
@@ -160,7 +164,8 @@ const VALUE_OPTIONS = {
160
164
  '--description': 'description',
161
165
  '--agent-context': 'agentContext',
162
166
  '--agent-context-file': 'agentContextFile',
163
- '--output': 'output'
167
+ '--output': 'output',
168
+ '--new-project': 'newProject'
164
169
  }
165
170
 
166
171
  const TASK_COMMANDS = new Set([
@@ -228,6 +233,8 @@ const POSITIONAL_LIMITS = new Map([
228
233
  ['run cancel', 1],
229
234
  ['session status', 1],
230
235
  ['session config', 2],
236
+ ['session export', 1],
237
+ ['session import', 1],
231
238
  ['settings agent-routing', 1],
232
239
  ['plan show', 1],
233
240
  ['plan approve', 1],
@@ -309,6 +316,7 @@ export const parseCliArgs = (argv) => {
309
316
  else if (arg === '--wait') options.wait = true
310
317
  else if (arg === '--return-on-attention') options.returnOnAttention = true
311
318
  else if (arg === '--plan-first') options.planFirst = true
319
+ else if (arg === '--non-interactive') options.nonInteractive = true
312
320
  else if (arg === '--provider-default-model') options.providerDefaultModel = true
313
321
  else if (arg === '--clear-compute-hosts') options.clearComputeHosts = true
314
322
  else if (arg === '--memory') {
@@ -464,8 +472,18 @@ export const parseCliArgs = (argv) => {
464
472
  if (options.jsonl && (command !== 'run' || subcommand || !options.wait)) {
465
473
  throw new CliUsageError('--jsonl requires run --wait.')
466
474
  }
467
- if (options.timeoutMs !== undefined && (command !== 'run' || subcommand || !options.wait)) {
468
- throw new CliUsageError('--timeout-ms requires run --wait.')
475
+ const isPackageTransfer = command === 'session' && ['export', 'import'].includes(subcommand)
476
+ if (
477
+ options.timeoutMs !== undefined &&
478
+ !isPackageTransfer &&
479
+ (command !== 'run' || subcommand || !options.wait)
480
+ ) {
481
+ throw new CliUsageError('--timeout-ms requires run --wait or session export/import.')
482
+ }
483
+ if (command === 'session' && subcommand === 'import') {
484
+ if (!options.nonInteractive) throw new CliUsageError('--non-interactive is required.')
485
+ if (options.project && options.newProject)
486
+ throw new CliUsageError('Use only one of --project and --new-project.')
469
487
  }
470
488
  if (options.returnOnAttention && (command !== 'run' || subcommand || !options.wait)) {
471
489
  throw new CliUsageError('--return-on-attention requires run --wait.')
@@ -1543,6 +1561,40 @@ export const runTaskCommand = async (parsed, dependencies = {}) => {
1543
1561
  )
1544
1562
  return
1545
1563
  }
1564
+ if (command === 'session' && subcommand === 'export') {
1565
+ const sessionId = positionals[0]
1566
+ if (!sessionId) throw new CliUsageError('Session id is required.')
1567
+ if (!options.project) throw new CliUsageError('--project is required.')
1568
+ if (!options.output) throw new CliUsageError('--output is required.')
1569
+ const projectId = await resolveCliProjectId(client, options.project)
1570
+ outputValue(
1571
+ await client.exportSessionPackage(projectId, sessionId, resolve(options.output), {
1572
+ timeoutMs: options.timeoutMs
1573
+ }),
1574
+ options,
1575
+ deps
1576
+ )
1577
+ return
1578
+ }
1579
+ if (command === 'session' && subcommand === 'import') {
1580
+ const packagePath = positionals[0]
1581
+ if (!packagePath) throw new CliUsageError('Session package path is required.')
1582
+ const resolvedPath = resolve(packagePath)
1583
+ const target = {}
1584
+ if (options.project) target.projectId = await resolveCliProjectId(client, options.project)
1585
+ else if (options.newProject) target.projectName = options.newProject
1586
+ outputValue(
1587
+ await client.importSessionPackage(
1588
+ target,
1589
+ resolvedPath,
1590
+ { nonInteractive: true },
1591
+ { timeoutMs: options.timeoutMs }
1592
+ ),
1593
+ options,
1594
+ deps
1595
+ )
1596
+ return
1597
+ }
1546
1598
  if (command === 'settings' && subcommand === 'agent-routing') {
1547
1599
  const action = positionals[0]
1548
1600
  if (action === 'show') {
package/index.d.ts CHANGED
@@ -99,6 +99,7 @@ export type ConnectorsSnapshot = {
99
99
  ncbi: NcbiCredentialsView
100
100
  // Optional only for compatibility with an older main process during local development.
101
101
  openAlex?: OpenAlexCredentialView
102
+ semanticScholar?: { hasApiKey: boolean }
102
103
  }
103
104
 
104
105
  export type DeviceCredentialKind = 'api_key' | 'token' | 'oauth'
package/index.mjs CHANGED
@@ -6,6 +6,7 @@ const defaultSleep = (milliseconds) =>
6
6
  new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds))
7
7
 
8
8
  const DEFAULT_REQUEST_TIMEOUT_MS = 30_000
9
+ const PACKAGE_REQUEST_TIMEOUT_MS = 60 * 60 * 1_000
9
10
  const DEFAULT_EVENT_IDLE_TIMEOUT_MS = 30_000
10
11
  const EVENT_RECONNECT_DELAY_MS = 100
11
12
  const MAX_BUFFERED_EVENTS = 1_024
@@ -308,6 +309,54 @@ export class OpenScienceClient {
308
309
  return this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/config`, options)
309
310
  }
310
311
 
312
+ // Web RPC seam: authenticated loopback dispatch of one application command channel.
313
+ // The CLI reaches headless-only commands (Session package export/import) this way.
314
+ async callRpc(channel, args, options = {}) {
315
+ return withRequestTimeout(
316
+ {
317
+ defaultTimeoutMs: this.requestTimeoutMs,
318
+ signal: options.signal,
319
+ timeoutMs: options.timeoutMs
320
+ },
321
+ async (signal) => {
322
+ const response = await this.fetch(`${this.baseUrl}/rpc/${encodeURIComponent(channel)}`, {
323
+ method: 'POST',
324
+ headers: {
325
+ authorization: `Bearer ${this.token}`,
326
+ accept: 'application/json',
327
+ 'content-type': 'application/json'
328
+ },
329
+ body: JSON.stringify({ protocolVersion: 1, args }),
330
+ signal
331
+ })
332
+ const payload = await response.json().catch(() => undefined)
333
+ if (!response.ok || payload?.ok !== true) {
334
+ throw new OpenScienceApiError(
335
+ payload?.error?.message ?? `Open-Science Web RPC request failed (${response.status}).`,
336
+ { code: payload?.error?.code, status: response.status }
337
+ )
338
+ }
339
+ return payload.result
340
+ }
341
+ )
342
+ }
343
+
344
+ exportSessionPackage(projectId, sessionId, destinationPath, options) {
345
+ return this.callRpc('sessions:export-package', [{ projectId, sessionId }, destinationPath], {
346
+ ...options,
347
+ timeoutMs: options?.timeoutMs ?? PACKAGE_REQUEST_TIMEOUT_MS
348
+ })
349
+ }
350
+
351
+ importSessionPackage(target, sourcePath, options, rpcOptions) {
352
+ if (options?.nonInteractive !== true)
353
+ throw new TypeError('Non-interactive Session import requires explicit confirmation.')
354
+ return this.callRpc('sessions:import-package', [target, sourcePath, { nonInteractive: true }], {
355
+ ...rpcOptions,
356
+ timeoutMs: rpcOptions?.timeoutMs ?? PACKAGE_REQUEST_TIMEOUT_MS
357
+ })
358
+ }
359
+
311
360
  updateSessionConfiguration(sessionId, request, options) {
312
361
  return this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/config`, {
313
362
  ...options,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipoch/open-science",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Node.js SDK and CLI for the Open-Science local daemon",
5
5
  "type": "module",
6
6
  "exports": {
@@ -38,10 +38,10 @@
38
38
  "access": "public"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@aipoch/open-science-darwin-arm64": "1.0.2",
42
- "@aipoch/open-science-darwin-x64": "1.0.2",
43
- "@aipoch/open-science-linux-x64-gnu": "1.0.2",
44
- "@aipoch/open-science-linux-arm64-gnu": "1.0.2",
45
- "@aipoch/open-science-win32-x64": "1.0.2"
41
+ "@aipoch/open-science-darwin-arm64": "1.0.3",
42
+ "@aipoch/open-science-darwin-x64": "1.0.3",
43
+ "@aipoch/open-science-linux-x64-gnu": "1.0.3",
44
+ "@aipoch/open-science-linux-arm64-gnu": "1.0.3",
45
+ "@aipoch/open-science-win32-x64": "1.0.3"
46
46
  }
47
47
  }