@phpsandbox/sdk 0.0.68 → 0.1.2

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 (56) hide show
  1. package/README.md +66 -32
  2. package/dist/auth.d.ts +0 -1
  3. package/dist/auth.js +0 -3
  4. package/dist/beacon/index.d.ts +2 -0
  5. package/dist/beacon/index.js +5 -0
  6. package/dist/browser/phpsandbox-sdk.esm.js +842 -1320
  7. package/dist/browser/phpsandbox-sdk.esm.min.js +24 -24
  8. package/dist/browser/phpsandbox-sdk.iife.js +842 -1320
  9. package/dist/browser/phpsandbox-sdk.iife.min.js +23 -23
  10. package/dist/composer.d.ts +36 -23
  11. package/dist/composer.js +61 -11
  12. package/dist/container.d.ts +2 -6
  13. package/dist/container.js +0 -28
  14. package/dist/filesystem.d.ts +58 -17
  15. package/dist/filesystem.js +169 -51
  16. package/dist/index.d.ts +30 -26
  17. package/dist/index.js +37 -32
  18. package/dist/log.d.ts +11 -5
  19. package/dist/log.js +10 -7
  20. package/dist/metrics.d.ts +10 -0
  21. package/dist/metrics.js +13 -0
  22. package/dist/ports.d.ts +10 -0
  23. package/dist/ports.js +13 -0
  24. package/dist/publications.d.ts +222 -0
  25. package/dist/{deployments.js → publications.js} +64 -69
  26. package/dist/repl.d.ts +0 -2
  27. package/dist/repl.js +0 -3
  28. package/dist/runtime/rest.js +1 -4
  29. package/dist/runtime-telemetry.d.ts +15 -0
  30. package/dist/runtime-telemetry.js +111 -0
  31. package/dist/schemas/{deployments.d.ts → publications.d.ts} +76 -76
  32. package/dist/schemas/publications.js +133 -0
  33. package/dist/services.d.ts +2 -9
  34. package/dist/services.js +3 -10
  35. package/dist/shell.d.ts +52 -22
  36. package/dist/shell.js +103 -75
  37. package/dist/terminal.d.ts +18 -14
  38. package/dist/terminal.js +29 -49
  39. package/docs/api-summary.md +37 -29
  40. package/docs/cdn-usage.md +4 -4
  41. package/docs/getting-started.md +17 -17
  42. package/examples/README.md +8 -9
  43. package/examples/basic.ts +2 -2
  44. package/examples/git-merge-smoke.ts +3 -6
  45. package/examples/laravel-load-test.ts +80 -98
  46. package/examples/notebook-readonly-smoke.ts +38 -41
  47. package/examples/publication-provider-label.ts +11 -0
  48. package/examples/{deploy-cloudflare-container.ts → publish-cloudflare-container.ts} +11 -11
  49. package/examples/{deploy-laravel-cloud.ts → publish-laravel-cloud.ts} +11 -11
  50. package/examples/{deploy-ssh-agent.ts → publish-ssh-agent.ts} +15 -16
  51. package/package.json +7 -2
  52. package/dist/deployments.d.ts +0 -251
  53. package/dist/laravel.d.ts +0 -22
  54. package/dist/laravel.js +0 -11
  55. package/dist/schemas/deployments.js +0 -133
  56. package/examples/deployment-provider-label.ts +0 -11
package/dist/shell.js CHANGED
@@ -1,108 +1,136 @@
1
1
  import { nanoid } from 'nanoid';
2
- function resolveExecInput(command, argsOrOpts = [], opts) {
2
+ function resolveRunInput(command, argsOrOpts = [], opts) {
3
3
  const args = Array.isArray(argsOrOpts) ? argsOrOpts : [];
4
4
  const resolvedOpts = Array.isArray(argsOrOpts) ? opts : argsOrOpts;
5
5
  return {
6
6
  command: Array.isArray(command) ? command : args.length > 0 ? [command, ...args] : command,
7
- opts: resolvedOpts,
7
+ opts: { tty: false, ...resolvedOpts },
8
8
  };
9
9
  }
10
10
  export class CommandError extends Error {
11
- constructor(output, exitCode) {
12
- super(output);
13
- this.output = output;
14
- this.exitCode = exitCode;
11
+ constructor(result) {
12
+ super(result.output);
13
+ this.result = result;
15
14
  }
16
15
  }
17
- class Result {
18
- constructor(output, exitCode) {
16
+ export class ProcessResult {
17
+ constructor(output, stdout, stderr, exitCode) {
19
18
  this.output = output;
19
+ this.stdout = stdout;
20
+ this.stderr = stderr;
20
21
  this.exitCode = exitCode;
21
22
  }
22
23
  throw() {
23
24
  if (this.exitCode !== 0) {
24
- throw new CommandError(this.output, this.exitCode);
25
+ throw new CommandError(this);
25
26
  }
26
27
  return this;
27
28
  }
28
29
  }
29
- export default class Shell {
30
- constructor(okra) {
31
- this.okra = okra;
30
+ export class ProcessReadable {
31
+ constructor() {
32
+ this.controller = null;
33
+ this.closed = false;
34
+ this.stream = new ReadableStream({
35
+ start: (controller) => {
36
+ this.controller = controller;
37
+ },
38
+ cancel: () => {
39
+ this.closed = true;
40
+ this.controller = null;
41
+ },
42
+ });
32
43
  }
33
- onOutput(id, handler) {
34
- return this.okra.listen(`shell.output.${id}`, handler);
44
+ enqueue(value) {
45
+ if (!this.closed) {
46
+ this.controller?.enqueue(value);
47
+ }
35
48
  }
36
- listen(event, handler) {
37
- return this.okra.listen(event, handler);
49
+ close() {
50
+ if (this.closed) {
51
+ return;
52
+ }
53
+ this.closed = true;
54
+ this.controller?.close();
55
+ this.controller = null;
38
56
  }
39
- async exec(command, argsOrOpts = [], opts) {
40
- const input = resolveExecInput(command, argsOrOpts, opts);
41
- const { abortSignal, ...payloadOpts } = input.opts ?? {};
42
- const result = await this.okra.invoke('shell.exec', {
43
- command: input.command,
44
- opts: Object.keys(payloadOpts).length > 0 ? payloadOpts : undefined,
45
- }, { abortSignal });
46
- return new Result(result.output, result.exitCode);
57
+ fail(error) {
58
+ if (this.closed) {
59
+ return;
60
+ }
61
+ this.closed = true;
62
+ this.controller?.error(error);
63
+ this.controller = null;
47
64
  }
48
- execStream(command, argsOrOpts = [], opts) {
49
- const input = resolveExecInput(command, argsOrOpts, opts);
50
- const id = input.opts?.id || nanoid();
51
- const disposables = new Set();
52
- let controller = null;
53
- let closed = false;
54
- const dispose = () => {
55
- for (const disposable of disposables) {
56
- disposable.dispose();
57
- }
58
- disposables.clear();
59
- };
60
- const close = () => {
61
- if (closed) {
62
- return;
63
- }
64
- closed = true;
65
- dispose();
66
- if (controller) {
67
- try {
68
- controller.close();
69
- }
70
- catch {
71
- // Ignore close errors after cancellation.
65
+ }
66
+ export class ShellProcess {
67
+ constructor(id, okra, command, opts) {
68
+ this.id = id;
69
+ this.okra = okra;
70
+ const output = new ProcessReadable();
71
+ const stdout = new ProcessReadable();
72
+ const stderr = new ProcessReadable();
73
+ this.output = output.stream;
74
+ this.stdout = stdout.stream;
75
+ this.stderr = stderr.stream;
76
+ this.stdin = new WritableStream({
77
+ write: async (chunk) => {
78
+ const input = typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);
79
+ const written = await this.okra.invoke('shell.input', { id: this.id, input });
80
+ if (!written) {
81
+ throw new Error(`Process ${this.id} is not accepting input`);
72
82
  }
83
+ },
84
+ });
85
+ const encoder = new TextEncoder();
86
+ let subscription = this.okra.listen(`shell.output.${this.id}`, (event) => {
87
+ const data = encoder.encode(event.output);
88
+ const source = event.source ?? 'output';
89
+ output.enqueue({ source, data });
90
+ if (source === 'stderr') {
91
+ stderr.enqueue(data);
92
+ }
93
+ else {
94
+ stdout.enqueue(data);
73
95
  }
96
+ });
97
+ const finish = () => {
98
+ subscription?.dispose();
99
+ subscription = null;
100
+ output.close();
101
+ stdout.close();
102
+ stderr.close();
74
103
  };
75
104
  const fail = (error) => {
76
- if (closed) {
77
- return;
78
- }
79
- closed = true;
80
- dispose();
81
- if (controller) {
82
- controller.error(error);
83
- }
105
+ subscription?.dispose();
106
+ subscription = null;
107
+ output.fail(error);
108
+ stdout.fail(error);
109
+ stderr.fail(error);
84
110
  };
85
- const output = new ReadableStream({
86
- start: (_controller) => {
87
- controller = _controller;
88
- disposables.add(this.listen(`shell.output.${id}`, (data) => {
89
- controller?.enqueue(data.output);
90
- }));
91
- },
92
- cancel: () => {
93
- closed = true;
94
- dispose();
95
- },
96
- });
97
- const result = this.exec(input.command, { ...input.opts, id })
98
- .then((response) => {
99
- close();
100
- return response;
101
- })
102
- .catch((error) => {
111
+ const { abortSignal, ...payloadOpts } = opts;
112
+ this.completion = this.okra.invoke('shell.exec', { command, opts: { ...payloadOpts, id: this.id } }, { abortSignal }).then((result) => {
113
+ finish();
114
+ return new ProcessResult(result.output, result.stdout, result.stderr, result.exitCode);
115
+ }).catch((error) => {
103
116
  fail(error);
104
117
  throw error;
105
118
  });
106
- return { output, result };
119
+ }
120
+ wait() {
121
+ return this.completion;
122
+ }
123
+ async kill() {
124
+ await this.okra.invoke('shell.kill', { id: this.id });
125
+ }
126
+ }
127
+ export default class Shell {
128
+ constructor(okra) {
129
+ this.okra = okra;
130
+ }
131
+ run(command, argsOrOpts = [], opts) {
132
+ const input = resolveRunInput(command, argsOrOpts, opts);
133
+ const id = input.opts?.id ?? nanoid();
134
+ return new ShellProcess(id, this.okra, input.command, { ...input.opts, id });
107
135
  }
108
136
  }
@@ -1,4 +1,4 @@
1
- import { Action, Disposable, NotebookInstance } from './index.js';
1
+ import { Action, NotebookInstance } from './index.js';
2
2
  export interface Task {
3
3
  id: string;
4
4
  command: string | string[];
@@ -14,7 +14,7 @@ export interface TerminalCreateInput {
14
14
  kind: string;
15
15
  size: [number, number];
16
16
  command?: string | string[];
17
- env?: Record<string, string>;
17
+ env?: Record<string, string | number | boolean>;
18
18
  cwd?: string;
19
19
  }
20
20
  export interface TerminalSpawnInput {
@@ -49,7 +49,6 @@ export interface TerminalActions {
49
49
  input: string;
50
50
  }>;
51
51
  'terminal.list': Action<{}, Task[]>;
52
- 'terminal.start': Action;
53
52
  'terminal.create': Action<TerminalCreateInput, Task>;
54
53
  'terminal.spawn': Action<TerminalSpawnInput, Task>;
55
54
  'terminal.attach': Action<{
@@ -66,8 +65,8 @@ export interface TerminalActions {
66
65
  }, boolean>;
67
66
  }
68
67
  export interface SandboxProcess {
69
- exit: Promise<number>;
70
- input: WritableStream<string>;
68
+ wait(): Promise<number>;
69
+ input: WritableStream<string | Uint8Array>;
71
70
  output: ReadableStream<string>;
72
71
  kill(): Promise<boolean>;
73
72
  resize(dimensions: {
@@ -88,22 +87,27 @@ export interface SpawnOptions {
88
87
  tty?: boolean;
89
88
  abortSignal?: AbortSignal;
90
89
  }
91
- export default class Terminal {
90
+ export interface TerminalOptions {
91
+ command?: string | string[];
92
+ args?: string[];
93
+ kind?: string;
94
+ size?: [number, number];
95
+ cwd?: string;
96
+ env?: Record<string, string | number | boolean>;
97
+ id?: string;
98
+ osc?: boolean;
99
+ tty?: boolean;
100
+ abortSignal?: AbortSignal;
101
+ }
102
+ export default class Terminals {
92
103
  protected okra: NotebookInstance;
93
104
  constructor(okra: NotebookInstance);
94
105
  list(): Promise<Task[]>;
95
- start(): Promise<void>;
96
- create(input: TerminalCreateInput): Promise<Task>;
97
- onStarted(handler: (t: Task) => void): Disposable;
98
- resize(id: string, size: [number, number]): Promise<boolean>;
99
- onOutput(id: string, handler: (data: TerminalEvents['terminal.output']) => void): Disposable;
100
- input(id: string, input: string): Promise<boolean>;
101
- listen<T extends keyof TerminalEvents>(event: T, handler: (data: TerminalEvents[T]) => void): Disposable;
106
+ create(options?: TerminalOptions): Promise<SandboxProcess & Task>;
102
107
  attach(id: string, opts?: {
103
108
  replayHistory?: boolean;
104
109
  abortSignal?: AbortSignal;
105
110
  }): Promise<(SandboxProcess & Task) | false>;
106
- spawn(command: string, args: string[], opts?: SpawnOptions): Promise<SandboxProcess & Task>;
107
111
  private processHandle;
108
112
  }
109
113
  export {};
package/dist/terminal.js CHANGED
@@ -2,33 +2,34 @@ import { nanoid } from 'nanoid';
2
2
  import { once } from './utils/promise.js';
3
3
  const TERMINAL_INPUT_FLUSH_DELAY_MS = 8;
4
4
  const terminalInputFlushPattern = /[\x00-\x1f\x7f]/;
5
- export default class Terminal {
5
+ export default class Terminals {
6
6
  constructor(okra) {
7
7
  this.okra = okra;
8
8
  }
9
9
  list() {
10
10
  return this.okra.invoke('terminal.list');
11
11
  }
12
- start() {
13
- return this.okra.invoke('terminal.start');
14
- }
15
- create(input) {
16
- return this.okra.invoke('terminal.create', input);
17
- }
18
- onStarted(handler) {
19
- return this.okra.listen('terminal.started', handler);
20
- }
21
- resize(id, size) {
22
- return this.okra.invoke('terminal.resize', { id, width: size[0], height: size[1] });
23
- }
24
- onOutput(id, handler) {
25
- return this.okra.listen(`terminal.output.${id}`, handler);
26
- }
27
- input(id, input) {
28
- return Promise.resolve(this.okra.send('terminal.input', { id, input }));
29
- }
30
- listen(event, handler) {
31
- return this.okra.listen(event, handler);
12
+ async create(options = {}) {
13
+ const id = options.id ?? nanoid();
14
+ const handle = this.processHandle(id, options.abortSignal);
15
+ const command = typeof options.command === 'string' && options.args?.length
16
+ ? [options.command, ...options.args]
17
+ : options.command;
18
+ const { abortSignal, args: _args, ...input } = options;
19
+ try {
20
+ const task = await this.okra.invoke('terminal.create', {
21
+ ...input,
22
+ id,
23
+ kind: options.kind ?? 'bash',
24
+ size: options.size ?? [80, 24],
25
+ command,
26
+ }, { abortSignal });
27
+ return { ...handle.process, ...task };
28
+ }
29
+ catch (error) {
30
+ handle.dispose();
31
+ throw error;
32
+ }
32
33
  }
33
34
  async attach(id, opts) {
34
35
  const handle = this.processHandle(id, opts?.abortSignal);
@@ -51,28 +52,6 @@ export default class Terminal {
51
52
  throw error;
52
53
  }
53
54
  }
54
- async spawn(command, args, opts) {
55
- const id = opts?.id || nanoid();
56
- const handle = this.processHandle(id, opts?.abortSignal);
57
- const { abortSignal, ...otherOpts } = opts ?? {};
58
- try {
59
- const result = await this.okra.invoke('terminal.spawn', {
60
- command: [command, ...args],
61
- opts: {
62
- id,
63
- ...otherOpts,
64
- },
65
- }, { abortSignal });
66
- return {
67
- ...handle.process,
68
- ...result,
69
- };
70
- }
71
- catch (error) {
72
- handle.dispose();
73
- throw error;
74
- }
75
- }
76
55
  processHandle(id, abortSignal) {
77
56
  const disposables = new Set();
78
57
  let abortHandler;
@@ -98,7 +77,7 @@ export default class Terminal {
98
77
  }
99
78
  const input = pendingInput;
100
79
  pendingInput = '';
101
- void this.input(id, input);
80
+ void this.okra.send('terminal.input', { id, input });
102
81
  };
103
82
  const scheduleInputFlush = () => {
104
83
  if (inputFlushTimer) {
@@ -106,7 +85,8 @@ export default class Terminal {
106
85
  }
107
86
  inputFlushTimer = setTimeout(flushInput, TERMINAL_INPUT_FLUSH_DELAY_MS);
108
87
  };
109
- const queueInput = (chunk) => {
88
+ const queueInput = (value) => {
89
+ const chunk = typeof value === 'string' ? value : new TextDecoder().decode(value);
110
90
  pendingInput += chunk;
111
91
  if (terminalInputFlushPattern.test(chunk)) {
112
92
  flushInput();
@@ -127,7 +107,7 @@ export default class Terminal {
127
107
  const output = new ReadableStream({
128
108
  start: (_controller) => {
129
109
  controller = _controller;
130
- disposables.add(this.listen(`terminal.output.${id}`, (data) => {
110
+ disposables.add(this.okra.listen(`terminal.output.${id}`, (data) => {
131
111
  controller?.enqueue(data.output);
132
112
  }));
133
113
  },
@@ -136,8 +116,8 @@ export default class Terminal {
136
116
  disposeInput();
137
117
  },
138
118
  });
139
- const exit = new Promise((resolve) => {
140
- disposables.add(this.listen(`terminal.close.${id}`, (data) => {
119
+ const completion = new Promise((resolve) => {
120
+ disposables.add(this.okra.listen(`terminal.close.${id}`, (data) => {
141
121
  if (controller) {
142
122
  try {
143
123
  controller.close();
@@ -165,7 +145,7 @@ export default class Terminal {
165
145
  }
166
146
  return {
167
147
  process: {
168
- exit,
148
+ wait: () => completion,
169
149
  input,
170
150
  output,
171
151
  kill,
@@ -23,45 +23,55 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
23
23
 
24
24
  - `create(template, input?, init = true)`
25
25
  - `get(id)`
26
- - `open(id)`
27
26
  - `fork(id, input?)`
28
- - `delete(id)`
27
+ - `destroy(id)`
29
28
  - `openFromData(data)`
30
29
  - `mail.status(id)`, `mail.enable(id)`, `mail.disable(id)`, `mail.list(id, options?)`, `mail.get(id, hash)`, `mail.delete(id, hash)`
31
30
 
32
31
  ## `NotebookInstance`
33
32
 
34
33
  - Lifecycle: `ready()`, `connected()`, `whenConnected()`, `reconnect()`, `dispose()`
35
- - Notebook actions: `fork(input?)`, `delete()`, `stop()`, `restart()`, `ping()`, `update()`
34
+ - Notebook actions: `fork(input?)`, `destroy()`, `stop()`, `restart()`, `ping()`, `update()`
35
+ - Processes: `exec(command, args?, options?)`, or `run(command, args?, options?)` returning native `stdin`, `stdout`, `stderr`, ordered `output`, `wait()`, and `kill()`
36
36
  - Events: `listen(event, handler)`, `onDidConnect(handler)`, `onDidDisconnect(handler)`, `onDidInitialize(handler)`
37
37
  - Raw call: `invoke(action, data?, options?)`
38
38
 
39
39
  ## Services
40
40
 
41
- ### Filesystem (`notebook.file`)
41
+ ### Filesystem (`notebook.files`)
42
42
 
43
- - File data: `info()`, `readFile()`, `write()`, `writeFile()`, `tail()`, `stat()`, `exists()`
44
- - File management: `move()`, `rename()`, `copy()`, `remove()`, `delete()`
45
- - Directory: `mkdir()`, `createDirectory()`, `readDirectory()`, `tree()`
43
+ - File data: `read()`, `readText()`, `readStream()`, `write()`, `tail()`, `stat()`, `exists()`
44
+ - File management: `move()`, `copy()`, `remove()`
45
+ - Directory: `createDirectory()`, `list()`, `tree()`
46
46
  - Search: `find()`, `search()`
47
- - Streaming/monitoring: `watch()`, `download()`
47
+ - Streaming/monitoring: `readStream()`, `watch()`, `download()`
48
+ - `tail()` is an eager last-lines snapshot; it does not follow future appends.
48
49
 
49
- ### Terminal (`notebook.terminal`)
50
+ ### Terminal (`notebook.terminals`)
50
51
 
51
- - Terminal control: `list()`, `create()`, `attach()`, `resize()`, `input()`
52
- - Process API: `spawn(command, args, opts?)`, `attach(id, opts?)`
53
- - Events: `listen()`, `onStarted()`, `onOutput(id, handler)`
52
+ - `list()` returns terminal metadata
53
+ - `create(options?)` returns a terminal handle
54
+ - `attach(id, options?)` returns a terminal handle
55
+ - Handles expose `input`, `output`, `wait()`, `resize()`, and `kill()`
54
56
 
55
57
  ### Container (`notebook.container`)
56
58
 
57
59
  - Lifecycle: `start()`, `stop()`, `state()`
58
- - Ports: `openedPorts()`, `onPort(handler)`
59
60
  - Runtime config: `setPhp(version)`
60
- - Telemetry: `enableTelemetry(features)`, `stopTelemetry()`, `listen(event, handler)`
61
61
 
62
- ### Shell (`notebook.shell`)
62
+ ### Runtime observability
63
63
 
64
- - `exec(command)` returning `{ output, exitCode }` plus `throw()` helper
64
+ - `notebook.logs.follow({ signal? })` returns `ReadableStream<RuntimeLogEntry>`
65
+ - `notebook.ports.list()` returns the current preview ports
66
+ - `notebook.ports.watch({ signal? })` returns `ReadableStream<PortInfo[]>`
67
+ - `notebook.metrics.current()` returns the current CPU, memory, and disk sample
68
+ - `notebook.metrics.watch({ signal? })` returns `ReadableStream<ContainerStats>`
69
+
70
+ ### Processes
71
+
72
+ - `notebook.run(command, args?, options?)` returns a streaming process handle
73
+ - `notebook.exec(command, args?, options?)` returns a buffered `ProcessResult` for one-shot commands
74
+ - Process handles expose `output`, `stdout`, `stderr`, `stdin`, `wait()`, and `kill()`
65
75
 
66
76
  ### Services (`notebook.services`)
67
77
 
@@ -70,13 +80,13 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
70
80
  - `run(name, command?)`
71
81
  - `stop(name)`
72
82
  - `logs(name, { tail? })`
73
- - `logs(name, { follow: true, tail?, id? })` returning `{ id, output, stop() }`
74
- - Events: `listen()`, `onLog(id, handler)`
83
+ - `logs(name, { follow: true, tail?, id? })` returning `ReadableStream<string>`; cancel the stream to stop following
75
84
 
76
85
  ### Composer (`notebook.composer`)
77
86
 
78
- - Core: `invoke(command, args?, options?)`
79
- - Helpers: `install()`, `update()`, `require()`, `remove()`, `dumpAutoload()`, `packages()`, `stream(handler)`
87
+ - Core: `run(command, args?, options?)`
88
+ - Helpers: `install()`, `update()`, `require()`, `remove()`, `dumpAutoload()`, `packages()`
89
+ - Command handles expose `output`, `stdout`, `stderr`, `wait()`, and `kill()`
80
90
 
81
91
  ### Git (`notebook.git`)
82
92
 
@@ -114,19 +124,17 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
114
124
  - `stop()`, `eval(code, args?, replOpts?)`, `write(input)`, `resize(cols, rows)`
115
125
  - Events: `listen()`, `onOutput()`
116
126
 
117
- ### Laravel (`notebook.laravel`)
118
-
119
- - Wrapper exists in the SDK, but the current runtime action set does not expose Laravel maintenance actions.
120
-
121
127
  ### Auth (`notebook.auth`)
122
128
 
123
129
  - `login(runtimeUrl)` using a fresh ticket-bearing runtime URL
124
- - `logout()`
125
130
 
126
- ### Log (`notebook.log`)
131
+ ## Publications
127
132
 
128
- - `stream(handler)`
129
- - `listen(event, handler)`
133
+ - `notebook.publish({ slug, provider, protection?, feedback?, metadata? })`
134
+ - `notebook.publication()` returns the current publication or `null`
135
+ - `client.publications.get(id)`, `destroy(id)`, `events(id)`, `logs(id)`, and `buildLogs(id, buildId)`
136
+ - A publish run exposes `events()` as an `AsyncIterable`, `result()`, and `publication()`.
137
+ - Built-in provider names are `cloudflare-containers`, `ssh-server`, and `laravel-cloud`. Provider maps support TypeScript declaration merging.
130
138
 
131
139
  ## Common Errors
132
140
 
@@ -137,6 +145,6 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
137
145
 
138
146
  ## Notes for Consumers
139
147
 
140
- - `open()` and `get()` do not auto-initialize; call `await notebook.ready()`.
148
+ - `get()` does not auto-initialize; call `await notebook.ready()`.
141
149
  - `create()` and `fork()` initialize by default.
142
150
  - Always call `notebook.dispose()` when finished.
package/docs/cdn-usage.md CHANGED
@@ -15,10 +15,10 @@ Do not hardcode long-lived private API tokens in public frontend code. Use short
15
15
  import { PHPSandbox } from 'https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js';
16
16
 
17
17
  const client = new PHPSandbox('your-short-lived-token');
18
- const notebook = await client.notebook.open('your-notebook-id');
18
+ const notebook = await client.notebook.get('your-notebook-id');
19
19
  await notebook.ready();
20
20
 
21
- const result = await notebook.shell.exec('php -v');
21
+ const result = await notebook.exec('php -v', { tty: false });
22
22
  result.throw();
23
23
  console.log(result.output);
24
24
  </script>
@@ -43,9 +43,9 @@ Do not hardcode long-lived private API tokens in public frontend code. Use short
43
43
  <script>
44
44
  const client = new PHPSandbox.PHPSandbox('your-short-lived-token');
45
45
 
46
- client.notebook.open('your-notebook-id').then(async (notebook) => {
46
+ client.notebook.get('your-notebook-id').then(async (notebook) => {
47
47
  await notebook.ready();
48
- const result = await notebook.shell.exec('php -v');
48
+ const result = await notebook.exec('php -v', { tty: false });
49
49
  console.log(result.output);
50
50
  notebook.dispose();
51
51
  });
@@ -35,8 +35,8 @@ const persistent = await client.notebook.create('laravel', {
35
35
  persistent: true,
36
36
  });
37
37
 
38
- // open()/get() need ready()
39
- const existing = await client.notebook.open('notebook-id');
38
+ // get() needs ready()
39
+ const existing = await client.notebook.get('notebook-id');
40
40
  await existing.ready();
41
41
 
42
42
  const fork = await existing.fork();
@@ -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,29 +85,29 @@ 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.shell.exec('php -v');
95
+ const result = await notebook.exec('php -v', { tty: false });
96
96
  result.throw();
97
- console.log(result.output);
97
+ console.log(result.stdout);
98
98
  ```
99
99
 
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
  ```
@@ -141,9 +141,9 @@ notebook.dispose();
141
141
 
142
142
  ## Common Pitfalls
143
143
 
144
- - `open()` / `get()` notebooks must call `await notebook.ready()` before tool calls.
145
- - `file.watch()` requires an `excludes` array.
146
- - `shell.exec()` does not throw by default; call `result.throw()` to fail on non-zero exit codes.
144
+ - `get()` notebooks must call `await notebook.ready()` before tool calls.
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