@worldlabsai/cli 0.1.0 → 0.3.0
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/README.md +64 -23
- package/dist/index.js +329 -114
- package/package.json +4 -5
- package/skills/worldlabs/SKILL.md +0 -43
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use the World Labs Developer API from a terminal or coding agent with a manually issued API key. The published package has zero npm runtime dependencies; the public SDK is bundled.
|
|
4
4
|
|
|
5
|
-
With Node
|
|
5
|
+
With Node 22 or newer, install the CLI and configure your key:
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install -g @worldlabsai/cli && worldlabs setup
|
|
@@ -10,7 +10,7 @@ npm install -g @worldlabsai/cli && worldlabs setup
|
|
|
10
10
|
|
|
11
11
|
On Windows, run these commands in Command Prompt.
|
|
12
12
|
|
|
13
|
-
`setup` opens the API keys page, prompts you to paste a new key without echoing it, validates it with `assets.list`, and saves it in a plaintext file in your user configuration directory. It can also install the
|
|
13
|
+
`setup` opens the API keys page, prompts you to paste a new key without echoing it, validates it with `assets.list`, and saves it in a plaintext file in your user configuration directory. It can also install the World Labs skill for a coding agent. Use `worldlabs setup --help` for options.
|
|
14
14
|
|
|
15
15
|
## Onboarding
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ flowchart TD
|
|
|
21
21
|
C --> D[Paste key into hidden prompt]
|
|
22
22
|
D --> E[Validate with assets.list]
|
|
23
23
|
E --> F[Save credentials file]
|
|
24
|
-
F --> G[Optionally install
|
|
24
|
+
F --> G[Optionally install the World Labs skill for your coding agent]
|
|
25
25
|
F --> H[Upload assets and run tasks]
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -29,20 +29,20 @@ On the API keys page, choose only the scopes required for your work: task submis
|
|
|
29
29
|
|
|
30
30
|
## Commands
|
|
31
31
|
|
|
32
|
-
| Command | Purpose
|
|
33
|
-
| ------------------------------------------------------------------------------------------- |
|
|
34
|
-
| `setup [--client
|
|
35
|
-
| `auth login [--no-browser]`
|
|
36
|
-
| `auth status` | Check the key through `assets.list` without printing it. This does not validate every generation scope.
|
|
37
|
-
| `auth logout` | Remove the locally stored key. Revoke the key on the website to invalidate it.
|
|
38
|
-
| `tasks list` | List tasks available through the public SDK.
|
|
39
|
-
| `tasks run <name> --request file.json [--idempotency-key key] [--wait] [--timeout seconds]` | Submit a task request; optionally wait for its operation. Some SDK tasks require beta entitlement.
|
|
40
|
-
| `assets upload file [--mime type]` | Upload a local file.
|
|
41
|
-
| `assets download id --out file` | Download an asset to a local path.
|
|
42
|
-
| `assets list [--page-token token]` | List accessible assets.
|
|
43
|
-
| `operations get id` | Read an operation.
|
|
44
|
-
| `operations wait id [--timeout seconds]` | Poll an operation until it finishes or the timeout expires. The default timeout is 600 seconds.
|
|
45
|
-
| `skills install\|uninstall --client
|
|
32
|
+
| Command | Purpose |
|
|
33
|
+
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `setup [--client <agent>] [--api-key-stdin] [--yes]` | Configure the API key and optionally install the World Labs skill. `--yes` accepts installer prompts. |
|
|
35
|
+
| `auth login [--no-browser] [--api-key-stdin]` | Open the API keys page, or print its URL for manual opening. `--api-key-stdin` reads a piped key. |
|
|
36
|
+
| `auth status` | Check the key through `assets.list` without printing it. This does not validate every generation scope. |
|
|
37
|
+
| `auth logout` | Remove the locally stored key. Revoke the key on the website to invalidate it. |
|
|
38
|
+
| `tasks list` | List tasks available through the public SDK. |
|
|
39
|
+
| `tasks run <name> --request file.json [--idempotency-key key] [--wait] [--timeout seconds]` | Submit a task request; optionally wait for its operation. Some SDK tasks require beta entitlement. |
|
|
40
|
+
| `assets upload file [--mime type]` | Upload a local file. |
|
|
41
|
+
| `assets download id --out file` | Download an asset to a local path. |
|
|
42
|
+
| `assets list [--page-token token]` | List accessible assets. |
|
|
43
|
+
| `operations get id` | Read an operation. |
|
|
44
|
+
| `operations wait id [--timeout seconds]` | Poll an operation until it finishes or the timeout expires. The default timeout is 600 seconds. |
|
|
45
|
+
| `skills install\|uninstall [--client <agent>] [--yes]` | Install or remove the World Labs skill globally. `--yes` accepts installer prompts. |
|
|
46
46
|
|
|
47
47
|
Use `--request -` to read task JSON from standard input. Results go to stdout; diagnostics go to stderr, so a result can be saved without mixing in progress messages:
|
|
48
48
|
|
|
@@ -50,19 +50,60 @@ Use `--request -` to read task JSON from standard input. Results go to stdout; d
|
|
|
50
50
|
worldlabs tasks run atlasGenerate --request - --wait --json < request.json > result.json
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Use `--json` for machine-readable output. Task submission generates an idempotency key and prints it to stderr before sending the request. Save that value and pass it back with `--idempotency-key` to retry an uncertain submission. The bundled `@worldlabsai/client
|
|
53
|
+
Use `--json` for machine-readable output. Task submission generates an idempotency key and prints it to stderr before sending the request. Save that value and pass it back with `--idempotency-key` to retry an uncertain submission. The bundled `@worldlabsai/client` SDK handles API retries and operation polling.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
## Install agent skills
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
[
|
|
57
|
+
Install the World Labs skill from [worldlabsai/skills](https://github.com/worldlabsai/skills)
|
|
58
|
+
using [Skills CLI](https://skills.sh). Installation is global and requires
|
|
59
|
+
network access, npm, and Git. The installer detects available agents and handles
|
|
60
|
+
client selection:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
worldlabs skills install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To choose an agent directly, pass `--client`:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
worldlabs skills install --client cursor
|
|
70
|
+
worldlabs skills install --client gemini-cli
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Pass `--yes` to accept installer prompts. Run the install command again to
|
|
74
|
+
refresh the World Labs skill. To remove it, select the agent you installed it
|
|
75
|
+
for:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
worldlabs skills uninstall --client cursor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Start a new agent session after installation to load the skill. Older
|
|
82
|
+
CLI-managed installations are moved to a local backup, whose path is printed
|
|
83
|
+
in the terminal. If installation fails or is cancelled, the CLI waits for the
|
|
84
|
+
installer to stop, then restores the old skill unless
|
|
85
|
+
the installer has already written a replacement; in that case, it keeps both
|
|
86
|
+
copies and reports the backup path. If an older installation has local edits,
|
|
87
|
+
installation stops and leaves it in place.
|
|
88
|
+
|
|
89
|
+
If the CLI cannot confirm that the installer has stopped, it retains the backup
|
|
90
|
+
without restoring it. Stop any remaining installer processes before restoring
|
|
91
|
+
that backup manually.
|
|
92
|
+
|
|
93
|
+
The public skill points to [How To Use Atlas](https://docs.atlas-beta.worldlabs.ai/how-to-use-atlas) for maintained guidance on model inputs, cameras, and task combinations.
|
|
59
94
|
|
|
60
95
|
## Configuration
|
|
61
96
|
|
|
62
97
|
The CLI uses `https://api.atlas-beta.worldlabs.ai` by default. Saved keys live in separate files per API origin under `~/.config/worldlabs/credentials/`. On macOS/Linux, new directories use mode `700` and new files use mode `600`. On Windows, files inherit the directory permissions. Keys are plaintext, so programs running as your user can read them. `auth status` shows the saved file location.
|
|
63
98
|
|
|
64
|
-
Set `WORLDLABS_CONFIG_DIR` to use another configuration directory, such as a temporary directory for tests. Keep it outside repositories. `WLT_API_KEY` takes precedence over saved keys for automation.
|
|
99
|
+
Set `WORLDLABS_CONFIG_DIR` to use another configuration directory, such as a temporary directory for tests. Keep it outside repositories. `WLT_API_KEY` takes precedence over saved keys for automation.
|
|
100
|
+
|
|
101
|
+
To save a key without the hidden prompt, for example from a secret manager or where no one can type into the terminal, pipe it to `--api-key-stdin`. The CLI checks it and saves it like `auth login`; the key stays out of the command line and shell history:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
worldlabs auth login --api-key-stdin < key.txt
|
|
105
|
+
```
|
|
65
106
|
|
|
66
107
|
For development or a nonproduction deployment, set `WORLDLABS_API_ORIGIN` and, when needed, `WORLDLABS_API_KEYS_URL`. The key page defaults to `https://atlas-beta.worldlabs.ai/api-keys`. Do not commit credentials, client IDs, or nonproduction hostnames.
|
|
67
108
|
|
|
68
|
-
See the [World Labs
|
|
109
|
+
See the [World Labs skill](https://github.com/worldlabsai/skills).
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
6
6
|
|
|
7
|
+
// ../client/dist/core/network-error.js
|
|
8
|
+
var NetworkError = class extends TypeError {
|
|
9
|
+
name = "NetworkError";
|
|
10
|
+
constructor(cause) {
|
|
11
|
+
super(cause.message, { cause });
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
// ../client/dist/core/sleep.js
|
|
8
16
|
var sleep = async (ms, signal) => {
|
|
9
17
|
if (signal?.aborted) {
|
|
@@ -21,6 +29,11 @@ var sleep = async (ms, signal) => {
|
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
// ../client/dist/core/transport.js
|
|
32
|
+
var validationIssue = (issue) => {
|
|
33
|
+
const location = typeof issue === "object" && issue !== null ? Reflect.get(issue, "loc") : void 0;
|
|
34
|
+
const message = typeof issue === "object" && issue !== null ? Reflect.get(issue, "msg") : issue;
|
|
35
|
+
return Array.isArray(location) ? `${location.join(".")}: ${String(message)}` : String(message);
|
|
36
|
+
};
|
|
24
37
|
var errorDetail = (body) => {
|
|
25
38
|
if (typeof body !== "object" || body === null) {
|
|
26
39
|
return void 0;
|
|
@@ -30,6 +43,9 @@ var errorDetail = (body) => {
|
|
|
30
43
|
if (typeof value === "string" && value !== "") {
|
|
31
44
|
return value;
|
|
32
45
|
}
|
|
46
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
47
|
+
return value.map((issue) => validationIssue(issue)).join("; ");
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
return void 0;
|
|
35
51
|
};
|
|
@@ -56,35 +72,36 @@ var fillPath = (path, params) => path.replaceAll(/\{(?<name>\w+)\}/gu, (_, name)
|
|
|
56
72
|
}
|
|
57
73
|
return encodeURIComponent(value);
|
|
58
74
|
});
|
|
59
|
-
var compose = (middleware, fetch2) => {
|
|
75
|
+
var compose = (middleware, fetch2, options) => {
|
|
60
76
|
let next = fetch2;
|
|
61
77
|
for (const layer of middleware.toReversed()) {
|
|
62
78
|
const inner = next;
|
|
63
|
-
next = async (request) => await layer(request, inner);
|
|
79
|
+
next = async (request) => await layer(request, inner, options);
|
|
64
80
|
}
|
|
65
81
|
return next;
|
|
66
82
|
};
|
|
67
83
|
var parseBody = async (response) => {
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
84
|
+
const text3 = await response.text();
|
|
85
|
+
if (text3 === "") {
|
|
70
86
|
return void 0;
|
|
71
87
|
}
|
|
72
88
|
try {
|
|
73
|
-
return JSON.parse(
|
|
89
|
+
return JSON.parse(text3);
|
|
74
90
|
} catch {
|
|
75
|
-
return
|
|
91
|
+
return text3;
|
|
76
92
|
}
|
|
77
93
|
};
|
|
78
94
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
79
|
-
var
|
|
80
|
-
|
|
95
|
+
var MAX_RETRY_AFTER_MS = 1e4;
|
|
96
|
+
var retryAfterMs = (error) => {
|
|
81
97
|
const value = error instanceof ApiError ? error.response.headers.get("retry-after")?.trim() : void 0;
|
|
82
98
|
if (!value) {
|
|
83
|
-
return
|
|
99
|
+
return void 0;
|
|
84
100
|
}
|
|
85
101
|
const delay = /^\d+$/u.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
|
|
86
|
-
return
|
|
102
|
+
return Number.isFinite(delay) ? Math.max(0, delay) : void 0;
|
|
87
103
|
};
|
|
104
|
+
var retryDelay = (error, attempt) => Math.max(1e3 * 2 ** attempt * (0.5 + Math.random() / 2), retryAfterMs(error) ?? 0);
|
|
88
105
|
var createTransport = (options) => {
|
|
89
106
|
const fetch2 = options.fetch ?? (async (request) => await globalThis.fetch(request));
|
|
90
107
|
const send = compose(options.middleware ?? [], fetch2);
|
|
@@ -93,7 +110,8 @@ var createTransport = (options) => {
|
|
|
93
110
|
return {
|
|
94
111
|
fetch: fetch2,
|
|
95
112
|
send,
|
|
96
|
-
request: async (path,
|
|
113
|
+
request: async (path, requestOptions = {}) => {
|
|
114
|
+
const { body, params, query, retry = false, ...init } = requestOptions;
|
|
97
115
|
const url = new URL(baseUrl + fillPath(path, { ...boundParams, ...params }));
|
|
98
116
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
99
117
|
if (value !== void 0 && value !== null) {
|
|
@@ -110,14 +128,14 @@ var createTransport = (options) => {
|
|
|
110
128
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
111
129
|
});
|
|
112
130
|
let networkError;
|
|
113
|
-
const sendRequest =
|
|
131
|
+
const sendRequest = compose(options.middleware ?? [], async (attempt) => {
|
|
114
132
|
try {
|
|
115
133
|
return await fetch2(attempt);
|
|
116
134
|
} catch (error) {
|
|
117
135
|
networkError = error;
|
|
118
136
|
throw error;
|
|
119
137
|
}
|
|
120
|
-
})
|
|
138
|
+
}, requestOptions);
|
|
121
139
|
for (let attempt = 0; ; attempt += 1) {
|
|
122
140
|
request.signal.throwIfAborted();
|
|
123
141
|
networkError = void 0;
|
|
@@ -139,8 +157,11 @@ var createTransport = (options) => {
|
|
|
139
157
|
return parsed;
|
|
140
158
|
} catch (error) {
|
|
141
159
|
request.signal.throwIfAborted();
|
|
142
|
-
const
|
|
160
|
+
const networkFailure = error instanceof TypeError && error === networkError;
|
|
161
|
+
const transient = error instanceof ApiError ? RETRYABLE_STATUS_CODES.has(error.response.status) && (retryAfterMs(error) ?? 0) <= MAX_RETRY_AFTER_MS : networkFailure;
|
|
143
162
|
if (!retry || attempt >= 3 || !transient) {
|
|
163
|
+
if (networkFailure)
|
|
164
|
+
throw new NetworkError(error);
|
|
144
165
|
throw error;
|
|
145
166
|
}
|
|
146
167
|
await sleep(retryDelay(error, attempt), request.signal);
|
|
@@ -281,6 +302,7 @@ var createOperations = (transport, operationPath2) => {
|
|
|
281
302
|
*
|
|
282
303
|
* An operation can finish with an API-reported task error; inspect `error` before
|
|
283
304
|
* reading `response`. Aborting stops local waiting without canceling the task.
|
|
305
|
+
* With `retryNetworkErrors`, network failures resume polling until cancellation.
|
|
284
306
|
* HTTP 502, 503, and 504 polling failures are retried up to three times in a row;
|
|
285
307
|
* a successful poll resets the retry count.
|
|
286
308
|
*
|
|
@@ -303,6 +325,9 @@ var createOperations = (transport, operationPath2) => {
|
|
|
303
325
|
gatewayErrors = 0;
|
|
304
326
|
return current;
|
|
305
327
|
} catch (error) {
|
|
328
|
+
signal?.throwIfAborted();
|
|
329
|
+
if (options?.retryNetworkErrors && error instanceof NetworkError)
|
|
330
|
+
return operation;
|
|
306
331
|
gatewayErrors += 1;
|
|
307
332
|
if (!(error instanceof ApiError) || ![502, 503, 504].includes(error.response.status) || gatewayErrors > 3) {
|
|
308
333
|
throw error;
|
|
@@ -315,7 +340,7 @@ var createOperations = (transport, operationPath2) => {
|
|
|
315
340
|
};
|
|
316
341
|
|
|
317
342
|
// ../client/dist/core/tasks.js
|
|
318
|
-
var submitTask = async (transport, path, body, { idempotencyKey = crypto.randomUUID(), signal, wait } = {}) => {
|
|
343
|
+
var submitTask = async (transport, path, body, { idempotencyKey = crypto.randomUUID(), signal, wait, retry = true } = {}) => {
|
|
319
344
|
if (idempotencyKey.trim() === "") {
|
|
320
345
|
throw new Error("Idempotency key must not be empty");
|
|
321
346
|
}
|
|
@@ -324,7 +349,7 @@ var submitTask = async (transport, path, body, { idempotencyKey = crypto.randomU
|
|
|
324
349
|
headers: { "Idempotency-Key": idempotencyKey },
|
|
325
350
|
method: "POST",
|
|
326
351
|
query: { wait },
|
|
327
|
-
retry
|
|
352
|
+
retry,
|
|
328
353
|
...signal ? { signal } : {}
|
|
329
354
|
});
|
|
330
355
|
};
|
|
@@ -393,6 +418,18 @@ var createTasks = (transport) => ({
|
|
|
393
418
|
* @beta Requires beta access for your account.
|
|
394
419
|
*/
|
|
395
420
|
images2PosedRGBD: async (body, options) => await submitTask(transport, "/api/v2/tasks:images2PosedRGBD", body, options),
|
|
421
|
+
/**
|
|
422
|
+
* Submit the posedImages2Splats task
|
|
423
|
+
* Retries network failures and HTTP 429/502/503/504 up to three times with the same key and body.
|
|
424
|
+
*
|
|
425
|
+
* @param body - Task inputs and settings.
|
|
426
|
+
* @param options - Abort signal, idempotency key, and server-wait preference.
|
|
427
|
+
* @returns An operation to pass to `client.operations.wait` for the typed result.
|
|
428
|
+
* Check the completed operation's `error` before reading its `response`.
|
|
429
|
+
* @throws {ApiError} If the API rejects the submission or retries are exhausted.
|
|
430
|
+
* @beta Requires beta access for your account.
|
|
431
|
+
*/
|
|
432
|
+
posedImages2Splats: async (body, options) => await submitTask(transport, "/api/v2/tasks:posedImages2Splats", body, options),
|
|
396
433
|
/**
|
|
397
434
|
* Submit the splats2Mesh task
|
|
398
435
|
* Retries network failures and HTTP 429/502/503/504 up to three times with the same key and body.
|
|
@@ -429,6 +466,9 @@ var createClient = (options) => {
|
|
|
429
466
|
};
|
|
430
467
|
};
|
|
431
468
|
|
|
469
|
+
// src/auth.ts
|
|
470
|
+
import { text } from "node:stream/consumers";
|
|
471
|
+
|
|
432
472
|
// src/config.ts
|
|
433
473
|
var CliError = class extends Error {
|
|
434
474
|
name = "CliError";
|
|
@@ -529,13 +569,20 @@ async function prompt(message, hidden = false) {
|
|
|
529
569
|
terminal: true
|
|
530
570
|
});
|
|
531
571
|
const controller = new AbortController();
|
|
532
|
-
|
|
572
|
+
let interrupted = false;
|
|
573
|
+
reader.once("SIGINT", () => {
|
|
574
|
+
interrupted = true;
|
|
575
|
+
controller.abort();
|
|
576
|
+
});
|
|
577
|
+
reader.once("close", () => controller.abort());
|
|
533
578
|
process.stderr.write(message);
|
|
534
579
|
try {
|
|
535
580
|
const answer = await reader.question("", { signal: controller.signal });
|
|
536
581
|
return answer.trim();
|
|
537
582
|
} catch {
|
|
538
|
-
throw new CliError(
|
|
583
|
+
throw new CliError(
|
|
584
|
+
interrupted ? "Cancelled." : "Input closed before an answer. For automation, set WLT_API_KEY or pipe the key to worldlabs auth login --api-key-stdin."
|
|
585
|
+
);
|
|
539
586
|
} finally {
|
|
540
587
|
reader.close();
|
|
541
588
|
if (hidden) process.stderr.write("\n");
|
|
@@ -573,12 +620,25 @@ async function authStatus() {
|
|
|
573
620
|
...process.env.WLT_API_KEY?.trim() ? {} : { credentialFile: credentialPath(configuration().apiOrigin) }
|
|
574
621
|
};
|
|
575
622
|
}
|
|
576
|
-
async function
|
|
623
|
+
async function pipedApiKey() {
|
|
624
|
+
if (process.stdin.isTTY) {
|
|
625
|
+
throw new CliError(
|
|
626
|
+
"--api-key-stdin reads a piped key, for example: worldlabs auth login --api-key-stdin < key.txt"
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const input = await text(process.stdin);
|
|
630
|
+
const apiKey = input.trim();
|
|
631
|
+
if (!apiKey) throw new CliError("No API key on standard input.");
|
|
632
|
+
if (/\s/u.test(apiKey)) throw new CliError("Standard input must contain only the API key.");
|
|
633
|
+
return apiKey;
|
|
634
|
+
}
|
|
635
|
+
async function login(noBrowser, pipedKey) {
|
|
636
|
+
if (pipedKey) return await saveValidatedKey(pipedKey);
|
|
577
637
|
if (process.env.WLT_API_KEY?.trim()) return await authStatus();
|
|
578
638
|
const { apiOrigin, apiKeysUrl } = configuration();
|
|
579
639
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
580
640
|
throw new CliError(
|
|
581
|
-
"Run worldlabs auth login in your own terminal to paste a key privately. Agents should never ask for keys in chat."
|
|
641
|
+
"Run worldlabs auth login in your own terminal to paste a key privately. Agents should never ask for keys in chat. To read a key from a file or secret manager instead, pipe it to worldlabs auth login --api-key-stdin."
|
|
582
642
|
);
|
|
583
643
|
}
|
|
584
644
|
process.stderr.write(
|
|
@@ -591,6 +651,10 @@ The key will be saved in plaintext at ${credentialPath(apiOrigin)}.
|
|
|
591
651
|
if (!noBrowser) await openBrowser(apiKeysUrl);
|
|
592
652
|
const apiKey = await prompt("Paste API key (hidden): ", true);
|
|
593
653
|
if (!apiKey) throw new CliError("No key entered.");
|
|
654
|
+
return await saveValidatedKey(apiKey);
|
|
655
|
+
}
|
|
656
|
+
async function saveValidatedKey(apiKey) {
|
|
657
|
+
const { apiOrigin } = configuration();
|
|
594
658
|
await createClient({ apiKey, baseUrl: apiOrigin }).assets.list({
|
|
595
659
|
pageSize: 1,
|
|
596
660
|
signal: AbortSignal.timeout(3e4)
|
|
@@ -618,7 +682,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
618
682
|
import { openAsBlob } from "node:fs";
|
|
619
683
|
import { open as open2, readFile, rm as rm2 } from "node:fs/promises";
|
|
620
684
|
import nodePath2 from "node:path";
|
|
621
|
-
import { text } from "node:stream/consumers";
|
|
685
|
+
import { text as text2 } from "node:stream/consumers";
|
|
622
686
|
import { pipeline } from "node:stream/promises";
|
|
623
687
|
import { parseArgs } from "node:util";
|
|
624
688
|
var mimeTypes = {
|
|
@@ -626,9 +690,11 @@ var mimeTypes = {
|
|
|
626
690
|
".jpeg": "image/jpeg",
|
|
627
691
|
".png": "image/png",
|
|
628
692
|
".webp": "image/webp",
|
|
693
|
+
".exr": "image/x-exr",
|
|
629
694
|
".mp4": "video/mp4",
|
|
630
|
-
".
|
|
631
|
-
".
|
|
695
|
+
".glb": "model/gltf-binary",
|
|
696
|
+
".ply": "model/x-ply",
|
|
697
|
+
".spz": "model/x-spz"
|
|
632
698
|
};
|
|
633
699
|
function timeout(value) {
|
|
634
700
|
const seconds = value === void 0 ? 600 : Number(value);
|
|
@@ -665,7 +731,7 @@ async function runApiCommand(args2) {
|
|
|
665
731
|
throw new CliError("Unknown task. Run worldlabs tasks list.");
|
|
666
732
|
}
|
|
667
733
|
const body = JSON.parse(
|
|
668
|
-
values.request === "-" ? await
|
|
734
|
+
values.request === "-" ? await text2(process.stdin) : await readFile(values.request, "utf8")
|
|
669
735
|
);
|
|
670
736
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
671
737
|
throw new CliError("Task request must be a JSON object.");
|
|
@@ -759,24 +825,30 @@ Reuse this key and request if submission is interrupted.
|
|
|
759
825
|
throw new CliError("Unknown command or arguments. Run worldlabs --help.");
|
|
760
826
|
}
|
|
761
827
|
|
|
828
|
+
// src/node-version.ts
|
|
829
|
+
var MINIMUM_NODE_MAJOR = 22;
|
|
830
|
+
function nodeVersionProblem(version2) {
|
|
831
|
+
if (Number(version2.split(".")[0]) >= MINIMUM_NODE_MAJOR) return void 0;
|
|
832
|
+
return `worldlabs requires Node.js ${MINIMUM_NODE_MAJOR} or newer, but this is Node.js ${version2}. Install the current Node.js LTS from https://nodejs.org and run the command again.`;
|
|
833
|
+
}
|
|
834
|
+
|
|
762
835
|
// src/skills.ts
|
|
763
|
-
import {
|
|
764
|
-
import {
|
|
836
|
+
import { execFile, spawn as spawn2 } from "node:child_process";
|
|
837
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
838
|
+
import { lstat as lstat2, mkdtemp, readFile as readFile2, readdir, rename as rename2, rmdir } from "node:fs/promises";
|
|
765
839
|
import { homedir as homedir2 } from "node:os";
|
|
766
840
|
import nodePath3 from "node:path";
|
|
767
|
-
import { fileURLToPath } from "node:url";
|
|
768
841
|
var marker = ".worldlabs-install.json";
|
|
769
|
-
var
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
"
|
|
842
|
+
var InstallerTerminationError = class extends CliError {
|
|
843
|
+
name = "InstallerTerminationError";
|
|
844
|
+
};
|
|
845
|
+
function skillClient(client) {
|
|
846
|
+
if (client !== void 0 && !/^[a-z0-9][a-z0-9-]*$/u.test(client)) {
|
|
847
|
+
throw new CliError(
|
|
848
|
+
"--client must be an agent name, such as codex, claude-code, cursor, or gemini-cli."
|
|
776
849
|
);
|
|
777
850
|
}
|
|
778
|
-
|
|
779
|
-
throw new CliError("Choose --client codex or --client claude-code.");
|
|
851
|
+
return client;
|
|
780
852
|
}
|
|
781
853
|
async function fingerprint(root) {
|
|
782
854
|
const hash = createHash2("sha256");
|
|
@@ -797,93 +869,213 @@ async function fingerprint(root) {
|
|
|
797
869
|
await visit("");
|
|
798
870
|
return hash.digest("hex");
|
|
799
871
|
}
|
|
800
|
-
async function
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
|
|
872
|
+
async function cleanupLegacySkill(client) {
|
|
873
|
+
const root = client === "codex" ? process.env.CODEX_HOME ?? nodePath3.join(homedir2(), ".codex") : nodePath3.join(homedir2(), ".claude");
|
|
874
|
+
const target = nodePath3.join(root, "skills", "worldlabs");
|
|
875
|
+
let stat;
|
|
804
876
|
try {
|
|
805
|
-
await
|
|
806
|
-
} catch {
|
|
877
|
+
stat = await lstat2(target);
|
|
878
|
+
} catch (error) {
|
|
879
|
+
if (error.code === "ENOENT") return;
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
882
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
|
883
|
+
let expected;
|
|
884
|
+
try {
|
|
885
|
+
expected = JSON.parse(await readFile2(nodePath3.join(target, marker), "utf8"));
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (error.code === "ENOENT") return;
|
|
888
|
+
throw new CliError(
|
|
889
|
+
`Cannot verify the old skill at ${target}. Move it aside before continuing.`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
if (await fingerprint(target) !== expected) {
|
|
807
893
|
throw new CliError(
|
|
808
|
-
`
|
|
894
|
+
`Old skill has local changes at ${target}. Move it aside before continuing.`
|
|
809
895
|
);
|
|
810
896
|
}
|
|
811
|
-
const
|
|
812
|
-
|
|
897
|
+
const backupContainer = await mkdtemp(
|
|
898
|
+
nodePath3.join(nodePath3.dirname(target), ".worldlabs-backup-")
|
|
899
|
+
);
|
|
900
|
+
const backup = nodePath3.join(backupContainer, "worldlabs");
|
|
813
901
|
try {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
902
|
+
await rename2(target, backup);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
await rmdir(backupContainer).catch(() => null);
|
|
905
|
+
throw error;
|
|
906
|
+
}
|
|
907
|
+
process.stderr.write(`Moved the old CLI-managed skill to ${backup}
|
|
908
|
+
`);
|
|
909
|
+
return { target, backup };
|
|
910
|
+
}
|
|
911
|
+
function runSkillsInstaller(args2, signal) {
|
|
912
|
+
signal.throwIfAborted();
|
|
913
|
+
const env = { ...process.env, DISABLE_TELEMETRY: "1" };
|
|
914
|
+
delete env.WLT_API_KEY;
|
|
915
|
+
const windows = process.platform === "win32";
|
|
916
|
+
return new Promise((resolve, reject) => {
|
|
917
|
+
const child = spawn2(
|
|
918
|
+
windows ? "cmd.exe" : "npx",
|
|
919
|
+
windows ? ["/d", "/s", "/c", `npx.cmd ${args2.join(" ")}`] : args2,
|
|
920
|
+
{ env, detached: !windows, stdio: ["inherit", "pipe", "pipe"] }
|
|
921
|
+
);
|
|
922
|
+
child.stdout.pipe(process.stderr, { end: false });
|
|
923
|
+
child.stderr.pipe(process.stderr, { end: false });
|
|
924
|
+
let forceTimer;
|
|
925
|
+
let stopTimer;
|
|
926
|
+
let settled = false;
|
|
927
|
+
const finish = (error) => {
|
|
928
|
+
if (settled) return;
|
|
929
|
+
settled = true;
|
|
930
|
+
clearTimeout(forceTimer);
|
|
931
|
+
clearTimeout(stopTimer);
|
|
932
|
+
child.stdout.unpipe(process.stderr);
|
|
933
|
+
child.stderr.unpipe(process.stderr);
|
|
934
|
+
signal.removeEventListener("abort", cancel);
|
|
935
|
+
if (error) reject(error);
|
|
936
|
+
else resolve();
|
|
937
|
+
};
|
|
938
|
+
const cannotStop = () => {
|
|
939
|
+
child.stdout.destroy();
|
|
940
|
+
child.stderr.destroy();
|
|
941
|
+
child.unref();
|
|
942
|
+
finish(
|
|
943
|
+
new InstallerTerminationError(
|
|
944
|
+
"Could not stop the skills installer. Legacy backups were retained; stop the installer before restoring them."
|
|
945
|
+
)
|
|
946
|
+
);
|
|
947
|
+
};
|
|
948
|
+
const killGroup = (terminationSignal) => {
|
|
949
|
+
if (!child.pid) return;
|
|
824
950
|
try {
|
|
825
|
-
|
|
826
|
-
} catch {
|
|
827
|
-
|
|
951
|
+
process.kill(-child.pid, terminationSignal);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
if (error.code !== "ESRCH") cannotStop();
|
|
828
954
|
}
|
|
829
|
-
|
|
830
|
-
|
|
955
|
+
};
|
|
956
|
+
const cancel = () => {
|
|
957
|
+
if (!child.pid) return;
|
|
958
|
+
stopTimer = setTimeout(cannotStop, 5e3).unref();
|
|
959
|
+
if (windows) {
|
|
960
|
+
execFile(
|
|
961
|
+
"taskkill",
|
|
962
|
+
["/PID", String(child.pid), "/T", "/F"],
|
|
963
|
+
{ timeout: 4e3 },
|
|
964
|
+
(error) => {
|
|
965
|
+
if (error && !settled) cannotStop();
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
} else {
|
|
969
|
+
forceTimer = setTimeout(() => killGroup("SIGKILL"), 2e3).unref();
|
|
970
|
+
killGroup(signal.reason);
|
|
831
971
|
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
new URL(import.meta.url.endsWith(".ts") ? "index.ts" : "index.js", import.meta.url)
|
|
972
|
+
};
|
|
973
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
974
|
+
child.once(
|
|
975
|
+
"error",
|
|
976
|
+
() => finish(
|
|
977
|
+
new CliError("Could not start the skills installer. Check that npm and npx are available.")
|
|
978
|
+
)
|
|
840
979
|
);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
980
|
+
child.once(
|
|
981
|
+
"close",
|
|
982
|
+
(code) => finish(
|
|
983
|
+
code === 0 && !signal.aborted ? void 0 : new CliError("Skills installer did not complete. See its output above.")
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
async function manageSkills(action, client, yes = false) {
|
|
989
|
+
skillClient(client);
|
|
990
|
+
const moved = [];
|
|
991
|
+
const controller = new AbortController();
|
|
992
|
+
const cancel = (signal) => controller.abort(signal);
|
|
993
|
+
process.on("SIGINT", cancel);
|
|
994
|
+
process.on("SIGTERM", cancel);
|
|
995
|
+
try {
|
|
996
|
+
for (const candidate of client ? [client] : ["codex", "claude-code"]) {
|
|
997
|
+
if (candidate === "codex" || candidate === "claude-code") {
|
|
998
|
+
const record = await cleanupLegacySkill(candidate);
|
|
999
|
+
if (record) moved.push(record);
|
|
854
1000
|
}
|
|
1001
|
+
if (controller.signal.aborted) throw new CliError("Skills installation cancelled.");
|
|
855
1002
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1003
|
+
const args2 = [
|
|
1004
|
+
"--yes",
|
|
1005
|
+
"skills@1.7.2",
|
|
1006
|
+
...action === "install" ? ["add", "worldlabsai/skills", "--skill", "worldlabs"] : ["remove", "worldlabs"],
|
|
1007
|
+
...client ? ["--agent", client] : [],
|
|
1008
|
+
"--global",
|
|
1009
|
+
...yes ? ["--yes"] : []
|
|
1010
|
+
];
|
|
1011
|
+
await runSkillsInstaller(args2, controller.signal);
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (error instanceof InstallerTerminationError) throw error;
|
|
1014
|
+
for (const { target, backup } of moved.toReversed()) {
|
|
1015
|
+
try {
|
|
1016
|
+
await lstat2(target);
|
|
1017
|
+
process.stderr.write(
|
|
1018
|
+
`Could not restore the old skill because a skill now exists at ${target}. Backup retained at ${backup}
|
|
1019
|
+
`
|
|
1020
|
+
);
|
|
1021
|
+
} catch (restoreCheckError) {
|
|
1022
|
+
if (restoreCheckError.code !== "ENOENT") {
|
|
1023
|
+
process.stderr.write(
|
|
1024
|
+
`Could not check the old skill location ${target}. Backup retained at ${backup}
|
|
1025
|
+
`
|
|
1026
|
+
);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
try {
|
|
1030
|
+
await rename2(backup, target);
|
|
1031
|
+
await rmdir(nodePath3.dirname(backup)).catch(() => null);
|
|
1032
|
+
} catch {
|
|
1033
|
+
process.stderr.write(
|
|
1034
|
+
`Could not restore the old skill at ${target}. Backup retained at ${backup}
|
|
1035
|
+
`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
863
1039
|
}
|
|
864
|
-
|
|
865
|
-
return { path: target, next: "Start a new agent session to load the worldlabs skill." };
|
|
1040
|
+
throw error;
|
|
866
1041
|
} finally {
|
|
867
|
-
|
|
868
|
-
|
|
1042
|
+
process.off("SIGINT", cancel);
|
|
1043
|
+
process.off("SIGTERM", cancel);
|
|
1044
|
+
if (controller.signal.aborted) {
|
|
1045
|
+
process.kill(process.pid, controller.signal.reason);
|
|
1046
|
+
}
|
|
869
1047
|
}
|
|
1048
|
+
return {
|
|
1049
|
+
client,
|
|
1050
|
+
...moved.length ? { legacyBackups: moved.map(({ backup }) => backup) } : {},
|
|
1051
|
+
next: "Start a new agent session after changing skills."
|
|
1052
|
+
};
|
|
870
1053
|
}
|
|
871
1054
|
|
|
872
1055
|
// src/setup.ts
|
|
873
|
-
async function setup(client, noBrowser) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1056
|
+
async function setup(client, noBrowser, yes = false, keyFromStdin = false) {
|
|
1057
|
+
const target = skillClient(client);
|
|
1058
|
+
const pipedKey = keyFromStdin ? await pipedApiKey() : void 0;
|
|
1059
|
+
let installSkill = target !== void 0;
|
|
1060
|
+
if (!installSkill && process.stdin.isTTY && process.stderr.isTTY) {
|
|
1061
|
+
const answer = await prompt("Install the World Labs agent skill? [y/N]: ");
|
|
1062
|
+
const choice = answer.toLowerCase();
|
|
1063
|
+
if (choice && !["y", "yes", "n", "no"].includes(choice)) {
|
|
1064
|
+
throw new CliError("Choose yes or no.");
|
|
1065
|
+
}
|
|
1066
|
+
installSkill = choice === "y" || choice === "yes";
|
|
1067
|
+
}
|
|
1068
|
+
const auth = await login(noBrowser, pipedKey);
|
|
1069
|
+
let skill;
|
|
1070
|
+
if (installSkill) {
|
|
1071
|
+
try {
|
|
1072
|
+
skill = await manageSkills("install", target, yes);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
throw new CliError(
|
|
1075
|
+
`API authentication succeeded, but skill installation failed. ${error instanceof Error ? error.message : "Retry worldlabs skills install."}`
|
|
1076
|
+
);
|
|
883
1077
|
}
|
|
884
1078
|
}
|
|
885
|
-
const auth = await login(noBrowser);
|
|
886
|
-
const skill = target ? await manageSkills("install", target) : void 0;
|
|
887
1079
|
return {
|
|
888
1080
|
...auth,
|
|
889
1081
|
...skill ? { skill } : {},
|
|
@@ -898,9 +1090,9 @@ var { version } = JSON.parse(
|
|
|
898
1090
|
var help = `worldlabs - World Labs Developer API CLI
|
|
899
1091
|
|
|
900
1092
|
Usage: worldlabs [--json] <command>
|
|
901
|
-
setup [--client
|
|
902
|
-
skills install|uninstall --client
|
|
903
|
-
auth login [--no-browser]
|
|
1093
|
+
setup [--client <agent>] [--no-browser] [--api-key-stdin] [--yes]
|
|
1094
|
+
skills install|uninstall [--client <agent>] [--yes]
|
|
1095
|
+
auth login [--no-browser] [--api-key-stdin]
|
|
904
1096
|
auth status
|
|
905
1097
|
auth logout
|
|
906
1098
|
config show
|
|
@@ -913,30 +1105,53 @@ Usage: worldlabs [--json] <command>
|
|
|
913
1105
|
--version
|
|
914
1106
|
--help
|
|
915
1107
|
|
|
1108
|
+
Skills use Skills CLI client detection when --client is omitted (e.g. codex, claude-code, cursor, gemini-cli).
|
|
916
1109
|
Use --request - to read task JSON from standard input.
|
|
917
1110
|
|
|
918
|
-
Saved login uses a plaintext file in your user configuration directory. Automation can set WLT_API_KEY
|
|
1111
|
+
Saved login uses a plaintext file in your user configuration directory. Automation can set WLT_API_KEY,
|
|
1112
|
+
or save a key without the prompt by piping it to --api-key-stdin.
|
|
919
1113
|
`;
|
|
920
1114
|
async function main(args2) {
|
|
1115
|
+
const unsupported = nodeVersionProblem(process.versions.node);
|
|
1116
|
+
if (unsupported) throw new CliError(unsupported);
|
|
921
1117
|
if (args2.length === 0 || args2.includes("--help")) return help;
|
|
922
1118
|
if (args2.length === 1 && args2[0] === "--version") return version;
|
|
923
1119
|
const [command, action, ...rest] = args2;
|
|
924
1120
|
if (command === "setup") {
|
|
925
1121
|
const { values } = parseArgs2({
|
|
926
1122
|
args: args2.slice(1),
|
|
927
|
-
options: {
|
|
1123
|
+
options: {
|
|
1124
|
+
client: { type: "string" },
|
|
1125
|
+
"no-browser": { type: "boolean" },
|
|
1126
|
+
"api-key-stdin": { type: "boolean" },
|
|
1127
|
+
yes: { type: "boolean" }
|
|
1128
|
+
}
|
|
928
1129
|
});
|
|
929
|
-
return await setup(
|
|
1130
|
+
return await setup(
|
|
1131
|
+
values.client,
|
|
1132
|
+
Boolean(values["no-browser"]),
|
|
1133
|
+
Boolean(values.yes),
|
|
1134
|
+
Boolean(values["api-key-stdin"])
|
|
1135
|
+
);
|
|
930
1136
|
}
|
|
931
1137
|
if (command === "skills" && (action === "install" || action === "uninstall")) {
|
|
932
|
-
const { values } = parseArgs2({
|
|
933
|
-
|
|
1138
|
+
const { values } = parseArgs2({
|
|
1139
|
+
args: rest,
|
|
1140
|
+
options: { client: { type: "string" }, yes: { type: "boolean" } }
|
|
1141
|
+
});
|
|
1142
|
+
return await manageSkills(action, skillClient(values.client), Boolean(values.yes));
|
|
934
1143
|
}
|
|
935
1144
|
if (command === "config" && action === "show" && !rest.length) return configuration();
|
|
936
1145
|
if (command === "auth") {
|
|
937
1146
|
if (action === "login") {
|
|
938
|
-
const { values } = parseArgs2({
|
|
939
|
-
|
|
1147
|
+
const { values } = parseArgs2({
|
|
1148
|
+
args: rest,
|
|
1149
|
+
options: { "no-browser": { type: "boolean" }, "api-key-stdin": { type: "boolean" } }
|
|
1150
|
+
});
|
|
1151
|
+
return await login(
|
|
1152
|
+
Boolean(values["no-browser"]),
|
|
1153
|
+
values["api-key-stdin"] ? await pipedApiKey() : void 0
|
|
1154
|
+
);
|
|
940
1155
|
}
|
|
941
1156
|
if (action === "status" && !rest.length) return await authStatus();
|
|
942
1157
|
if (action === "logout" && !rest.length) return await logout();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worldlabsai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "World Labs command line interface",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/index.js",
|
|
11
|
-
"skills",
|
|
12
11
|
"LICENSE"
|
|
13
12
|
],
|
|
14
13
|
"type": "module",
|
|
15
14
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
15
|
+
"node": ">=22"
|
|
17
16
|
},
|
|
18
17
|
"publishConfig": {
|
|
19
|
-
"access": "
|
|
18
|
+
"access": "public",
|
|
20
19
|
"registry": "https://registry.npmjs.org/",
|
|
21
|
-
"tag": "
|
|
20
|
+
"tag": "latest"
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: worldlabs
|
|
3
|
-
description: Generate and edit scene images, estimate cameras and depth, or extract meshes with the World Labs CLI.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# World Labs
|
|
7
|
-
|
|
8
|
-
Use `{{CLI}}` for the Developer API. Run commands in `{{SHELL}}`. Run `{{CLI}} --help` for command syntax.
|
|
9
|
-
|
|
10
|
-
## Choose a task
|
|
11
|
-
|
|
12
|
-
| Task | Purpose |
|
|
13
|
-
| --- | --- |
|
|
14
|
-
| `atlasTextToImage` | Generate starting images from a description of an environment. |
|
|
15
|
-
| `images2PosedRGBD` | Estimate cameras and depth from images of a scene. |
|
|
16
|
-
| `atlasGenerate` | Generate new viewpoints using posed RGBD context. |
|
|
17
|
-
| `atlasChisel` | Generate posed images from a prompt and optional depth layouts. |
|
|
18
|
-
| `atlasMasked` | Fill masked regions across posed images. |
|
|
19
|
-
| `splats2Mesh` | Extract a mesh from a Gaussian splat scene. |
|
|
20
|
-
|
|
21
|
-
Read [How To Use Atlas](https://marble5.worldlabs.ai/docs/how-to-use-atlas) before choosing model inputs, calculating cameras, or chaining tasks. It explains scene data, model behavior, and the requirements that affect results. Follow its task or camera links when you need exact fields or geometry calculations. `{{CLI}} tasks list` reports SDK task names; account access can differ.
|
|
22
|
-
|
|
23
|
-
## Submit and inspect
|
|
24
|
-
|
|
25
|
-
Upload a local image:
|
|
26
|
-
|
|
27
|
-
```{{SHELL}}
|
|
28
|
-
{{CLI}} assets upload photo.jpg --json > asset.json
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Write `request.json` as `{"frames":[{"imageAsset":{"assetId":"<id>"}}]}`, replacing `<id>` with the uploaded asset's `id` from `asset.json`.
|
|
32
|
-
|
|
33
|
-
```{{SHELL}}
|
|
34
|
-
{{CLI}} tasks run images2PosedRGBD --request request.json --wait --json > result.json
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Use `--request -` to read JSON from stdin. Results go to stdout; diagnostics and the submission's idempotency key go to stderr. Preserve the key and reuse it with `--idempotency-key <key>` and the same request if submission is interrupted.
|
|
38
|
-
|
|
39
|
-
A timeout stops local waiting, not server work. Resume with `{{CLI}} operations wait <operation-id> --json`. Check a completed operation's `error` before reading `response`. Download returned assets with `{{CLI}} assets download <asset-id> --out result.png`. Inspect the outputs before using them in another task; keep each image, camera, depth, and confidence together.
|
|
40
|
-
|
|
41
|
-
## Authentication
|
|
42
|
-
|
|
43
|
-
Check `{{CLI}} auth status`. If login is needed, have the user run `{{CLI}} auth login` in their own terminal and enter the key privately. Never request keys in chat or put them in command arguments. Automation can supply `WLT_API_KEY`. The key selects the API project and billing; a successful status check verifies asset access, not every task permission.
|