@worldlabsai/cli 0.1.0 → 0.2.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 +317 -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://marble5.worldlabs.ai/docs/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
|
};
|
|
@@ -429,6 +454,9 @@ var createClient = (options) => {
|
|
|
429
454
|
};
|
|
430
455
|
};
|
|
431
456
|
|
|
457
|
+
// src/auth.ts
|
|
458
|
+
import { text } from "node:stream/consumers";
|
|
459
|
+
|
|
432
460
|
// src/config.ts
|
|
433
461
|
var CliError = class extends Error {
|
|
434
462
|
name = "CliError";
|
|
@@ -529,13 +557,20 @@ async function prompt(message, hidden = false) {
|
|
|
529
557
|
terminal: true
|
|
530
558
|
});
|
|
531
559
|
const controller = new AbortController();
|
|
532
|
-
|
|
560
|
+
let interrupted = false;
|
|
561
|
+
reader.once("SIGINT", () => {
|
|
562
|
+
interrupted = true;
|
|
563
|
+
controller.abort();
|
|
564
|
+
});
|
|
565
|
+
reader.once("close", () => controller.abort());
|
|
533
566
|
process.stderr.write(message);
|
|
534
567
|
try {
|
|
535
568
|
const answer = await reader.question("", { signal: controller.signal });
|
|
536
569
|
return answer.trim();
|
|
537
570
|
} catch {
|
|
538
|
-
throw new CliError(
|
|
571
|
+
throw new CliError(
|
|
572
|
+
interrupted ? "Cancelled." : "Input closed before an answer. For automation, set WLT_API_KEY or pipe the key to worldlabs auth login --api-key-stdin."
|
|
573
|
+
);
|
|
539
574
|
} finally {
|
|
540
575
|
reader.close();
|
|
541
576
|
if (hidden) process.stderr.write("\n");
|
|
@@ -573,12 +608,25 @@ async function authStatus() {
|
|
|
573
608
|
...process.env.WLT_API_KEY?.trim() ? {} : { credentialFile: credentialPath(configuration().apiOrigin) }
|
|
574
609
|
};
|
|
575
610
|
}
|
|
576
|
-
async function
|
|
611
|
+
async function pipedApiKey() {
|
|
612
|
+
if (process.stdin.isTTY) {
|
|
613
|
+
throw new CliError(
|
|
614
|
+
"--api-key-stdin reads a piped key, for example: worldlabs auth login --api-key-stdin < key.txt"
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const input = await text(process.stdin);
|
|
618
|
+
const apiKey = input.trim();
|
|
619
|
+
if (!apiKey) throw new CliError("No API key on standard input.");
|
|
620
|
+
if (/\s/u.test(apiKey)) throw new CliError("Standard input must contain only the API key.");
|
|
621
|
+
return apiKey;
|
|
622
|
+
}
|
|
623
|
+
async function login(noBrowser, pipedKey) {
|
|
624
|
+
if (pipedKey) return await saveValidatedKey(pipedKey);
|
|
577
625
|
if (process.env.WLT_API_KEY?.trim()) return await authStatus();
|
|
578
626
|
const { apiOrigin, apiKeysUrl } = configuration();
|
|
579
627
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
580
628
|
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."
|
|
629
|
+
"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
630
|
);
|
|
583
631
|
}
|
|
584
632
|
process.stderr.write(
|
|
@@ -591,6 +639,10 @@ The key will be saved in plaintext at ${credentialPath(apiOrigin)}.
|
|
|
591
639
|
if (!noBrowser) await openBrowser(apiKeysUrl);
|
|
592
640
|
const apiKey = await prompt("Paste API key (hidden): ", true);
|
|
593
641
|
if (!apiKey) throw new CliError("No key entered.");
|
|
642
|
+
return await saveValidatedKey(apiKey);
|
|
643
|
+
}
|
|
644
|
+
async function saveValidatedKey(apiKey) {
|
|
645
|
+
const { apiOrigin } = configuration();
|
|
594
646
|
await createClient({ apiKey, baseUrl: apiOrigin }).assets.list({
|
|
595
647
|
pageSize: 1,
|
|
596
648
|
signal: AbortSignal.timeout(3e4)
|
|
@@ -618,7 +670,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
618
670
|
import { openAsBlob } from "node:fs";
|
|
619
671
|
import { open as open2, readFile, rm as rm2 } from "node:fs/promises";
|
|
620
672
|
import nodePath2 from "node:path";
|
|
621
|
-
import { text } from "node:stream/consumers";
|
|
673
|
+
import { text as text2 } from "node:stream/consumers";
|
|
622
674
|
import { pipeline } from "node:stream/promises";
|
|
623
675
|
import { parseArgs } from "node:util";
|
|
624
676
|
var mimeTypes = {
|
|
@@ -626,9 +678,11 @@ var mimeTypes = {
|
|
|
626
678
|
".jpeg": "image/jpeg",
|
|
627
679
|
".png": "image/png",
|
|
628
680
|
".webp": "image/webp",
|
|
681
|
+
".exr": "image/x-exr",
|
|
629
682
|
".mp4": "video/mp4",
|
|
630
|
-
".
|
|
631
|
-
".
|
|
683
|
+
".glb": "model/gltf-binary",
|
|
684
|
+
".ply": "model/x-ply",
|
|
685
|
+
".spz": "model/x-spz"
|
|
632
686
|
};
|
|
633
687
|
function timeout(value) {
|
|
634
688
|
const seconds = value === void 0 ? 600 : Number(value);
|
|
@@ -665,7 +719,7 @@ async function runApiCommand(args2) {
|
|
|
665
719
|
throw new CliError("Unknown task. Run worldlabs tasks list.");
|
|
666
720
|
}
|
|
667
721
|
const body = JSON.parse(
|
|
668
|
-
values.request === "-" ? await
|
|
722
|
+
values.request === "-" ? await text2(process.stdin) : await readFile(values.request, "utf8")
|
|
669
723
|
);
|
|
670
724
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
671
725
|
throw new CliError("Task request must be a JSON object.");
|
|
@@ -759,24 +813,30 @@ Reuse this key and request if submission is interrupted.
|
|
|
759
813
|
throw new CliError("Unknown command or arguments. Run worldlabs --help.");
|
|
760
814
|
}
|
|
761
815
|
|
|
816
|
+
// src/node-version.ts
|
|
817
|
+
var MINIMUM_NODE_MAJOR = 22;
|
|
818
|
+
function nodeVersionProblem(version2) {
|
|
819
|
+
if (Number(version2.split(".")[0]) >= MINIMUM_NODE_MAJOR) return void 0;
|
|
820
|
+
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.`;
|
|
821
|
+
}
|
|
822
|
+
|
|
762
823
|
// src/skills.ts
|
|
763
|
-
import {
|
|
764
|
-
import {
|
|
824
|
+
import { execFile, spawn as spawn2 } from "node:child_process";
|
|
825
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
826
|
+
import { lstat as lstat2, mkdtemp, readFile as readFile2, readdir, rename as rename2, rmdir } from "node:fs/promises";
|
|
765
827
|
import { homedir as homedir2 } from "node:os";
|
|
766
828
|
import nodePath3 from "node:path";
|
|
767
|
-
import { fileURLToPath } from "node:url";
|
|
768
829
|
var marker = ".worldlabs-install.json";
|
|
769
|
-
var
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
"
|
|
830
|
+
var InstallerTerminationError = class extends CliError {
|
|
831
|
+
name = "InstallerTerminationError";
|
|
832
|
+
};
|
|
833
|
+
function skillClient(client) {
|
|
834
|
+
if (client !== void 0 && !/^[a-z0-9][a-z0-9-]*$/u.test(client)) {
|
|
835
|
+
throw new CliError(
|
|
836
|
+
"--client must be an agent name, such as codex, claude-code, cursor, or gemini-cli."
|
|
776
837
|
);
|
|
777
838
|
}
|
|
778
|
-
|
|
779
|
-
throw new CliError("Choose --client codex or --client claude-code.");
|
|
839
|
+
return client;
|
|
780
840
|
}
|
|
781
841
|
async function fingerprint(root) {
|
|
782
842
|
const hash = createHash2("sha256");
|
|
@@ -797,93 +857,213 @@ async function fingerprint(root) {
|
|
|
797
857
|
await visit("");
|
|
798
858
|
return hash.digest("hex");
|
|
799
859
|
}
|
|
800
|
-
async function
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
|
|
860
|
+
async function cleanupLegacySkill(client) {
|
|
861
|
+
const root = client === "codex" ? process.env.CODEX_HOME ?? nodePath3.join(homedir2(), ".codex") : nodePath3.join(homedir2(), ".claude");
|
|
862
|
+
const target = nodePath3.join(root, "skills", "worldlabs");
|
|
863
|
+
let stat;
|
|
804
864
|
try {
|
|
805
|
-
await
|
|
806
|
-
} catch {
|
|
865
|
+
stat = await lstat2(target);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
if (error.code === "ENOENT") return;
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
870
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
|
871
|
+
let expected;
|
|
872
|
+
try {
|
|
873
|
+
expected = JSON.parse(await readFile2(nodePath3.join(target, marker), "utf8"));
|
|
874
|
+
} catch (error) {
|
|
875
|
+
if (error.code === "ENOENT") return;
|
|
807
876
|
throw new CliError(
|
|
808
|
-
`
|
|
877
|
+
`Cannot verify the old skill at ${target}. Move it aside before continuing.`
|
|
809
878
|
);
|
|
810
879
|
}
|
|
811
|
-
|
|
812
|
-
|
|
880
|
+
if (await fingerprint(target) !== expected) {
|
|
881
|
+
throw new CliError(
|
|
882
|
+
`Old skill has local changes at ${target}. Move it aside before continuing.`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
const backupContainer = await mkdtemp(
|
|
886
|
+
nodePath3.join(nodePath3.dirname(target), ".worldlabs-backup-")
|
|
887
|
+
);
|
|
888
|
+
const backup = nodePath3.join(backupContainer, "worldlabs");
|
|
813
889
|
try {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
890
|
+
await rename2(target, backup);
|
|
891
|
+
} catch (error) {
|
|
892
|
+
await rmdir(backupContainer).catch(() => null);
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
895
|
+
process.stderr.write(`Moved the old CLI-managed skill to ${backup}
|
|
896
|
+
`);
|
|
897
|
+
return { target, backup };
|
|
898
|
+
}
|
|
899
|
+
function runSkillsInstaller(args2, signal) {
|
|
900
|
+
signal.throwIfAborted();
|
|
901
|
+
const env = { ...process.env, DISABLE_TELEMETRY: "1" };
|
|
902
|
+
delete env.WLT_API_KEY;
|
|
903
|
+
const windows = process.platform === "win32";
|
|
904
|
+
return new Promise((resolve, reject) => {
|
|
905
|
+
const child = spawn2(
|
|
906
|
+
windows ? "cmd.exe" : "npx",
|
|
907
|
+
windows ? ["/d", "/s", "/c", `npx.cmd ${args2.join(" ")}`] : args2,
|
|
908
|
+
{ env, detached: !windows, stdio: ["inherit", "pipe", "pipe"] }
|
|
909
|
+
);
|
|
910
|
+
child.stdout.pipe(process.stderr, { end: false });
|
|
911
|
+
child.stderr.pipe(process.stderr, { end: false });
|
|
912
|
+
let forceTimer;
|
|
913
|
+
let stopTimer;
|
|
914
|
+
let settled = false;
|
|
915
|
+
const finish = (error) => {
|
|
916
|
+
if (settled) return;
|
|
917
|
+
settled = true;
|
|
918
|
+
clearTimeout(forceTimer);
|
|
919
|
+
clearTimeout(stopTimer);
|
|
920
|
+
child.stdout.unpipe(process.stderr);
|
|
921
|
+
child.stderr.unpipe(process.stderr);
|
|
922
|
+
signal.removeEventListener("abort", cancel);
|
|
923
|
+
if (error) reject(error);
|
|
924
|
+
else resolve();
|
|
925
|
+
};
|
|
926
|
+
const cannotStop = () => {
|
|
927
|
+
child.stdout.destroy();
|
|
928
|
+
child.stderr.destroy();
|
|
929
|
+
child.unref();
|
|
930
|
+
finish(
|
|
931
|
+
new InstallerTerminationError(
|
|
932
|
+
"Could not stop the skills installer. Legacy backups were retained; stop the installer before restoring them."
|
|
933
|
+
)
|
|
934
|
+
);
|
|
935
|
+
};
|
|
936
|
+
const killGroup = (terminationSignal) => {
|
|
937
|
+
if (!child.pid) return;
|
|
824
938
|
try {
|
|
825
|
-
|
|
826
|
-
} catch {
|
|
827
|
-
|
|
939
|
+
process.kill(-child.pid, terminationSignal);
|
|
940
|
+
} catch (error) {
|
|
941
|
+
if (error.code !== "ESRCH") cannotStop();
|
|
828
942
|
}
|
|
829
|
-
|
|
830
|
-
|
|
943
|
+
};
|
|
944
|
+
const cancel = () => {
|
|
945
|
+
if (!child.pid) return;
|
|
946
|
+
stopTimer = setTimeout(cannotStop, 5e3).unref();
|
|
947
|
+
if (windows) {
|
|
948
|
+
execFile(
|
|
949
|
+
"taskkill",
|
|
950
|
+
["/PID", String(child.pid), "/T", "/F"],
|
|
951
|
+
{ timeout: 4e3 },
|
|
952
|
+
(error) => {
|
|
953
|
+
if (error && !settled) cannotStop();
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
} else {
|
|
957
|
+
forceTimer = setTimeout(() => killGroup("SIGKILL"), 2e3).unref();
|
|
958
|
+
killGroup(signal.reason);
|
|
831
959
|
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
new URL(import.meta.url.endsWith(".ts") ? "index.ts" : "index.js", import.meta.url)
|
|
960
|
+
};
|
|
961
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
962
|
+
child.once(
|
|
963
|
+
"error",
|
|
964
|
+
() => finish(
|
|
965
|
+
new CliError("Could not start the skills installer. Check that npm and npx are available.")
|
|
966
|
+
)
|
|
840
967
|
);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
968
|
+
child.once(
|
|
969
|
+
"close",
|
|
970
|
+
(code) => finish(
|
|
971
|
+
code === 0 && !signal.aborted ? void 0 : new CliError("Skills installer did not complete. See its output above.")
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
async function manageSkills(action, client, yes = false) {
|
|
977
|
+
skillClient(client);
|
|
978
|
+
const moved = [];
|
|
979
|
+
const controller = new AbortController();
|
|
980
|
+
const cancel = (signal) => controller.abort(signal);
|
|
981
|
+
process.on("SIGINT", cancel);
|
|
982
|
+
process.on("SIGTERM", cancel);
|
|
983
|
+
try {
|
|
984
|
+
for (const candidate of client ? [client] : ["codex", "claude-code"]) {
|
|
985
|
+
if (candidate === "codex" || candidate === "claude-code") {
|
|
986
|
+
const record = await cleanupLegacySkill(candidate);
|
|
987
|
+
if (record) moved.push(record);
|
|
854
988
|
}
|
|
989
|
+
if (controller.signal.aborted) throw new CliError("Skills installation cancelled.");
|
|
855
990
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
991
|
+
const args2 = [
|
|
992
|
+
"--yes",
|
|
993
|
+
"skills@1.7.2",
|
|
994
|
+
...action === "install" ? ["add", "worldlabsai/skills", "--skill", "worldlabs"] : ["remove", "worldlabs"],
|
|
995
|
+
...client ? ["--agent", client] : [],
|
|
996
|
+
"--global",
|
|
997
|
+
...yes ? ["--yes"] : []
|
|
998
|
+
];
|
|
999
|
+
await runSkillsInstaller(args2, controller.signal);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
if (error instanceof InstallerTerminationError) throw error;
|
|
1002
|
+
for (const { target, backup } of moved.toReversed()) {
|
|
1003
|
+
try {
|
|
1004
|
+
await lstat2(target);
|
|
1005
|
+
process.stderr.write(
|
|
1006
|
+
`Could not restore the old skill because a skill now exists at ${target}. Backup retained at ${backup}
|
|
1007
|
+
`
|
|
1008
|
+
);
|
|
1009
|
+
} catch (restoreCheckError) {
|
|
1010
|
+
if (restoreCheckError.code !== "ENOENT") {
|
|
1011
|
+
process.stderr.write(
|
|
1012
|
+
`Could not check the old skill location ${target}. Backup retained at ${backup}
|
|
1013
|
+
`
|
|
1014
|
+
);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
await rename2(backup, target);
|
|
1019
|
+
await rmdir(nodePath3.dirname(backup)).catch(() => null);
|
|
1020
|
+
} catch {
|
|
1021
|
+
process.stderr.write(
|
|
1022
|
+
`Could not restore the old skill at ${target}. Backup retained at ${backup}
|
|
1023
|
+
`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
863
1027
|
}
|
|
864
|
-
|
|
865
|
-
return { path: target, next: "Start a new agent session to load the worldlabs skill." };
|
|
1028
|
+
throw error;
|
|
866
1029
|
} finally {
|
|
867
|
-
|
|
868
|
-
|
|
1030
|
+
process.off("SIGINT", cancel);
|
|
1031
|
+
process.off("SIGTERM", cancel);
|
|
1032
|
+
if (controller.signal.aborted) {
|
|
1033
|
+
process.kill(process.pid, controller.signal.reason);
|
|
1034
|
+
}
|
|
869
1035
|
}
|
|
1036
|
+
return {
|
|
1037
|
+
client,
|
|
1038
|
+
...moved.length ? { legacyBackups: moved.map(({ backup }) => backup) } : {},
|
|
1039
|
+
next: "Start a new agent session after changing skills."
|
|
1040
|
+
};
|
|
870
1041
|
}
|
|
871
1042
|
|
|
872
1043
|
// src/setup.ts
|
|
873
|
-
async function setup(client, noBrowser) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1044
|
+
async function setup(client, noBrowser, yes = false, keyFromStdin = false) {
|
|
1045
|
+
const target = skillClient(client);
|
|
1046
|
+
const pipedKey = keyFromStdin ? await pipedApiKey() : void 0;
|
|
1047
|
+
let installSkill = target !== void 0;
|
|
1048
|
+
if (!installSkill && process.stdin.isTTY && process.stderr.isTTY) {
|
|
1049
|
+
const answer = await prompt("Install the World Labs agent skill? [y/N]: ");
|
|
1050
|
+
const choice = answer.toLowerCase();
|
|
1051
|
+
if (choice && !["y", "yes", "n", "no"].includes(choice)) {
|
|
1052
|
+
throw new CliError("Choose yes or no.");
|
|
1053
|
+
}
|
|
1054
|
+
installSkill = choice === "y" || choice === "yes";
|
|
1055
|
+
}
|
|
1056
|
+
const auth = await login(noBrowser, pipedKey);
|
|
1057
|
+
let skill;
|
|
1058
|
+
if (installSkill) {
|
|
1059
|
+
try {
|
|
1060
|
+
skill = await manageSkills("install", target, yes);
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
throw new CliError(
|
|
1063
|
+
`API authentication succeeded, but skill installation failed. ${error instanceof Error ? error.message : "Retry worldlabs skills install."}`
|
|
1064
|
+
);
|
|
883
1065
|
}
|
|
884
1066
|
}
|
|
885
|
-
const auth = await login(noBrowser);
|
|
886
|
-
const skill = target ? await manageSkills("install", target) : void 0;
|
|
887
1067
|
return {
|
|
888
1068
|
...auth,
|
|
889
1069
|
...skill ? { skill } : {},
|
|
@@ -898,9 +1078,9 @@ var { version } = JSON.parse(
|
|
|
898
1078
|
var help = `worldlabs - World Labs Developer API CLI
|
|
899
1079
|
|
|
900
1080
|
Usage: worldlabs [--json] <command>
|
|
901
|
-
setup [--client
|
|
902
|
-
skills install|uninstall --client
|
|
903
|
-
auth login [--no-browser]
|
|
1081
|
+
setup [--client <agent>] [--no-browser] [--api-key-stdin] [--yes]
|
|
1082
|
+
skills install|uninstall [--client <agent>] [--yes]
|
|
1083
|
+
auth login [--no-browser] [--api-key-stdin]
|
|
904
1084
|
auth status
|
|
905
1085
|
auth logout
|
|
906
1086
|
config show
|
|
@@ -913,30 +1093,53 @@ Usage: worldlabs [--json] <command>
|
|
|
913
1093
|
--version
|
|
914
1094
|
--help
|
|
915
1095
|
|
|
1096
|
+
Skills use Skills CLI client detection when --client is omitted (e.g. codex, claude-code, cursor, gemini-cli).
|
|
916
1097
|
Use --request - to read task JSON from standard input.
|
|
917
1098
|
|
|
918
|
-
Saved login uses a plaintext file in your user configuration directory. Automation can set WLT_API_KEY
|
|
1099
|
+
Saved login uses a plaintext file in your user configuration directory. Automation can set WLT_API_KEY,
|
|
1100
|
+
or save a key without the prompt by piping it to --api-key-stdin.
|
|
919
1101
|
`;
|
|
920
1102
|
async function main(args2) {
|
|
1103
|
+
const unsupported = nodeVersionProblem(process.versions.node);
|
|
1104
|
+
if (unsupported) throw new CliError(unsupported);
|
|
921
1105
|
if (args2.length === 0 || args2.includes("--help")) return help;
|
|
922
1106
|
if (args2.length === 1 && args2[0] === "--version") return version;
|
|
923
1107
|
const [command, action, ...rest] = args2;
|
|
924
1108
|
if (command === "setup") {
|
|
925
1109
|
const { values } = parseArgs2({
|
|
926
1110
|
args: args2.slice(1),
|
|
927
|
-
options: {
|
|
1111
|
+
options: {
|
|
1112
|
+
client: { type: "string" },
|
|
1113
|
+
"no-browser": { type: "boolean" },
|
|
1114
|
+
"api-key-stdin": { type: "boolean" },
|
|
1115
|
+
yes: { type: "boolean" }
|
|
1116
|
+
}
|
|
928
1117
|
});
|
|
929
|
-
return await setup(
|
|
1118
|
+
return await setup(
|
|
1119
|
+
values.client,
|
|
1120
|
+
Boolean(values["no-browser"]),
|
|
1121
|
+
Boolean(values.yes),
|
|
1122
|
+
Boolean(values["api-key-stdin"])
|
|
1123
|
+
);
|
|
930
1124
|
}
|
|
931
1125
|
if (command === "skills" && (action === "install" || action === "uninstall")) {
|
|
932
|
-
const { values } = parseArgs2({
|
|
933
|
-
|
|
1126
|
+
const { values } = parseArgs2({
|
|
1127
|
+
args: rest,
|
|
1128
|
+
options: { client: { type: "string" }, yes: { type: "boolean" } }
|
|
1129
|
+
});
|
|
1130
|
+
return await manageSkills(action, skillClient(values.client), Boolean(values.yes));
|
|
934
1131
|
}
|
|
935
1132
|
if (command === "config" && action === "show" && !rest.length) return configuration();
|
|
936
1133
|
if (command === "auth") {
|
|
937
1134
|
if (action === "login") {
|
|
938
|
-
const { values } = parseArgs2({
|
|
939
|
-
|
|
1135
|
+
const { values } = parseArgs2({
|
|
1136
|
+
args: rest,
|
|
1137
|
+
options: { "no-browser": { type: "boolean" }, "api-key-stdin": { type: "boolean" } }
|
|
1138
|
+
});
|
|
1139
|
+
return await login(
|
|
1140
|
+
Boolean(values["no-browser"]),
|
|
1141
|
+
values["api-key-stdin"] ? await pipedApiKey() : void 0
|
|
1142
|
+
);
|
|
940
1143
|
}
|
|
941
1144
|
if (action === "status" && !rest.length) return await authStatus();
|
|
942
1145
|
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.2.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.
|