@labelbox/recursion-cli 0.0.47 → 0.0.49
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 +9 -18
- package/dist/manifest.js +6 -42
- package/dist/resolve.d.ts +2 -6
- package/dist/resolve.js +3 -8
- package/dist/skills.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
`recursion` — the Recursion CLI. It mirrors the TypeScript SDK
|
|
4
4
|
(`@labelbox/recursion-sdk`) exactly: where the SDK is `rl.synthesizers.create(...)`, the
|
|
5
5
|
CLI is `recursion synthesizers create …`. Dots become spaces; you get `--help` at every
|
|
6
|
-
level. The
|
|
7
|
-
runs the same command tree;
|
|
6
|
+
level. The short `rl` executable remains a permanently supported convenience alias and
|
|
7
|
+
runs the same command tree; documentation uses the canonical `recursion` spelling.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
recursion --help # list nouns (synthesizers, synthesizer-runs)
|
|
@@ -30,22 +30,13 @@ live CLI reflects it as soon as the backend deploys. The CLI is re-released only
|
|
|
30
30
|
its own engine code changes. When the server is reachable, the manifest is
|
|
31
31
|
revalidated on every run via a conditional fetch (ETag / `If-None-Match`) and cached
|
|
32
32
|
per base-url under `~/.cache/recursion/`. When the server is unreachable, the CLI may
|
|
33
|
-
use the last locally validated copy so commands remain available offline.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
hard-linked from a unique same-directory temporary file, so it never replaces a
|
|
41
|
-
canonical entry created concurrently. A valid concurrent canonical entry wins; an
|
|
42
|
-
invalid one is left untouched while that invocation uses the validated legacy value
|
|
43
|
-
in memory. Validated fresh HTTP `200` responses use atomic same-directory rename and
|
|
44
|
-
therefore retain last-network-writer behavior. Replacement preserves an existing
|
|
45
|
-
file's POSIX mode bits, but deliberately publishes a new inode and does not preserve
|
|
46
|
-
its ACLs or extended attributes. Both paths require parent-directory write/search
|
|
47
|
-
permission; if unavailable, the CLI keeps using the validated in-memory result and
|
|
48
|
-
never falls back to a partial direct write.
|
|
33
|
+
use the last locally validated copy so commands remain available offline. Validated
|
|
34
|
+
fresh HTTP `200` responses use atomic same-directory rename and therefore retain
|
|
35
|
+
last-network-writer behavior. Replacement preserves an existing file's POSIX mode
|
|
36
|
+
bits, but deliberately publishes a new inode and does not preserve its ACLs or
|
|
37
|
+
extended attributes. Publication requires parent-directory write/search permission;
|
|
38
|
+
if unavailable, the CLI keeps using the validated in-memory result and never falls
|
|
39
|
+
back to a partial direct write.
|
|
49
40
|
|
|
50
41
|
### Docs browse surfaces
|
|
51
42
|
|
package/dist/manifest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { closeSync, fchmodSync,
|
|
2
|
+
import { closeSync, fchmodSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import process from 'node:process';
|
|
@@ -308,9 +308,6 @@ export function hostSlug(baseUrl) {
|
|
|
308
308
|
function canonicalCachePathFor(baseUrl) {
|
|
309
309
|
return join(homedir(), '.cache', 'recursion', `${hostSlug(baseUrl)}.json`);
|
|
310
310
|
}
|
|
311
|
-
function legacyCachePathFor(baseUrl) {
|
|
312
|
-
return join(homedir(), '.cache', 'rl-gym', `${hostSlug(baseUrl)}.json`);
|
|
313
|
-
}
|
|
314
311
|
function readCache(path) {
|
|
315
312
|
let raw;
|
|
316
313
|
try {
|
|
@@ -339,14 +336,13 @@ function readCache(path) {
|
|
|
339
336
|
return {
|
|
340
337
|
etag: parsed.data.etag,
|
|
341
338
|
manifest: parseManifest(parsed.data.manifest, 'the cache'),
|
|
342
|
-
raw,
|
|
343
339
|
};
|
|
344
340
|
}
|
|
345
341
|
catch {
|
|
346
342
|
return undefined;
|
|
347
343
|
}
|
|
348
344
|
}
|
|
349
|
-
const CACHE_TEMP_PREFIX = '.
|
|
345
|
+
const CACHE_TEMP_PREFIX = '.recursion-cli-';
|
|
350
346
|
function uniqueCacheTempPath(directory) {
|
|
351
347
|
return join(directory, `${CACHE_TEMP_PREFIX}${process.pid.toString()}-${randomUUID()}.tmp`);
|
|
352
348
|
}
|
|
@@ -357,9 +353,7 @@ function uniqueCacheTempPath(directory) {
|
|
|
357
353
|
* including a partial write or close failure — triggers best-effort pathname cleanup.
|
|
358
354
|
* Descriptor ownership is cleared before the single close attempt: retrying a numeric
|
|
359
355
|
* descriptor could close an unrelated resource if the operating system reused it.
|
|
360
|
-
* Publication happens only after close succeeds
|
|
361
|
-
* transferred the inode (rename) or retained it (hard-link adoption), which determines
|
|
362
|
-
* final cleanup ownership.
|
|
356
|
+
* Publication happens only after close succeeds and transfers the temp inode.
|
|
363
357
|
*/
|
|
364
358
|
function publishFromOwnedTemp(path, raw, prepare, publish) {
|
|
365
359
|
let tempPath;
|
|
@@ -383,8 +377,8 @@ function publishFromOwnedTemp(path, raw, prepare, publish) {
|
|
|
383
377
|
const descriptorToClose = descriptor;
|
|
384
378
|
descriptor = undefined;
|
|
385
379
|
closeSync(descriptorToClose);
|
|
386
|
-
|
|
387
|
-
|
|
380
|
+
publish(tempPath);
|
|
381
|
+
ownsTemp = false;
|
|
388
382
|
}
|
|
389
383
|
catch {
|
|
390
384
|
// Cache persistence is a bandwidth optimization. The caller already has a
|
|
@@ -428,38 +422,8 @@ function replaceCacheAtomically(path, raw) {
|
|
|
428
422
|
fchmodSync(descriptor, destination.mode & 0o7777);
|
|
429
423
|
}, (tempPath) => {
|
|
430
424
|
renameSync(tempPath, path);
|
|
431
|
-
return 'transferred';
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Publish validated legacy bytes only if the canonical destination is absent.
|
|
436
|
-
*
|
|
437
|
-
* The hard link is the no-replace commit point: unlike rename, it fails if any
|
|
438
|
-
* concurrent invocation has created the destination. Best-effort temporary-path
|
|
439
|
-
* cleanup is attempted afterward, while a winning canonical path is never overwritten.
|
|
440
|
-
*/
|
|
441
|
-
function publishCacheIfAbsent(path, raw) {
|
|
442
|
-
publishFromOwnedTemp(path, raw, () => { }, (tempPath) => {
|
|
443
|
-
linkSync(tempPath, path);
|
|
444
|
-
return 'retained';
|
|
445
425
|
});
|
|
446
426
|
}
|
|
447
|
-
/** Prefer canonical cache; otherwise adopt a validated legacy file without mutating it. */
|
|
448
|
-
function selectCache(baseUrl) {
|
|
449
|
-
const canonicalPath = canonicalCachePathFor(baseUrl);
|
|
450
|
-
const canonical = readCache(canonicalPath);
|
|
451
|
-
if (canonical !== undefined)
|
|
452
|
-
return canonical;
|
|
453
|
-
const legacy = readCache(legacyCachePathFor(baseUrl));
|
|
454
|
-
if (legacy === undefined)
|
|
455
|
-
return undefined;
|
|
456
|
-
// Copy the validated bytes exactly. Hard-link publication cannot replace a
|
|
457
|
-
// canonical file that appears after the reads above. Re-read afterward: prefer a
|
|
458
|
-
// valid concurrent winner, but leave an invalid winner untouched and keep using
|
|
459
|
-
// the already-validated legacy value in memory.
|
|
460
|
-
publishCacheIfAbsent(canonicalPath, legacy.raw);
|
|
461
|
-
return readCache(canonicalPath) ?? legacy;
|
|
462
|
-
}
|
|
463
427
|
// Every `recursion` invocation gates on this fetch, so it must be bounded: a server that
|
|
464
428
|
// accepts the connection but never responds (a hung gateway, a stalled captive
|
|
465
429
|
// portal) would otherwise hang the CLI forever. A timeout makes `fetch` reject, so
|
|
@@ -478,7 +442,7 @@ const MANIFEST_FETCH_TIMEOUT_MS = 30_000;
|
|
|
478
442
|
export async function fetchManifest(baseUrl, apiKey) {
|
|
479
443
|
const url = supportUrl(baseUrl, '/cli/manifest');
|
|
480
444
|
const canonicalCachePath = canonicalCachePathFor(baseUrl);
|
|
481
|
-
const cached =
|
|
445
|
+
const cached = readCache(canonicalCachePath);
|
|
482
446
|
let res;
|
|
483
447
|
try {
|
|
484
448
|
res = await fetch(url, {
|
package/dist/resolve.d.ts
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
export declare function flagValue(argv: string[], name: string): string | undefined;
|
|
3
3
|
/** `--api-key` wins over `LABELBOX_API_KEY`; undefined when neither is set. */
|
|
4
4
|
export declare function resolveApiKey(argv: string[]): string | undefined;
|
|
5
|
-
/**
|
|
6
|
-
* The base URL from the environment: the current `RECURSION_BASE_URL`, else the
|
|
7
|
-
* deprecated `RL_GYM_BASE_URL`. Shared by `resolveBaseUrl` here and the `recursion skills`
|
|
8
|
-
* resolver so the precedence lives — and is tested — in one place.
|
|
9
|
-
*/
|
|
5
|
+
/** The canonical base URL from the environment. */
|
|
10
6
|
export declare function envBaseUrl(): string | undefined;
|
|
11
|
-
/** `--base-url` wins over `RECURSION_BASE_URL
|
|
7
|
+
/** `--base-url` wins over `RECURSION_BASE_URL`, falling back to the production host. */
|
|
12
8
|
export declare function resolveBaseUrl(argv: string[]): string;
|
package/dist/resolve.js
CHANGED
|
@@ -28,16 +28,11 @@ export function resolveApiKey(argv) {
|
|
|
28
28
|
const { LABELBOX_API_KEY: envApiKey } = process.env;
|
|
29
29
|
return flagValue(argv, 'api-key') ?? envApiKey;
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* The base URL from the environment: the current `RECURSION_BASE_URL`, else the
|
|
33
|
-
* deprecated `RL_GYM_BASE_URL`. Shared by `resolveBaseUrl` here and the `recursion skills`
|
|
34
|
-
* resolver so the precedence lives — and is tested — in one place.
|
|
35
|
-
*/
|
|
31
|
+
/** The canonical base URL from the environment. */
|
|
36
32
|
export function envBaseUrl() {
|
|
37
|
-
|
|
38
|
-
return recursionBaseUrl ?? legacyBaseUrl;
|
|
33
|
+
return process.env['RECURSION_BASE_URL'];
|
|
39
34
|
}
|
|
40
|
-
/** `--base-url` wins over `RECURSION_BASE_URL
|
|
35
|
+
/** `--base-url` wins over `RECURSION_BASE_URL`, falling back to the production host. */
|
|
41
36
|
export function resolveBaseUrl(argv) {
|
|
42
37
|
return flagValue(argv, 'base-url') ?? envBaseUrl() ?? DEFAULT_BASE_URL;
|
|
43
38
|
}
|
package/dist/skills.js
CHANGED
|
@@ -61,9 +61,8 @@ const SkillSummarySchema = z.object({
|
|
|
61
61
|
/** Resolve the base URL for the skills endpoints (never throws — they are public). */
|
|
62
62
|
function resolveBaseUrl(program) {
|
|
63
63
|
const { baseUrl } = program.opts();
|
|
64
|
-
// --base-url wins over
|
|
65
|
-
// envBaseUrl() with resolve.ts so the
|
|
66
|
-
// precedence lives in one tested place.
|
|
64
|
+
// --base-url wins over RECURSION_BASE_URL; neither set → the production proxy.
|
|
65
|
+
// Shares envBaseUrl() with resolve.ts so the precedence lives in one tested place.
|
|
67
66
|
return baseUrl ?? envBaseUrl() ?? DEFAULT_BASE_URL;
|
|
68
67
|
}
|
|
69
68
|
/** The configured API key (--api-key or LABELBOX_API_KEY), if any. */
|