@phpsandbox/sdk 0.1.1 → 0.1.4

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.
@@ -48,18 +48,18 @@ API notebook creates and forks are ephemeral by default. Use `persistent: true`
48
48
  ## 3. Write and Read Files
49
49
 
50
50
  ```ts
51
- await notebook.file.write('hello.php', '<?php echo "Hello";');
51
+ await notebook.files.write('hello.php', '<?php echo "Hello";');
52
52
 
53
- const data = await notebook.file.readFile('hello.php');
53
+ const data = await notebook.files.read('hello.php');
54
54
  const text = new TextDecoder().decode(data);
55
55
 
56
56
  console.log(text);
57
57
  ```
58
58
 
59
- If you need binary-safe writes, use `writeFile`:
59
+ The same `write()` method accepts text, bytes, readable streams, and async iterables:
60
60
 
61
61
  ```ts
62
- await notebook.file.writeFile('binary.dat', new Uint8Array([1, 2, 3]), {
62
+ await notebook.files.write('binary.dat', new Uint8Array([1, 2, 3]), {
63
63
  create: true,
64
64
  overwrite: true,
65
65
  unlock: false,
@@ -72,7 +72,7 @@ await notebook.file.writeFile('binary.dat', new Uint8Array([1, 2, 3]), {
72
72
  ### Terminal (stream output)
73
73
 
74
74
  ```ts
75
- const task = await notebook.terminal.spawn('php', ['hello.php']);
75
+ const task = await notebook.terminals.create({ command: 'php', args: ['hello.php'] });
76
76
  const reader = task.output.getReader();
77
77
 
78
78
  try {
@@ -85,14 +85,14 @@ try {
85
85
  reader.releaseLock();
86
86
  }
87
87
 
88
- const exitCode = await task.exit;
88
+ const exitCode = await task.wait();
89
89
  console.log(`\nExit code: ${exitCode}`);
90
90
  ```
91
91
 
92
92
  ### Shell (single command result)
93
93
 
94
94
  ```ts
95
- const result = await notebook.run('php -v', { tty: false }).exit;
95
+ const result = await notebook.exec('php -v', { tty: false });
96
96
  result.throw();
97
97
  console.log(result.stdout);
98
98
  ```
@@ -100,14 +100,14 @@ console.log(result.stdout);
100
100
  ## 5. Search and Watch Files
101
101
 
102
102
  ```ts
103
- const matches = await notebook.file.find('*.php', {
103
+ const matches = await notebook.files.find('*.php', {
104
104
  includes: ['app/**'],
105
105
  excludes: ['vendor/**'],
106
106
  });
107
107
 
108
108
  console.log(matches.map((m) => m.path));
109
109
 
110
- const watcher = notebook.file.watch(
110
+ const watcher = notebook.files.watch(
111
111
  '/app',
112
112
  {
113
113
  recursive: true,
@@ -125,8 +125,8 @@ watcher.dispose();
125
125
  ## 6. Use Composer and Git
126
126
 
127
127
  ```ts
128
- await notebook.composer.install({ noInteraction: true });
129
- await notebook.composer.require({ packages: ['monolog/monolog'] });
128
+ (await notebook.composer.install().wait()).throw();
129
+ (await notebook.composer.require({ packages: ['monolog/monolog'] }).wait()).throw();
130
130
 
131
131
  await notebook.git.checkpoint('Jane Doe <jane@example.com>', 'Initial checkpoint');
132
132
  ```
@@ -142,8 +142,8 @@ notebook.dispose();
142
142
  ## Common Pitfalls
143
143
 
144
144
  - `get()` notebooks must call `await notebook.ready()` before tool calls.
145
- - `file.watch()` requires an `excludes` array.
146
- - Await `process.exit`, then call `result.throw()` to fail on non-zero exit codes.
145
+ - `files.watch()` requires an `excludes` array.
146
+ - Use `notebook.exec()` for one-shot commands; for streaming shell handles, await `process.wait()` and call `result.throw()` to fail on non-zero exit codes.
147
147
 
148
148
  ## Next Reading
149
149
 
package/examples/basic.ts CHANGED
@@ -16,7 +16,7 @@ console.log('\n');
16
16
 
17
17
  console.log('====== Read routes/web.php in the notebook ======');
18
18
  const decoder = new TextDecoder();
19
- const content = await notebook.file.readFile('routes/web.php');
19
+ const content = await notebook.files.read('routes/web.php');
20
20
  console.log(decoder.decode(content));
21
21
 
22
22
  // Always close the notebook when done to clean up the connection.
@@ -54,10 +54,7 @@ async function main(): Promise<void> {
54
54
 
55
55
  await fork.ready();
56
56
  const editContent = buildEditContent(base.data.id, forkNotebookId, options);
57
- const written = await fork.file.write(options.editPath, editContent);
58
- if (!written) {
59
- throw new Error(`fs.write returned false for ${options.editPath}.`);
60
- }
57
+ await fork.files.write(options.editPath, editContent);
61
58
  console.log(`Edited fork file: ${options.editPath}`);
62
59
 
63
60
  const forkCheckpoint = await fork.git.checkpoint(options.author, `Smoke edit in fork ${forkNotebookId}`, options.ref, false);
@@ -389,11 +389,10 @@ async function runScenario(
389
389
  let notebook: NotebookInstance | null = null;
390
390
  let previewUrl: string | undefined;
391
391
  let logsCaptured = 0;
392
- let logListener: Disposable | null = null;
392
+ let logReader: ReadableStreamDefaultReader<import('../src/log.js').RuntimeLogEntry> | null = null;
393
393
  let initTrace: InitTrace | undefined;
394
394
  let readyResult: NotebookInitSuccessResult | undefined;
395
395
  let initTraceDisposables: Disposable[] = [];
396
- let telemetryEnabled = false;
397
396
  let redisRunning = false;
398
397
 
399
398
  const routeSlug = id.toLowerCase();
@@ -560,12 +559,19 @@ async function runScenario(
560
559
  }
561
560
 
562
561
  if (options.mode !== 'init') {
563
- logListener = notebook.log.stream((line) => {
564
- if (line.trim().length > 0) {
565
- logsCaptured += 1;
566
- ui.updateScenarioLogs(id, logsCaptured);
562
+ logReader = notebook.logs.follow().getReader();
563
+ void (async () => {
564
+ while (logReader) {
565
+ const entry = await logReader.read();
566
+ if (entry.done) {
567
+ return;
568
+ }
569
+ if (entry.value.message.trim().length > 0) {
570
+ logsCaptured += 1;
571
+ ui.updateScenarioLogs(id, logsCaptured);
572
+ }
567
573
  }
568
- });
574
+ })().catch(() => {});
569
575
 
570
576
  await runCheckedStep('notebook.ping', () => notebook!.ping(), { continueOnError: false });
571
577
 
@@ -599,8 +605,8 @@ async function runScenario(
599
605
  });
600
606
 
601
607
  await runCheckedStep('container.state', async () => {
602
- const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.container.openedPorts()]);
603
- const portInfo = assertPortInfoList(ports, 'container.openedPorts');
608
+ const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.ports.list()]);
609
+ const portInfo = assertPortInfoList(ports, 'ports.list');
604
610
  return `${state.state} (${describePorts(portInfo)})`;
605
611
  });
606
612
 
@@ -609,11 +615,11 @@ async function runScenario(
609
615
  'container.restart',
610
616
  async () => {
611
617
  await notebook!.container.start();
612
- const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.container.openedPorts()]);
618
+ const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.ports.list()]);
613
619
  if (state.state !== 'running') {
614
620
  throw new Error(`Expected running container after restart, got ${state.state}`);
615
621
  }
616
- return `${state.state} (${describePorts(assertPortInfoList(ports, 'container.openedPorts after restart'))})`;
622
+ return `${state.state} (${describePorts(assertPortInfoList(ports, 'ports.list after restart'))})`;
617
623
  },
618
624
  { timeoutMs: options.stepTimeoutMs * 3, continueOnError: false }
619
625
  );
@@ -626,29 +632,20 @@ async function runScenario(
626
632
  async () =>
627
633
  retryWithBackoff(
628
634
  async () => {
629
- const statsPromise = waitForNextEvent<ContainerStats>(
630
- (handler) => notebook!.container.listen('container.stats', handler),
631
- options.telemetryTimeoutMs,
632
- 'container.stats'
633
- );
634
- void statsPromise.catch(() => undefined);
635
- const portsPromise = waitForNextEvent<PortInfo[]>(
636
- (handler) => notebook!.container.listen('container.ports', handler),
637
- options.telemetryTimeoutMs,
638
- 'container.ports'
639
- );
640
- void portsPromise.catch(() => undefined);
641
-
642
- await notebook!.container.enableTelemetry(new Set(['stats', 'ports']));
643
- telemetryEnabled = true;
644
-
645
- return await firstFulfilled([
646
- statsPromise.then(
647
- (stats) =>
648
- `cpu=${stats.cpu.usage}/${stats.cpu.limit} memory=${stats.memory.usage}/${stats.memory.limit}`
649
- ),
650
- portsPromise.then((ports) => describePorts(assertPortInfoList(ports, 'container.ports'))),
651
- ]);
635
+ const statsReader = notebook!.metrics.watch().getReader();
636
+ const portsReader = notebook!.ports.watch().getReader();
637
+ try {
638
+ const statsPromise = withTimeout(statsReader.read(), options.telemetryTimeoutMs, 'metrics.watch')
639
+ .then(({ value }) => {
640
+ if (!value) throw new Error('metrics.watch ended before a sample was received.');
641
+ return `cpu=${value.cpu.usage}/${value.cpu.limit} memory=${value.memory.usage}/${value.memory.limit}`;
642
+ });
643
+ const portsPromise = withTimeout(portsReader.read(), options.telemetryTimeoutMs, 'ports.watch')
644
+ .then(({ value }) => describePorts(assertPortInfoList(value ?? [], 'ports.watch')));
645
+ return await firstFulfilled([statsPromise, portsPromise]);
646
+ } finally {
647
+ await Promise.all([statsReader.cancel(), portsReader.cancel()]);
648
+ }
652
649
  },
653
650
  TELEMETRY_RETRY_DELAYS_MS,
654
651
  shouldRetryTelemetryError
@@ -660,7 +657,7 @@ async function runScenario(
660
657
  'filesystem.watch-and-write',
661
658
  async () => {
662
659
  const changeSeen = createDeferred<FileChange>();
663
- const watch = await notebook!.file.watch(
660
+ const watch = await notebook!.files.watch(
664
661
  'routes',
665
662
  { recursive: true, excludes: ['vendor/**', 'node_modules/**'] },
666
663
  (change) => {
@@ -671,12 +668,9 @@ async function runScenario(
671
668
  );
672
669
 
673
670
  try {
674
- const current = textDecoder.decode(await notebook!.file.readFile(routePath));
671
+ const current = textDecoder.decode(await notebook!.files.read(routePath));
675
672
  const next = appendRouteDefinition(current, id, routeUrlPath);
676
- const written = await notebook!.file.write(routePath, next);
677
- if (!written) {
678
- throw new Error(`fs.write returned false for ${routePath}.`);
679
- }
673
+ await notebook!.files.write(routePath, next);
680
674
 
681
675
  const change = await withTimeout(
682
676
  changeSeen.promise,
@@ -692,14 +686,13 @@ async function runScenario(
692
686
  );
693
687
 
694
688
  await runCheckedStep('filesystem.inspect', async () => {
695
- const [raw, info, stat, directory, tree, found, tail] = await Promise.all([
696
- notebook!.file.readFile(routePath),
697
- notebook!.file.info(routePath),
698
- notebook!.file.stat(routePath),
699
- notebook!.file.readDirectory('routes'),
700
- notebook!.file.tree('routes'),
701
- notebook!.file.find(routeFileName, { includes: ['routes/**'], maxResults: 10 }),
702
- notebook!.file.tail(routePath, 20),
689
+ const [raw, stat, directory, tree, found, tail] = await Promise.all([
690
+ notebook!.files.read(routePath),
691
+ notebook!.files.stat(routePath),
692
+ notebook!.files.list('routes'),
693
+ notebook!.files.tree('routes'),
694
+ notebook!.files.find(routeFileName, { includes: ['routes/**'], maxResults: 10 }),
695
+ notebook!.files.tail(routePath, { lines: 20 }),
703
696
  ]);
704
697
 
705
698
  const text = textDecoder.decode(raw);
@@ -707,7 +700,7 @@ async function runScenario(
707
700
  throw new Error(`Expected ${routeUrlPath} in ${routePath}.`);
708
701
  }
709
702
 
710
- const [, matches] = await notebook!.file.search(
703
+ const [, matches] = await notebook!.files.search(
711
704
  { pattern: routeUrlPath },
712
705
  { includes: [routePath], excludes: [], maxResults: 10 }
713
706
  );
@@ -716,12 +709,12 @@ async function runScenario(
716
709
  throw new Error(`file.search() did not find ${routeUrlPath}.`);
717
710
  }
718
711
 
719
- const exists = await notebook!.file.exists(routePath);
712
+ const exists = await notebook!.files.exists(routePath);
720
713
  if (!exists) {
721
714
  throw new Error(`${routePath} does not exist after write.`);
722
715
  }
723
716
 
724
- return `size=${stat.size} info=${info.language} dir=${directory.length} tree-lines=${tree.split('\n').length} find=${found.length} tail=${tail.length}`;
717
+ return `size=${stat.size} dir=${directory.length} tree-lines=${tree.split('\n').length} find=${found.length} tail=${tail.length}`;
725
718
  });
726
719
 
727
720
  if (options.includePreview) {
@@ -732,7 +725,10 @@ async function runScenario(
732
725
 
733
726
  if (options.includeDownload) {
734
727
  await runCheckedStep('filesystem.download', async () => {
735
- const archive = await notebook!.file.download(undefined, ['vendor/**', 'node_modules/**'], [routePath, 'composer.json']);
728
+ const archive = await notebook!.files.download({
729
+ exclude: ['vendor/**', 'node_modules/**'],
730
+ include: [routePath, 'composer.json'],
731
+ });
736
732
  if (archive.size === 0) {
737
733
  throw new Error('file.download() returned an empty blob.');
738
734
  }
@@ -784,12 +780,12 @@ async function runScenario(
784
780
  );
785
781
 
786
782
  await runCheckedStep(
787
- 'terminal.spawn',
783
+ 'terminals.create',
788
784
  async () => {
789
- const process = await notebook!.terminal.spawn(
790
- 'php',
791
- ['artisan', 'route:list', `--path=${routeUrlPath}`]
792
- );
785
+ const process = await notebook!.terminals.create({
786
+ command: 'php',
787
+ args: ['artisan', 'route:list', `--path=${routeUrlPath}`],
788
+ });
793
789
 
794
790
  try {
795
791
  const outputPromise = readStreamToString(process.output, options.streamTimeoutMs, options.maxStreamChars);
@@ -797,7 +793,7 @@ async function runScenario(
797
793
  const [output, exitCode] = await Promise.all([outputPromise, exitPromise]);
798
794
 
799
795
  if (exitCode !== 0) {
800
- throw new Error(`terminal.spawn() exited with ${exitCode}: ${trimDetail(output)}`);
796
+ throw new Error(`terminals.create() exited with ${exitCode}: ${trimDetail(output)}`);
801
797
  }
802
798
 
803
799
  if (!output.includes(routeUrlPath)) {
@@ -820,7 +816,7 @@ async function runScenario(
820
816
  redisRunning = started.running || started.state === 'running' || started.state === 'starting';
821
817
 
822
818
  const stream = notebook!.services.logs('redis', { follow: true, tail: 25 });
823
- const reader = stream.output.getReader();
819
+ const reader = stream.getReader();
824
820
 
825
821
  try {
826
822
  const [snapshot, chunk] = await Promise.all([
@@ -837,7 +833,6 @@ async function runScenario(
837
833
  } finally {
838
834
  await reader.cancel().catch(() => undefined);
839
835
  reader.releaseLock();
840
- await stream.stop().catch(() => false);
841
836
  await notebook!.services.stop('redis').catch(() => started);
842
837
  redisRunning = false;
843
838
  }
@@ -847,25 +842,20 @@ async function runScenario(
847
842
 
848
843
  await runCheckedStep('composer.metadata', async () => {
849
844
  let composerLogChunks = 0;
850
- const composerLogs = notebook!.composer.stream((line) => {
851
- if (line.trim().length > 0) {
845
+ const dump = notebook!.composer.dumpAutoload();
846
+ for await (const chunk of dump.output) {
847
+ if (chunk.data.byteLength > 0) {
852
848
  composerLogChunks += 1;
853
849
  }
854
- });
855
-
856
- try {
857
- const [packages, dump] = await Promise.all([notebook!.composer.packages(), notebook!.composer.dumpAutoload()]);
858
- if (!dump.success) {
859
- throw new Error(dump.message || 'composer.dumpAutoload() failed.');
860
- }
861
- if (packages.length === 0) {
862
- throw new Error('composer.packages() returned no packages.');
863
- }
850
+ }
864
851
 
865
- return `packages=${packages.length} composer-log-events=${composerLogChunks}`;
866
- } finally {
867
- composerLogs.dispose();
852
+ (await dump.wait()).throw();
853
+ const packages = await notebook!.composer.packages();
854
+ if (packages.length === 0) {
855
+ throw new Error('composer.packages() returned no packages.');
868
856
  }
857
+
858
+ return `packages=${packages.length} composer-log-events=${composerLogChunks}`;
869
859
  }, { timeoutMs: Math.max(options.stepTimeoutMs * 2, 120_000) });
870
860
 
871
861
  await runCheckedStep('git.checkpoint', async () => {
@@ -960,11 +950,8 @@ async function runScenario(
960
950
  await notebook.services.stop('redis').catch(() => undefined);
961
951
  }
962
952
 
963
- if (telemetryEnabled) {
964
- await notebook.container.stopTelemetry().catch(() => undefined);
965
- }
966
-
967
- logListener?.dispose();
953
+ await logReader?.cancel().catch(() => undefined);
954
+ logReader = null;
968
955
  for (const disposable of initTraceDisposables) {
969
956
  disposable.dispose();
970
957
  }
@@ -155,12 +155,12 @@ async function main(): Promise<void> {
155
155
  }, false);
156
156
 
157
157
  await recordStep(steps, 'container.opened-ports', options.stepTimeoutMs, async () => {
158
- const ports = await notebook!.container.openedPorts();
159
- return describePorts(assertPortInfoList(ports, 'container.openedPorts'));
158
+ const ports = await notebook!.ports.list();
159
+ return describePorts(assertPortInfoList(ports, 'ports.list'));
160
160
  }, false);
161
161
 
162
162
  const rootSnapshot = await recordStep(steps, 'filesystem.root', options.stepTimeoutMs, async () => {
163
- const rootEntries = await notebook!.file.readDirectory('/');
163
+ const rootEntries = await notebook!.files.list('/');
164
164
 
165
165
  const preferred = ['composer.json', 'index.php', 'artisan'];
166
166
  const rootNames = new Set(rootEntries.map(([name]) => name));
@@ -174,7 +174,7 @@ async function main(): Promise<void> {
174
174
  const treePath = rootSnapshot.candidate === 'artisan' ? 'routes' : undefined;
175
175
  if (treePath) {
176
176
  await recordStep(steps, 'filesystem.tree', options.stepTimeoutMs, async () => {
177
- const tree = await notebook!.file.tree(treePath);
177
+ const tree = await notebook!.files.tree(treePath);
178
178
  return `path=${treePath} bytes=${tree.length}`;
179
179
  }, false);
180
180
  } else {
@@ -183,8 +183,8 @@ async function main(): Promise<void> {
183
183
 
184
184
  const findSnapshot = await recordStep(steps, 'filesystem.find', options.stepTimeoutMs, async () => {
185
185
  const [composerFiles, indexFiles] = await Promise.all([
186
- notebook!.file.find('composer.json', { maxResults: 10 }),
187
- notebook!.file.find('index.php', { maxResults: 10 }),
186
+ notebook!.files.find('composer.json', { maxResults: 10 }),
187
+ notebook!.files.find('index.php', { maxResults: 10 }),
188
188
  ]);
189
189
  const candidate = [...composerFiles, ...indexFiles].find((file) => typeof file.path === 'string')?.path;
190
190
  return {
@@ -197,15 +197,13 @@ async function main(): Promise<void> {
197
197
 
198
198
  if (candidate) {
199
199
  await recordStep(steps, 'filesystem.read-candidate', options.stepTimeoutMs, async () => {
200
- const [info, stat, content, tail] = await Promise.all([
201
- notebook!.file.info(candidate),
202
- notebook!.file.stat(candidate),
203
- notebook!.file.readFile(candidate),
204
- notebook!.file.tail(candidate, 10),
200
+ const [stat, content, tail] = await Promise.all([
201
+ notebook!.files.stat(candidate),
202
+ notebook!.files.read(candidate),
203
+ notebook!.files.tail(candidate, { lines: 10 }),
205
204
  ]);
206
205
 
207
- const bytes = content instanceof Uint8Array ? content : new TextEncoder().encode(JSON.stringify(content));
208
- return `path=${candidate} size=${stat.size} binary=${info.binary} sha256=${sha256(bytes)} tailBytes=${tail.length}`;
206
+ return `path=${candidate} size=${stat.size} sha256=${sha256(content)} tailBytes=${tail.length}`;
209
207
  }, false);
210
208
  } else {
211
209
  recordSkippedStep(steps, 'filesystem.read-candidate', 'No readable candidate file found.');
@@ -214,7 +212,10 @@ async function main(): Promise<void> {
214
212
  if (options.includeDownload) {
215
213
  if (candidate) {
216
214
  await recordStep(steps, 'filesystem.download', options.stepTimeoutMs, async () => {
217
- const archive = await notebook!.file.download(undefined, ['vendor/**', 'node_modules/**'], [candidate]);
215
+ const archive = await notebook!.files.download({
216
+ exclude: ['vendor/**', 'node_modules/**'],
217
+ include: [candidate],
218
+ });
218
219
  if (archive.size === 0) {
219
220
  throw new Error('file.download() returned an empty blob.');
220
221
  }
@@ -228,7 +229,7 @@ async function main(): Promise<void> {
228
229
  }
229
230
 
230
231
  await recordStep(steps, 'filesystem.search', options.stepTimeoutMs, async () => {
231
- const [hasMore, matches] = await notebook!.file.search({
232
+ const [hasMore, matches] = await notebook!.files.search({
232
233
  pattern: 'php',
233
234
  isRegExp: false,
234
235
  isCaseSensitive: false,
@@ -260,7 +261,7 @@ async function main(): Promise<void> {
260
261
  await runExecutionChecks(notebook, steps, options);
261
262
  } else {
262
263
  recordSkippedStep(steps, 'shell.run', 'Enable with --include-exec.');
263
- recordSkippedStep(steps, 'terminal.spawn', 'Enable with --include-exec.');
264
+ recordSkippedStep(steps, 'terminals.create', 'Enable with --include-exec.');
264
265
  recordSkippedStep(steps, 'terminal.interactive', 'Enable with --include-exec.');
265
266
  recordSkippedStep(steps, 'terminal.osc', 'Enable with --include-exec.');
266
267
  recordSkippedStep(steps, 'repl.eval', 'Enable with --include-exec.');
@@ -332,26 +333,25 @@ async function runExecutionChecks(notebook: NotebookInstance, steps: StepArtifac
332
333
  return `exit=${result.exitCode}`;
333
334
  }, false);
334
335
 
335
- await recordStep(steps, 'terminal.spawn', options.stepTimeoutMs, async () => {
336
- const process = await notebook.terminal.spawn('php', ['-r', 'echo "sdk-smoke";'], { output: true });
336
+ await recordStep(steps, 'terminals.create', options.stepTimeoutMs, async () => {
337
+ const process = await notebook.terminals.create({ command: 'php', args: ['-r', 'echo "sdk-smoke";'] });
337
338
  const [exitCode, output] = await Promise.all([
338
339
  withTimeout(process.wait(), options.stepTimeoutMs, 'waiting for terminal process exit'),
339
340
  readStreamToString(process.output, options.streamTimeoutMs, options.maxStreamChars),
340
341
  ]);
341
342
 
342
343
  if (exitCode !== 0 || !output.includes('sdk-smoke')) {
343
- throw new Error(`terminal.spawn failed: exit=${exitCode} output=${trimDetail(output)}`);
344
+ throw new Error(`terminals.create failed: exit=${exitCode} output=${trimDetail(output)}`);
344
345
  }
345
346
 
346
347
  return `exit=${exitCode}`;
347
348
  }, false);
348
349
 
349
350
  await recordStep(steps, 'terminal.interactive', options.stepTimeoutMs, async () => {
350
- const process = await notebook.terminal.spawn(
351
- 'bash',
352
- ['-lc', 'IFS= read -r line; printf "sdk-input:%s\\n" "$line"'],
353
- { output: true }
354
- );
351
+ const process = await notebook.terminals.create({
352
+ command: 'bash',
353
+ args: ['-lc', 'IFS= read -r line; printf "sdk-input:%s\\n" "$line"'],
354
+ });
355
355
 
356
356
  try {
357
357
  const writer = process.input.getWriter();
@@ -374,15 +374,12 @@ async function runExecutionChecks(notebook: NotebookInstance, steps: StepArtifac
374
374
  }, false);
375
375
 
376
376
  await recordStep(steps, 'terminal.osc', options.stepTimeoutMs, async () => {
377
- const process = await notebook.terminal.spawn(
378
- 'bash',
379
- ['-lc', 'printf "sdk-prompt$"'],
380
- {
381
- output: true,
382
- osc: true,
383
- terminal: { cols: 80, rows: 15 },
384
- }
385
- );
377
+ const process = await notebook.terminals.create({
378
+ command: 'bash',
379
+ args: ['-lc', 'printf "sdk-prompt$"'],
380
+ osc: true,
381
+ size: [80, 15],
382
+ });
386
383
 
387
384
  try {
388
385
  const [exitCode, output] = await Promise.all([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phpsandbox/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "A comprehensive TypeScript SDK for interacting with cloud-based PHP development environments",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",