@labelbox/recursion-cli 0.0.48 → 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 CHANGED
@@ -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. If only the
34
- previous `~/.cache/rl-gym/` entry exists, the CLI validates and uses it immediately,
35
- then adopts it into the Recursion cache non-destructively. The
36
- old entry is retained unchanged for rollback; new server responses are written only
37
- to the Recursion cache.
38
-
39
- Legacy adoption uses atomic create-if-absent publication: a validated copy is
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, linkSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from 'node:fs';
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 = '.rlc-';
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. The caller reports whether it
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
- if (publish(tempPath) === 'transferred')
387
- ownsTemp = false;
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 = selectCache(baseUrl);
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` (or the deprecated `RL_GYM_BASE_URL`), falling back to the production host. */
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
- const { RECURSION_BASE_URL: recursionBaseUrl, RL_GYM_BASE_URL: legacyBaseUrl } = process.env;
38
- return recursionBaseUrl ?? legacyBaseUrl;
33
+ return process.env['RECURSION_BASE_URL'];
39
34
  }
40
- /** `--base-url` wins over `RECURSION_BASE_URL` (or the deprecated `RL_GYM_BASE_URL`), falling back to the production host. */
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 the env vars; none set → the production proxy. Shares
65
- // envBaseUrl() with resolve.ts so the RECURSION_BASE_URL / RL_GYM_BASE_URL
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@labelbox/recursion-cli",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",