@phpsandbox/sdk 0.0.51 → 0.0.53
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/LICENSE +21 -0
- package/README.md +7 -6
- package/dist/auth.d.ts +0 -1
- package/dist/auth.js +0 -1
- package/dist/beacon/index.d.ts +4 -5
- package/dist/beacon/index.js +11 -12
- package/dist/beacon/navigator.d.ts +1 -2
- package/dist/beacon/navigator.js +0 -1
- package/dist/beacon/types.d.ts +12 -13
- package/dist/beacon/types.js +0 -1
- package/dist/browser/phpsandbox-sdk.esm.js +313 -481
- package/dist/browser/phpsandbox-sdk.esm.min.js +2 -3
- package/dist/browser/phpsandbox-sdk.iife.js +308 -481
- package/dist/browser/phpsandbox-sdk.iife.min.js +2 -3
- package/dist/composer.d.ts +2 -3
- package/dist/composer.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/container.d.ts +0 -1
- package/dist/container.js +0 -1
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +0 -1
- package/dist/events/index.d.ts +15 -15
- package/dist/events/index.js +16 -9
- package/dist/filesystem.d.ts +6 -7
- package/dist/filesystem.js +36 -14
- package/dist/git.d.ts +20 -9
- package/dist/git.js +31 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +38 -25
- package/dist/laravel.d.ts +1 -2
- package/dist/laravel.js +1 -2
- package/dist/log.d.ts +3 -4
- package/dist/log.js +1 -2
- package/dist/lsp.d.ts +10 -11
- package/dist/lsp.js +9 -10
- package/dist/repl.d.ts +3 -4
- package/dist/repl.js +2 -3
- package/dist/services.d.ts +0 -1
- package/dist/services.js +0 -1
- package/dist/shell.d.ts +1 -2
- package/dist/shell.js +1 -2
- package/dist/socket/index.d.ts +23 -129
- package/dist/socket/index.js +60 -350
- package/dist/terminal.d.ts +2 -3
- package/dist/terminal.js +45 -26
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/disposable.d.ts +0 -1
- package/dist/utils/disposable.js +0 -1
- package/dist/utils/promise.d.ts +0 -1
- package/dist/utils/promise.js +0 -1
- package/dist/utils/retry.d.ts +0 -1
- package/dist/utils/retry.js +0 -1
- package/dist/utils/websocket.d.ts +0 -1
- package/dist/utils/websocket.js +0 -1
- package/docs/api-summary.md +136 -0
- package/docs/cdn-usage.md +80 -0
- package/docs/getting-started.md +152 -0
- package/examples/README.md +206 -0
- package/examples/basic.ts +24 -0
- package/examples/get.ts +21 -0
- package/examples/git-merge-smoke.ts +399 -0
- package/examples/laravel-load-test.ts +2829 -0
- package/examples/notebook-readonly-smoke.ts +823 -0
- package/package.json +13 -7
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/beacon/index.d.ts.map +0 -1
- package/dist/beacon/index.js.map +0 -1
- package/dist/beacon/navigator.d.ts.map +0 -1
- package/dist/beacon/navigator.js.map +0 -1
- package/dist/beacon/types.d.ts.map +0 -1
- package/dist/beacon/types.js.map +0 -1
- package/dist/browser/phpsandbox-sdk.esm.js.map +0 -7
- package/dist/browser/phpsandbox-sdk.esm.min.js.map +0 -7
- package/dist/browser/phpsandbox-sdk.iife.js.map +0 -7
- package/dist/browser/phpsandbox-sdk.iife.min.js.map +0 -7
- package/dist/composer.d.ts.map +0 -1
- package/dist/composer.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/events/index.d.ts.map +0 -1
- package/dist/events/index.js.map +0 -1
- package/dist/filesystem.d.ts.map +0 -1
- package/dist/filesystem.js.map +0 -1
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/laravel.d.ts.map +0 -1
- package/dist/laravel.js.map +0 -1
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/lsp.d.ts.map +0 -1
- package/dist/lsp.js.map +0 -1
- package/dist/repl.d.ts.map +0 -1
- package/dist/repl.js.map +0 -1
- package/dist/services.d.ts.map +0 -1
- package/dist/services.js.map +0 -1
- package/dist/shell.d.ts.map +0 -1
- package/dist/shell.js.map +0 -1
- package/dist/socket/index.d.ts.map +0 -1
- package/dist/socket/index.js.map +0 -1
- package/dist/terminal.d.ts.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/disposable.d.ts.map +0 -1
- package/dist/utils/disposable.js.map +0 -1
- package/dist/utils/promise.d.ts.map +0 -1
- package/dist/utils/promise.js.map +0 -1
- package/dist/utils/retry.d.ts.map +0 -1
- package/dist/utils/retry.js.map +0 -1
- package/dist/utils/websocket.d.ts.map +0 -1
- package/dist/utils/websocket.js.map +0 -1
package/dist/terminal.js
CHANGED
|
@@ -16,13 +16,13 @@ export default class Terminal {
|
|
|
16
16
|
return this.okra.invoke('terminal.create', input);
|
|
17
17
|
}
|
|
18
18
|
onStarted(handler) {
|
|
19
|
-
this.okra.listen('terminal.started', handler);
|
|
19
|
+
return this.okra.listen('terminal.started', handler);
|
|
20
20
|
}
|
|
21
21
|
resize(id, size) {
|
|
22
22
|
return this.okra.invoke('terminal.resize', { id, width: size[0], height: size[1] });
|
|
23
23
|
}
|
|
24
24
|
onOutput(id, handler) {
|
|
25
|
-
this.okra.listen(`terminal.output.${id}`, handler);
|
|
25
|
+
return this.okra.listen(`terminal.output.${id}`, handler);
|
|
26
26
|
}
|
|
27
27
|
input(id, input) {
|
|
28
28
|
return Promise.resolve(this.okra.send('terminal.input', { id, input }));
|
|
@@ -32,38 +32,55 @@ export default class Terminal {
|
|
|
32
32
|
}
|
|
33
33
|
async attach(id, opts) {
|
|
34
34
|
const handle = this.processHandle(id, opts?.abortSignal);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
try {
|
|
36
|
+
const task = await this.okra.invoke('terminal.attach', {
|
|
37
|
+
id,
|
|
38
|
+
replayHistory: opts?.replayHistory,
|
|
39
|
+
}, { abortSignal: opts?.abortSignal });
|
|
40
|
+
if (!task) {
|
|
41
|
+
handle.dispose();
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...handle.process,
|
|
46
|
+
...task,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
40
50
|
handle.dispose();
|
|
41
|
-
|
|
51
|
+
throw error;
|
|
42
52
|
}
|
|
43
|
-
return {
|
|
44
|
-
...handle.process,
|
|
45
|
-
...task,
|
|
46
|
-
};
|
|
47
53
|
}
|
|
48
54
|
async spawn(command, args, opts) {
|
|
49
55
|
const id = opts?.id || nanoid();
|
|
50
56
|
const handle = this.processHandle(id, opts?.abortSignal);
|
|
51
57
|
const { abortSignal, ...otherOpts } = opts ?? {};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|
|
63
75
|
}
|
|
64
76
|
processHandle(id, abortSignal) {
|
|
65
77
|
const disposables = new Set();
|
|
78
|
+
let abortHandler;
|
|
66
79
|
const dispose = () => {
|
|
80
|
+
if (abortHandler) {
|
|
81
|
+
abortSignal?.removeEventListener('abort', abortHandler);
|
|
82
|
+
abortHandler = undefined;
|
|
83
|
+
}
|
|
67
84
|
for (const disposable of disposables) {
|
|
68
85
|
disposable.dispose();
|
|
69
86
|
}
|
|
@@ -140,8 +157,11 @@ export default class Terminal {
|
|
|
140
157
|
const resize = (dimensions) => {
|
|
141
158
|
this.okra.invoke('terminal.resize', { id, width: dimensions.cols, height: dimensions.rows });
|
|
142
159
|
};
|
|
143
|
-
if (abortSignal) {
|
|
144
|
-
|
|
160
|
+
if (abortSignal && !abortSignal.aborted) {
|
|
161
|
+
abortHandler = () => {
|
|
162
|
+
void kill();
|
|
163
|
+
};
|
|
164
|
+
abortSignal.addEventListener('abort', abortHandler, { once: true });
|
|
145
165
|
}
|
|
146
166
|
return {
|
|
147
167
|
process: {
|
|
@@ -155,4 +175,3 @@ export default class Terminal {
|
|
|
155
175
|
};
|
|
156
176
|
}
|
|
157
177
|
}
|
|
158
|
-
//# sourceMappingURL=terminal.js.map
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
package/dist/utils/disposable.js
CHANGED
package/dist/utils/promise.d.ts
CHANGED
|
@@ -14,4 +14,3 @@ export declare const timeout: <T>(prom: Promise<T>, time: number) => Promise<T>;
|
|
|
14
14
|
* @returns A wrapped function that memoizes the first execution
|
|
15
15
|
*/
|
|
16
16
|
export declare const once: <T>(fn: () => Promise<T>) => (() => Promise<T>);
|
|
17
|
-
//# sourceMappingURL=promise.d.ts.map
|
package/dist/utils/promise.js
CHANGED
package/dist/utils/retry.d.ts
CHANGED
package/dist/utils/retry.js
CHANGED
package/dist/utils/websocket.js
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# PHPSandbox SDK API Summary
|
|
2
|
+
|
|
3
|
+
This is a quick reference for the current SDK surface.
|
|
4
|
+
|
|
5
|
+
## Top-Level Client
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { PHPSandbox } from '@phpsandbox/sdk';
|
|
9
|
+
|
|
10
|
+
const client = new PHPSandbox(token, 'https://api.phpsandbox.io/v1', {
|
|
11
|
+
debug: false,
|
|
12
|
+
startClosed: true,
|
|
13
|
+
});
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### `NotebookApi`
|
|
17
|
+
|
|
18
|
+
- `create(template, input?, init = true)`
|
|
19
|
+
- `get(id)`
|
|
20
|
+
- `open(id)`
|
|
21
|
+
- `fork(id, input?)`
|
|
22
|
+
- `delete(id)`
|
|
23
|
+
- `openFromData(data)`
|
|
24
|
+
- `mail.status(id)`, `mail.enable(id)`, `mail.disable(id)`, `mail.list(id, options?)`, `mail.get(id, hash)`, `mail.delete(id, hash)`
|
|
25
|
+
|
|
26
|
+
## `NotebookInstance`
|
|
27
|
+
|
|
28
|
+
- Lifecycle: `ready()`, `connected()`, `whenConnected()`, `reconnect()`, `dispose()`
|
|
29
|
+
- Notebook actions: `fork(input?)`, `delete()`, `stop()`, `restart()`, `ping()`, `update()`
|
|
30
|
+
- Events: `listen(event, handler)`, `onDidConnect(handler)`, `onDidDisconnect(handler)`, `onDidInitialize(handler)`
|
|
31
|
+
- Raw call: `invoke(action, data?, options?)`
|
|
32
|
+
|
|
33
|
+
## Services
|
|
34
|
+
|
|
35
|
+
### Filesystem (`notebook.file`)
|
|
36
|
+
|
|
37
|
+
- File data: `info()`, `readFile()`, `write()`, `writeFile()`, `tail()`, `stat()`, `exists()`
|
|
38
|
+
- File management: `move()`, `rename()`, `copy()`, `remove()`, `delete()`
|
|
39
|
+
- Directory: `mkdir()`, `createDirectory()`, `readDirectory()`, `tree()`
|
|
40
|
+
- Search: `find()`, `search()`
|
|
41
|
+
- Streaming/monitoring: `watch()`, `download()`
|
|
42
|
+
|
|
43
|
+
### Terminal (`notebook.terminal`)
|
|
44
|
+
|
|
45
|
+
- Terminal control: `list()`, `create()`, `attach()`, `resize()`, `input()`
|
|
46
|
+
- Process API: `spawn(command, args, opts?)`, `attach(id, opts?)`
|
|
47
|
+
- Events: `listen()`, `onStarted()`, `onOutput(id, handler)`
|
|
48
|
+
|
|
49
|
+
### Container (`notebook.container`)
|
|
50
|
+
|
|
51
|
+
- Lifecycle: `start()`, `stop()`, `state()`
|
|
52
|
+
- Ports: `openedPorts()`, `onPort(handler)`
|
|
53
|
+
- Runtime config: `setPhp(version)`
|
|
54
|
+
- Telemetry: `enableTelemetry(features)`, `stopTelemetry()`, `listen(event, handler)`
|
|
55
|
+
|
|
56
|
+
### Shell (`notebook.shell`)
|
|
57
|
+
|
|
58
|
+
- `exec(command)` returning `{ output, exitCode }` plus `throw()` helper
|
|
59
|
+
|
|
60
|
+
### Services (`notebook.services`)
|
|
61
|
+
|
|
62
|
+
- Constants: `notebookBuiltinServices`, `notebookKnownServices`
|
|
63
|
+
- `list()`
|
|
64
|
+
- `run(name, command?)`
|
|
65
|
+
- `stop(name)`
|
|
66
|
+
- `logs(name, { tail? })`
|
|
67
|
+
- `logs(name, { follow: true, tail?, id? })` returning `{ id, output, stop() }`
|
|
68
|
+
- Events: `listen()`, `onLog(id, handler)`
|
|
69
|
+
|
|
70
|
+
### Composer (`notebook.composer`)
|
|
71
|
+
|
|
72
|
+
- Core: `invoke(command, args?, options?)`
|
|
73
|
+
- Helpers: `install()`, `update()`, `require()`, `remove()`, `dumpAutoload()`, `packages()`, `stream(handler)`
|
|
74
|
+
|
|
75
|
+
### Git (`notebook.git`)
|
|
76
|
+
|
|
77
|
+
- `checkpoint(author, message, branch = 'main')`
|
|
78
|
+
- `sync(url, author, ref?, token?, direction?, force?)`
|
|
79
|
+
- `diff({ url?, token?, ref?, base?, target? })`
|
|
80
|
+
- `merge({ url?, token?, ref?, target?, expectedBaseRef?, author?, message? })`
|
|
81
|
+
- `conflicts()`
|
|
82
|
+
- `abortMerge()`
|
|
83
|
+
- `continueMerge(author, message?)`
|
|
84
|
+
- `review()`
|
|
85
|
+
- `stage({ path?, paths?, all? })`
|
|
86
|
+
- `unstage({ path?, paths?, all? })`
|
|
87
|
+
- `revert({ path?, paths?, all?, staged? })`
|
|
88
|
+
- `log(ref = 'main')`
|
|
89
|
+
- `restore(ref)`
|
|
90
|
+
|
|
91
|
+
### Mail (`notebook.mail`)
|
|
92
|
+
|
|
93
|
+
- `status()` returning non-sensitive mail configuration state
|
|
94
|
+
- `enable()` enabling SMTP env injection for the notebook
|
|
95
|
+
- `disable()` disabling SMTP env injection and revoking the active SMTP token
|
|
96
|
+
- `list({ page? })` returning paginated notebook mail
|
|
97
|
+
- `get(hash)`
|
|
98
|
+
- `delete(hash)`
|
|
99
|
+
|
|
100
|
+
### LSP (`notebook.lsp`)
|
|
101
|
+
|
|
102
|
+
- Messaging: `start(id)`, `close(id)`, `message(id, payload)`
|
|
103
|
+
- Events: `onResponse()`, `onError()`, `onClose()`, `onClientDisconnect()`
|
|
104
|
+
- Connection wrapper: `connection(id)`
|
|
105
|
+
|
|
106
|
+
### REPL (`notebook.repl`)
|
|
107
|
+
|
|
108
|
+
- `stop()`, `eval(code, args?, replOpts?)`, `write(input)`, `resize(cols, rows)`
|
|
109
|
+
- Events: `listen()`, `onOutput()`
|
|
110
|
+
|
|
111
|
+
### Laravel (`notebook.laravel`)
|
|
112
|
+
|
|
113
|
+
- Wrapper exists in the SDK, but the current okra websocket action set does not expose Laravel maintenance actions.
|
|
114
|
+
|
|
115
|
+
### Auth (`notebook.auth`)
|
|
116
|
+
|
|
117
|
+
- `login(newConnectionData)`
|
|
118
|
+
- `logout()`
|
|
119
|
+
|
|
120
|
+
### Log (`notebook.log`)
|
|
121
|
+
|
|
122
|
+
- `stream(handler)`
|
|
123
|
+
- `listen(event, handler)`
|
|
124
|
+
|
|
125
|
+
## Common Errors
|
|
126
|
+
|
|
127
|
+
- `ApiError` for HTTP API failures
|
|
128
|
+
- `NotebookInitError` when notebook initialization returns `type: 'error'`
|
|
129
|
+
- `FilesystemError` + `FilesystemErrorType.*` for file system operations
|
|
130
|
+
- `RateLimitError` for rate-limited flows
|
|
131
|
+
|
|
132
|
+
## Notes for Consumers
|
|
133
|
+
|
|
134
|
+
- `open()` and `get()` do not auto-initialize; call `await notebook.ready()`.
|
|
135
|
+
- `create()` and `fork()` initialize by default.
|
|
136
|
+
- Always call `notebook.dispose()` when finished.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# CDN Usage Guide
|
|
2
|
+
|
|
3
|
+
The SDK ships browser bundles under `dist/browser`.
|
|
4
|
+
|
|
5
|
+
## Security First
|
|
6
|
+
|
|
7
|
+
Do not hardcode long-lived private API tokens in public frontend code. Use short-lived tokens issued by your backend.
|
|
8
|
+
|
|
9
|
+
## ESM in Browsers
|
|
10
|
+
|
|
11
|
+
### unpkg
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<script type="module">
|
|
15
|
+
import { PHPSandbox } from 'https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js';
|
|
16
|
+
|
|
17
|
+
const client = new PHPSandbox('your-short-lived-token');
|
|
18
|
+
const notebook = await client.notebook.open('your-notebook-id');
|
|
19
|
+
await notebook.ready();
|
|
20
|
+
|
|
21
|
+
const result = await notebook.shell.exec('php -v');
|
|
22
|
+
result.throw();
|
|
23
|
+
console.log(result.output);
|
|
24
|
+
</script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### jsDelivr
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script type="module">
|
|
31
|
+
import { PHPSandbox } from 'https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js';
|
|
32
|
+
|
|
33
|
+
const client = new PHPSandbox('your-short-lived-token');
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Script Tag (IIFE)
|
|
38
|
+
|
|
39
|
+
### unpkg
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<script src="https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.iife.min.js"></script>
|
|
43
|
+
<script>
|
|
44
|
+
const client = new PHPSandbox.PHPSandbox('your-short-lived-token');
|
|
45
|
+
|
|
46
|
+
client.notebook.open('your-notebook-id').then(async (notebook) => {
|
|
47
|
+
await notebook.ready();
|
|
48
|
+
const result = await notebook.shell.exec('php -v');
|
|
49
|
+
console.log(result.output);
|
|
50
|
+
notebook.dispose();
|
|
51
|
+
});
|
|
52
|
+
</script>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### jsDelivr
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<script src="https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.iife.min.js"></script>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Version Pinning
|
|
62
|
+
|
|
63
|
+
Always pin a version in production URLs:
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js
|
|
67
|
+
https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.iife.min.js
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Bundle Files
|
|
71
|
+
|
|
72
|
+
- `phpsandbox-sdk.esm.js`
|
|
73
|
+
- `phpsandbox-sdk.esm.min.js`
|
|
74
|
+
- `phpsandbox-sdk.iife.js`
|
|
75
|
+
- `phpsandbox-sdk.iife.min.js`
|
|
76
|
+
|
|
77
|
+
## Browser Requirements
|
|
78
|
+
|
|
79
|
+
- ES2020-compatible runtime
|
|
80
|
+
- `fetch`, `WebSocket`, and `ReadableStream` support
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Getting Started with PHPSandbox SDK
|
|
2
|
+
|
|
3
|
+
This guide walks through the fastest path from install to a working notebook.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- A PHPSandbox API token
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @phpsandbox/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 1. Create a Client
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { PHPSandbox } from '@phpsandbox/sdk';
|
|
20
|
+
|
|
21
|
+
const token = process.env.PHPSANDBOX_TOKEN;
|
|
22
|
+
if (!token) throw new Error('Missing PHPSANDBOX_TOKEN');
|
|
23
|
+
|
|
24
|
+
const client = new PHPSandbox(token);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 2. Create or Open a Notebook
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// create() initializes by default
|
|
31
|
+
const notebook = await client.notebook.create('php');
|
|
32
|
+
|
|
33
|
+
const persistent = await client.notebook.create('laravel', {
|
|
34
|
+
title: 'Persistent Laravel',
|
|
35
|
+
persistent: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// open()/get() need ready()
|
|
39
|
+
const existing = await client.notebook.open('notebook-id');
|
|
40
|
+
await existing.ready();
|
|
41
|
+
|
|
42
|
+
const fork = await existing.fork();
|
|
43
|
+
const persistentFork = await existing.fork({ persistent: true });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
API notebook creates and forks are ephemeral by default. Use `persistent: true` only when your account is allowed to create persistent notebooks.
|
|
47
|
+
|
|
48
|
+
## 3. Write and Read Files
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
await notebook.file.write('hello.php', '<?php echo "Hello";');
|
|
52
|
+
|
|
53
|
+
const data = await notebook.file.readFile('hello.php');
|
|
54
|
+
const text = new TextDecoder().decode(data);
|
|
55
|
+
|
|
56
|
+
console.log(text);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If you need binary-safe writes, use `writeFile`:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await notebook.file.writeFile('binary.dat', new Uint8Array([1, 2, 3]), {
|
|
63
|
+
create: true,
|
|
64
|
+
overwrite: true,
|
|
65
|
+
unlock: false,
|
|
66
|
+
atomic: false,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 4. Run Commands
|
|
71
|
+
|
|
72
|
+
### Terminal (stream output)
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const task = await notebook.terminal.spawn('php', ['hello.php']);
|
|
76
|
+
const reader = task.output.getReader();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
while (true) {
|
|
80
|
+
const { done, value } = await reader.read();
|
|
81
|
+
if (done) break;
|
|
82
|
+
process.stdout.write(value);
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
reader.releaseLock();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const exitCode = await task.exit;
|
|
89
|
+
console.log(`\nExit code: ${exitCode}`);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Shell (single command result)
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const result = await notebook.shell.exec('php -v');
|
|
96
|
+
result.throw();
|
|
97
|
+
console.log(result.output);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 5. Search and Watch Files
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const matches = await notebook.file.find('*.php', {
|
|
104
|
+
includes: ['app/**'],
|
|
105
|
+
excludes: ['vendor/**'],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(matches.map((m) => m.path));
|
|
109
|
+
|
|
110
|
+
const watcher = notebook.file.watch(
|
|
111
|
+
'/app',
|
|
112
|
+
{
|
|
113
|
+
recursive: true,
|
|
114
|
+
excludes: ['vendor/**', 'node_modules/**'],
|
|
115
|
+
},
|
|
116
|
+
(change) => {
|
|
117
|
+
console.log(change.type, change.path);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// later
|
|
122
|
+
watcher.dispose();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 6. Use Composer and Git
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
await notebook.composer.install({ noInteraction: true });
|
|
129
|
+
await notebook.composer.require({ packages: ['monolog/monolog'] });
|
|
130
|
+
|
|
131
|
+
await notebook.git.checkpoint('Jane Doe <jane@example.com>', 'Initial checkpoint');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 7. Clean Up
|
|
135
|
+
|
|
136
|
+
Always dispose notebook connections when finished:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
notebook.dispose();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Common Pitfalls
|
|
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.
|
|
147
|
+
|
|
148
|
+
## Next Reading
|
|
149
|
+
|
|
150
|
+
- API overview: `docs/api-summary.md`
|
|
151
|
+
- CDN/browser usage: `docs/cdn-usage.md`
|
|
152
|
+
- Root quick start: `README.md`
|