@labelbox/recursion-cli 0.0.46 → 0.0.48
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 +18 -17
- package/dist/embed.d.ts +2 -2
- package/dist/embed.js +4 -4
- package/dist/git-host.js +1 -1
- package/dist/manifest.js +7 -7
- package/dist/program.d.ts +5 -5
- package/dist/program.js +35 -29
- package/dist/resolve.d.ts +1 -1
- package/dist/resolve.js +2 -2
- package/dist/run.d.ts +1 -1
- package/dist/run.js +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +10 -10
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# @labelbox/recursion-cli
|
|
2
2
|
|
|
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
|
-
CLI is `
|
|
6
|
-
level.
|
|
5
|
+
CLI is `recursion synthesizers create …`. Dots become spaces; you get `--help` at every
|
|
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.
|
|
7
8
|
|
|
8
9
|
```sh
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
recursion --help # list nouns (synthesizers, synthesizer-runs)
|
|
11
|
+
recursion synthesizers --help # list verbs (create, get, list, …)
|
|
12
|
+
recursion synthesizers create --help # list flags
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
## How it works (fully live, zero per-operation code)
|
|
@@ -48,15 +49,15 @@ never falls back to a partial direct write.
|
|
|
48
49
|
|
|
49
50
|
### Docs browse surfaces
|
|
50
51
|
|
|
51
|
-
Beyond the executable `
|
|
52
|
-
exposed as one consistent positional shape — `
|
|
52
|
+
Beyond the executable `recursion <noun> <verb>` operations, the manifest carries the docs,
|
|
53
|
+
exposed as one consistent positional shape — `recursion <group> [<id>]` (bare lists, an id
|
|
53
54
|
shows that one):
|
|
54
55
|
|
|
55
56
|
```sh
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
recursion resources [<id>] # Reference — resource hubs: object shape, operations, recipes
|
|
58
|
+
recursion recipes [<id>] # How-to — multi-step user-goal guides (--format cli|ts|curl)
|
|
59
|
+
recursion explain [<concept>] # Explanation — concept pages
|
|
60
|
+
recursion tutorials [<id>] # Tutorials — getting-started docs
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
## Install / run
|
|
@@ -66,9 +67,9 @@ setup beyond Node itself.
|
|
|
66
67
|
|
|
67
68
|
```sh
|
|
68
69
|
npm uninstall -g @recursion/cli @labelbox/rl-cli # no-op unless a pre-rename package is installed
|
|
69
|
-
npm install -g @labelbox/recursion-cli #
|
|
70
|
+
npm install -g @labelbox/recursion-cli # old packages also own the `rl` alias — EEXIST otherwise
|
|
70
71
|
# or, zero-install:
|
|
71
|
-
npx @labelbox/recursion-cli --help
|
|
72
|
+
npx --package @labelbox/recursion-cli recursion --help
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
The CLI is standalone, so this pulls in no `@labelbox/recursion-sdk`.
|
|
@@ -110,9 +111,9 @@ host with `--base-url <url>` (defaults to the public Recursion API gateway, see
|
|
|
110
111
|
no-content operations).
|
|
111
112
|
|
|
112
113
|
```sh
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
recursion synthesizers get --synthesizer-job-id sj_01HX...
|
|
115
|
+
recursion synthesizers create --environment-id env_01HX... --from-json ./body.json
|
|
116
|
+
recursion synthesizer-runs trigger --problem-version-id pv_01HX... --from-json ./run.json
|
|
116
117
|
```
|
|
117
118
|
|
|
118
119
|
## The command surface is the live server
|
package/dist/embed.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ExecuteCliOptions {
|
|
|
8
8
|
apiKey: string;
|
|
9
9
|
/** The API the CLI dispatches against. Server configuration, not caller input. */
|
|
10
10
|
baseUrl: string;
|
|
11
|
-
/** Reported by `
|
|
11
|
+
/** Reported by `recursion --version`. */
|
|
12
12
|
version: string;
|
|
13
13
|
/** The pre-assembled manifest — served from memory, never re-fetched over HTTP. */
|
|
14
14
|
manifest: Manifest;
|
|
@@ -31,7 +31,7 @@ export interface CliResult {
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function assertAllowedArgv(args: readonly string[]): void;
|
|
33
33
|
/**
|
|
34
|
-
* Run one `
|
|
34
|
+
* Run one `recursion` invocation in-process and capture its output.
|
|
35
35
|
*
|
|
36
36
|
* Resolves for both success and failure — a non-zero exit is a normal outcome the
|
|
37
37
|
* model should see and correct from, not an exception. It rejects only on a
|
package/dist/embed.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { run } from './run.js';
|
|
2
|
-
// Running `
|
|
2
|
+
// Running `recursion` inside a server process, on behalf of an agent, without a shell.
|
|
3
3
|
//
|
|
4
4
|
// The MCP endpoint exposes one tool whose argument is an argv array. This module
|
|
5
5
|
// is the whole surface it needs: `executeCli` builds the argv, runs the same
|
|
@@ -91,11 +91,11 @@ export function assertAllowedArgv(args) {
|
|
|
91
91
|
const command = args.find((arg) => !arg.startsWith('-'));
|
|
92
92
|
if (command !== undefined && BLOCKED_COMMANDS.has(command)) {
|
|
93
93
|
throw new Error(`"${command}" is not available here: it acts on a local developer checkout, ` +
|
|
94
|
-
'which this server does not have. Run it from your own terminal with the
|
|
94
|
+
'which this server does not have. Run it from your own terminal with the recursion CLI.');
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* Run one `
|
|
98
|
+
* Run one `recursion` invocation in-process and capture its output.
|
|
99
99
|
*
|
|
100
100
|
* Resolves for both success and failure — a non-zero exit is a normal outcome the
|
|
101
101
|
* model should see and correct from, not an exception. It rejects only on a
|
|
@@ -125,7 +125,7 @@ export async function executeCli(options) {
|
|
|
125
125
|
// argv can outrank it.
|
|
126
126
|
argv: [
|
|
127
127
|
'node',
|
|
128
|
-
'
|
|
128
|
+
'recursion',
|
|
129
129
|
`--api-key=${options.apiKey}`,
|
|
130
130
|
`--base-url=${options.baseUrl}`,
|
|
131
131
|
...options.args,
|
package/dist/git-host.js
CHANGED
|
@@ -128,7 +128,7 @@ function stashClaim(repoDir, claim) {
|
|
|
128
128
|
function readStashedClaim(repoDir) {
|
|
129
129
|
const path = claimFilePath(repoDir);
|
|
130
130
|
if (!existsSync(path)) {
|
|
131
|
-
throw new Error(`no claim found at ${path} — run \`
|
|
131
|
+
throw new Error(`no claim found at ${path} — run \`recursion scaffold\` in this directory first, or pass --path to point at a scaffolded repo`);
|
|
132
132
|
}
|
|
133
133
|
const parsed = StashedClaimSchema.safeParse(JSON.parse(readFileSync(path, 'utf8')));
|
|
134
134
|
if (!parsed.success) {
|
package/dist/manifest.js
CHANGED
|
@@ -159,7 +159,7 @@ export const ManifestOperationSchema = z.object({
|
|
|
159
159
|
params: z.array(ManifestParamSchema),
|
|
160
160
|
response: ShapeNodeSchema.optional(),
|
|
161
161
|
});
|
|
162
|
-
// ── Resources — the Reference browse surface (`
|
|
162
|
+
// ── Resources — the Reference browse surface (`recursion resources [<id>]`) ──────────
|
|
163
163
|
const ManifestResourceObjectSchema = z.object({
|
|
164
164
|
name: z.string(),
|
|
165
165
|
fields: z.array(ShapeNodeSchema),
|
|
@@ -175,14 +175,14 @@ export const ManifestResourceSchema = z.object({
|
|
|
175
175
|
object: ManifestResourceObjectSchema.optional(),
|
|
176
176
|
operationIds: z.array(z.string()),
|
|
177
177
|
});
|
|
178
|
-
// ── Recipes — the How-to browse surface (`
|
|
178
|
+
// ── Recipes — the How-to browse surface (`recursion recipes [<id>]`) ─────────────────
|
|
179
179
|
const RecipeSnippetSchema = z.object({ setup: z.string(), main: z.string() });
|
|
180
180
|
// Steps are read only to map a recipe to the resources it touches (via each SDK
|
|
181
181
|
// step's operationId), so they're modeled loosely — just the fields the CLI uses.
|
|
182
182
|
const ManifestRecipeStepSchema = z.object({ operationId: z.string().optional() });
|
|
183
183
|
// A recipe's place in the relationship graph (mirrors `RecipeRelated` in
|
|
184
184
|
// sdk-ts's recipes-schema). Modeled loosely here — the CLI only reads these to
|
|
185
|
-
// render the "Related" / "Unblocks" block on `
|
|
185
|
+
// render the "Related" / "Unblocks" block on `recursion recipes <id>`; the generator
|
|
186
186
|
// is the source of truth that validates them. Without these fields the manifest
|
|
187
187
|
// parse would silently strip `related`, so the CLI would never see the links.
|
|
188
188
|
const ManifestLinkTargetSchema = z.object({
|
|
@@ -224,7 +224,7 @@ export const ManifestRecipeSchema = z.object({
|
|
|
224
224
|
// The recipe's relationship links (optional — absent for an island recipe).
|
|
225
225
|
related: ManifestRelatedSchema.optional(),
|
|
226
226
|
});
|
|
227
|
-
// ── Concepts — the Explanation browse surface (`
|
|
227
|
+
// ── Concepts — the Explanation browse surface (`recursion explain [<concept>]`) ──────
|
|
228
228
|
export const ManifestConceptSchema = z.object({
|
|
229
229
|
id: z.string(),
|
|
230
230
|
title: z.string(),
|
|
@@ -234,7 +234,7 @@ export const ManifestConceptSchema = z.object({
|
|
|
234
234
|
// metadata — the frontend fetches the `.md` separately; the CLI bundles it).
|
|
235
235
|
body: z.string(),
|
|
236
236
|
});
|
|
237
|
-
// ── Tutorials — the getting-started docs (`
|
|
237
|
+
// ── Tutorials — the getting-started docs (`recursion tutorials [<id>]`) ──────────────
|
|
238
238
|
export const ManifestTutorialSchema = z.object({
|
|
239
239
|
id: z.string(),
|
|
240
240
|
title: z.string(),
|
|
@@ -279,7 +279,7 @@ export function parseManifest(value, source) {
|
|
|
279
279
|
// means no pre-rename package (@labelbox/rl-cli, or the older
|
|
280
280
|
// @recursion/cli) owns the `rl` bin. A pre-rename user sees the
|
|
281
281
|
// pre-rename binary's own message instead.
|
|
282
|
-
'this `
|
|
282
|
+
'this `recursion` CLI is out of date — upgrade it (`npm install -g @labelbox/recursion-cli`)'
|
|
283
283
|
: 'the server is older than this CLI — point --base-url at an up-to-date server';
|
|
284
284
|
throw new Error(`command-manifest format mismatch: this CLI speaks v${MANIFEST_FORMAT_VERSION}, ` +
|
|
285
285
|
`${source} served v${formatVersion}. ${direction}.`);
|
|
@@ -460,7 +460,7 @@ function selectCache(baseUrl) {
|
|
|
460
460
|
publishCacheIfAbsent(canonicalPath, legacy.raw);
|
|
461
461
|
return readCache(canonicalPath) ?? legacy;
|
|
462
462
|
}
|
|
463
|
-
// Every `
|
|
463
|
+
// Every `recursion` invocation gates on this fetch, so it must be bounded: a server that
|
|
464
464
|
// accepts the connection but never responds (a hung gateway, a stalled captive
|
|
465
465
|
// portal) would otherwise hang the CLI forever. A timeout makes `fetch` reject, so
|
|
466
466
|
// the catch below degrades to the cached manifest (or the clear cold-cache error).
|
package/dist/program.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare function parseBodyBase(opts: Record<string, unknown>): Record<str
|
|
|
20
20
|
* Where CLI output goes, and the reason nothing here touches `process` directly.
|
|
21
21
|
*
|
|
22
22
|
* The terminal entrypoint (`bin.ts`) binds these to the real streams. The MCP
|
|
23
|
-
* endpoint binds them to string buffers so one `
|
|
23
|
+
* endpoint binds them to string buffers so one `recursion` invocation can be executed
|
|
24
24
|
* **in-process** on behalf of an agent and its output returned as a tool result.
|
|
25
25
|
* A stray `process.stdout.write` would leak an agent's output into the server's
|
|
26
26
|
* logs; a stray `process.exit` would take the whole API server down mid-request.
|
|
@@ -86,7 +86,7 @@ export declare function leafShapeHelp(entry: ManifestOperation): string;
|
|
|
86
86
|
* and `--data` is the single documented way to pass a body.
|
|
87
87
|
*/
|
|
88
88
|
export declare function addOptions(command: Command, entry: ManifestOperation, fileFlags?: boolean): void;
|
|
89
|
-
/** Output formats `
|
|
89
|
+
/** Output formats `recursion recipes <id>` can render, mapped to the composed snippet field. */
|
|
90
90
|
declare const RECIPE_FORMATS: {
|
|
91
91
|
readonly cli: "cli";
|
|
92
92
|
readonly ts: "sdk";
|
|
@@ -94,7 +94,7 @@ declare const RECIPE_FORMATS: {
|
|
|
94
94
|
readonly curl: "curl";
|
|
95
95
|
};
|
|
96
96
|
export type RecipeFormat = keyof typeof RECIPE_FORMATS;
|
|
97
|
-
/** The full catalog, grouped by category, for `
|
|
97
|
+
/** The full catalog, grouped by category, for `recursion recipes`. */
|
|
98
98
|
export declare function renderRecipeList(reference: Record<string, ManifestRecipe>): string;
|
|
99
99
|
/**
|
|
100
100
|
* The "Related" + "Unblocks" block for a recipe — its place in the graph. The
|
|
@@ -104,10 +104,10 @@ export declare function renderRecipeList(reference: Record<string, ManifestRecip
|
|
|
104
104
|
* and where it can go next. Returns `''` when the recipe is an island.
|
|
105
105
|
*/
|
|
106
106
|
export declare function renderRelatedBlock(entry: ManifestRecipe, allRecipes: Record<string, ManifestRecipe>): string;
|
|
107
|
-
/** One recipe rendered for `
|
|
107
|
+
/** One recipe rendered for `recursion recipes <id>`: goal + composed code + related links + a docs link. */
|
|
108
108
|
export declare function renderRecipeShow(entry: ManifestRecipe, format: RecipeFormat, allRecipes: Record<string, ManifestRecipe>): string;
|
|
109
109
|
/** The program shell — name, description, version, and the global options. */
|
|
110
110
|
export declare function buildBaseProgram(version: string, io: CliIo): Command;
|
|
111
|
-
/** Build the full `
|
|
111
|
+
/** Build the full `recursion` program from a fetched manifest. */
|
|
112
112
|
export declare function buildProgram(manifest: Manifest, ctx: ProgramContext): Command;
|
|
113
113
|
export {};
|
package/dist/program.js
CHANGED
|
@@ -336,7 +336,7 @@ export function addOptions(command, entry, fileFlags = true) {
|
|
|
336
336
|
// resource type as their request body, so `agent_id` is both the path
|
|
337
337
|
// parameter and a property of the body schema. Commander throws when the same
|
|
338
338
|
// flag is added twice, and that throw happens while the whole program is
|
|
339
|
-
// being built, so one such operation takes down `
|
|
339
|
+
// being built, so one such operation takes down `recursion` entirely rather than
|
|
340
340
|
// just itself. Path and query win: they address the resource and are what the
|
|
341
341
|
// URL is built from. The shadowed body field is still settable through
|
|
342
342
|
// `--from-json` / `--data`.
|
|
@@ -386,15 +386,15 @@ export function addOptions(command, entry, fileFlags = true) {
|
|
|
386
386
|
command.option('--data <json>', 'Inline JSON request body');
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
|
-
// ── docs browse surfaces — one consistent shape: `
|
|
389
|
+
// ── docs browse surfaces — one consistent shape: `recursion <group> [<id>]` ─────────
|
|
390
390
|
//
|
|
391
391
|
// All four read-only browse groups (resources / recipes / explain / tutorials)
|
|
392
392
|
// take one positional shape: bare lists, an id shows that one. No `show`/`list`
|
|
393
|
-
// verbs (those are the action layer's, `
|
|
393
|
+
// verbs (those are the action layer's, `recursion <noun> <verb>`). All data is the live
|
|
394
394
|
// manifest; these commands are pure presentation (no further network).
|
|
395
395
|
// The Labelbox app host (not the recursion API base) — recipes/docs render in-app.
|
|
396
396
|
const RECIPE_DOCS_BASE_URL = 'https://app.labelbox.com';
|
|
397
|
-
/** Output formats `
|
|
397
|
+
/** Output formats `recursion recipes <id>` can render, mapped to the composed snippet field. */
|
|
398
398
|
const RECIPE_FORMATS = { cli: 'cli', ts: 'sdk', py: 'python', curl: 'curl' };
|
|
399
399
|
function isRecipeFormat(value) {
|
|
400
400
|
return Object.hasOwn(RECIPE_FORMATS, value);
|
|
@@ -421,7 +421,7 @@ function groupByDomain(entries, domainOf, domains) {
|
|
|
421
421
|
const ACTIONS_HELP_GROUP = 'Actions:';
|
|
422
422
|
const DOCS_HELP_GROUP = 'Documentation:';
|
|
423
423
|
const GIT_HOST_HELP_GROUP = 'Coding tasks:';
|
|
424
|
-
/** Nested resource groups (`
|
|
424
|
+
/** Nested resource groups (`recursion problems --help`, etc.) — allowed subcommands. */
|
|
425
425
|
const COMMANDS_HELP_GROUP = 'Commands';
|
|
426
426
|
/** Nested resource groups — subcommands the caller can't run (separate help section). */
|
|
427
427
|
const UNAVAILABLE_HELP_GROUP = 'Unavailable (missing permission)';
|
|
@@ -463,7 +463,7 @@ function resolveResourceDomain(resource, byId) {
|
|
|
463
463
|
}
|
|
464
464
|
return undefined;
|
|
465
465
|
}
|
|
466
|
-
/** The `
|
|
466
|
+
/** The `recursion <callPath>` invocation for an operation, with its required-flag hints. */
|
|
467
467
|
function operationInvocation(op) {
|
|
468
468
|
const flags = [];
|
|
469
469
|
for (const param of op.params) {
|
|
@@ -475,12 +475,12 @@ function operationInvocation(op) {
|
|
|
475
475
|
if (op.bodyKey)
|
|
476
476
|
flags.push('--data <json>');
|
|
477
477
|
// kebab each segment so the printed command matches the real one (the tree is
|
|
478
|
-
// built from `kebab(segment)`) — `
|
|
478
|
+
// built from `kebab(segment)`) — `recursion environments attach-external-id`, not the
|
|
479
479
|
// camelCase callPath.
|
|
480
480
|
const command = op.callPath.map(kebab).join(' ');
|
|
481
|
-
return `
|
|
481
|
+
return `recursion ${command}${flags.length ? ` ${flags.join(' ')}` : ''}`;
|
|
482
482
|
}
|
|
483
|
-
/** `
|
|
483
|
+
/** `recursion resources [<id>]` — the Reference hub (grouped browse, or one resource). */
|
|
484
484
|
function addResourcesCommand(program, manifest, io) {
|
|
485
485
|
const byId = manifest.resources;
|
|
486
486
|
program
|
|
@@ -493,11 +493,11 @@ function addResourcesCommand(program, manifest, io) {
|
|
|
493
493
|
return;
|
|
494
494
|
}
|
|
495
495
|
// `Object.hasOwn`, not `byId[id]`: a Zod `z.record` is a plain object, so a bare
|
|
496
|
-
// read walks the prototype chain — `
|
|
496
|
+
// read walks the prototype chain — `recursion resources constructor` would resolve to a
|
|
497
497
|
// truthy inherited member, bypass the not-found guard, and crash in the renderer.
|
|
498
498
|
const entry = Object.hasOwn(byId, id) ? byId[id] : undefined;
|
|
499
499
|
if (!entry) {
|
|
500
|
-
throw new Error(`unknown resource "${id}". Run \`
|
|
500
|
+
throw new Error(`unknown resource "${id}". Run \`recursion resources\` to list resources.`);
|
|
501
501
|
}
|
|
502
502
|
io.stdout(renderResourceShow(entry, manifest));
|
|
503
503
|
});
|
|
@@ -506,7 +506,7 @@ function renderResourceList(manifest) {
|
|
|
506
506
|
const byId = manifest.resources;
|
|
507
507
|
const all = Object.values(byId);
|
|
508
508
|
const groups = groupByDomain(all, (r) => resolveResourceDomain(r, byId), manifest.domains);
|
|
509
|
-
const lines = ['Resources — run `
|
|
509
|
+
const lines = ['Resources — run `recursion resources <id>` for the full overview.', ''];
|
|
510
510
|
for (const group of groups) {
|
|
511
511
|
lines.push(group.title);
|
|
512
512
|
const sorted = group.entries.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id, 'en'));
|
|
@@ -549,7 +549,7 @@ function renderResourceShow(resource, manifest) {
|
|
|
549
549
|
}
|
|
550
550
|
return `${parts.join('\n')}\n`;
|
|
551
551
|
}
|
|
552
|
-
/** `
|
|
552
|
+
/** `recursion recipes [<id>]` — How-to (list grouped by category, or one recipe). */
|
|
553
553
|
function addRecipesCommand(program, manifest, io) {
|
|
554
554
|
program
|
|
555
555
|
.command('recipes [id]')
|
|
@@ -564,7 +564,7 @@ function addRecipesCommand(program, manifest, io) {
|
|
|
564
564
|
// Object.hasOwn guards the prototype chain (see the resources hub above).
|
|
565
565
|
const entry = Object.hasOwn(manifest.recipes, id) ? manifest.recipes[id] : undefined;
|
|
566
566
|
if (!entry) {
|
|
567
|
-
throw new Error(`unknown recipe "${id}". Run \`
|
|
567
|
+
throw new Error(`unknown recipe "${id}". Run \`recursion recipes\` to see all recipes.`);
|
|
568
568
|
}
|
|
569
569
|
const format = opts.format ?? 'cli';
|
|
570
570
|
// `Object.hasOwn`, not `format in RECIPE_FORMATS`: `in` walks the prototype
|
|
@@ -577,7 +577,7 @@ function addRecipesCommand(program, manifest, io) {
|
|
|
577
577
|
io.stdout(renderRecipeShow(entry, format, manifest.recipes));
|
|
578
578
|
});
|
|
579
579
|
}
|
|
580
|
-
/** The full catalog, grouped by category, for `
|
|
580
|
+
/** The full catalog, grouped by category, for `recursion recipes`. */
|
|
581
581
|
export function renderRecipeList(reference) {
|
|
582
582
|
const entries = Object.values(reference);
|
|
583
583
|
if (entries.length === 0)
|
|
@@ -588,7 +588,7 @@ export function renderRecipeList(reference) {
|
|
|
588
588
|
group.push(entry);
|
|
589
589
|
byCategory.set(entry.category, group);
|
|
590
590
|
}
|
|
591
|
-
const lines = ['Recipes — run `
|
|
591
|
+
const lines = ['Recipes — run `recursion recipes <id>` for the full walkthrough.', ''];
|
|
592
592
|
for (const category of [...byCategory.keys()].sort()) {
|
|
593
593
|
lines.push(category);
|
|
594
594
|
const group = byCategory.get(category) ?? [];
|
|
@@ -649,7 +649,7 @@ export function renderRelatedBlock(entry, allRecipes) {
|
|
|
649
649
|
}
|
|
650
650
|
return lines.length > 0 ? `Related\n${lines.join('\n')}\n` : '';
|
|
651
651
|
}
|
|
652
|
-
/** One recipe rendered for `
|
|
652
|
+
/** One recipe rendered for `recursion recipes <id>`: goal + composed code + related links + a docs link. */
|
|
653
653
|
export function renderRecipeShow(entry, format, allRecipes) {
|
|
654
654
|
// Optional on the wire: an older server predates the Python surface, so
|
|
655
655
|
// `--format py` renders the note rather than crashing on a missing snippet.
|
|
@@ -672,7 +672,7 @@ export function renderRecipeShow(entry, format, allRecipes) {
|
|
|
672
672
|
'',
|
|
673
673
|
].join('\n');
|
|
674
674
|
}
|
|
675
|
-
/** `
|
|
675
|
+
/** `recursion explain [<concept>]` — Explanation (list grouped by domain, or one page). */
|
|
676
676
|
function addExplainCommand(program, manifest, io) {
|
|
677
677
|
program
|
|
678
678
|
.command('explain [concept]')
|
|
@@ -688,14 +688,17 @@ function addExplainCommand(program, manifest, io) {
|
|
|
688
688
|
? manifest.concepts[concept]
|
|
689
689
|
: undefined;
|
|
690
690
|
if (!entry) {
|
|
691
|
-
throw new Error(`unknown concept "${concept}". Run \`
|
|
691
|
+
throw new Error(`unknown concept "${concept}". Run \`recursion explain\` to list concepts.`);
|
|
692
692
|
}
|
|
693
693
|
io.stdout(renderConceptShow(entry));
|
|
694
694
|
});
|
|
695
695
|
}
|
|
696
696
|
function renderConceptList(manifest) {
|
|
697
697
|
const groups = groupByDomain(Object.values(manifest.concepts), (c) => c.domain, manifest.domains);
|
|
698
|
-
const lines = [
|
|
698
|
+
const lines = [
|
|
699
|
+
'Explanations — run `recursion explain <concept>` for the full page.',
|
|
700
|
+
'',
|
|
701
|
+
];
|
|
699
702
|
for (const group of groups) {
|
|
700
703
|
lines.push(group.title);
|
|
701
704
|
for (const c of group.entries.sort((a, b) => a.id.localeCompare(b.id, 'en'))) {
|
|
@@ -713,7 +716,7 @@ function renderConceptShow(concept) {
|
|
|
713
716
|
}
|
|
714
717
|
return `${parts.join('\n')}\n`;
|
|
715
718
|
}
|
|
716
|
-
/** `
|
|
719
|
+
/** `recursion tutorials [<id>]` — Tutorials (the existing getting-started docs). */
|
|
717
720
|
function addTutorialsCommand(program, manifest, io) {
|
|
718
721
|
program
|
|
719
722
|
.command('tutorials [id]')
|
|
@@ -721,7 +724,10 @@ function addTutorialsCommand(program, manifest, io) {
|
|
|
721
724
|
.description('Browse getting-started tutorials (run without an argument to list all)')
|
|
722
725
|
.action((id) => {
|
|
723
726
|
if (id === undefined) {
|
|
724
|
-
const lines = [
|
|
727
|
+
const lines = [
|
|
728
|
+
'Tutorials — run `recursion tutorials <id>` for the full text.',
|
|
729
|
+
'',
|
|
730
|
+
];
|
|
725
731
|
for (const t of manifest.tutorials)
|
|
726
732
|
lines.push(` ${t.id} — ${t.title}`);
|
|
727
733
|
io.stdout(`${lines.join('\n')}\n`);
|
|
@@ -729,7 +735,7 @@ function addTutorialsCommand(program, manifest, io) {
|
|
|
729
735
|
}
|
|
730
736
|
const entry = manifest.tutorials.find((t) => t.id === id);
|
|
731
737
|
if (!entry) {
|
|
732
|
-
throw new Error(`unknown tutorial "${id}". Run \`
|
|
738
|
+
throw new Error(`unknown tutorial "${id}". Run \`recursion tutorials\` to list tutorials.`);
|
|
733
739
|
}
|
|
734
740
|
if (entry.body === null) {
|
|
735
741
|
io.stdout(`${entry.title}\n\nThis tutorial is a notebook — open it in the app: ` +
|
|
@@ -741,7 +747,7 @@ function addTutorialsCommand(program, manifest, io) {
|
|
|
741
747
|
}
|
|
742
748
|
/** The program shell — name, description, version, and the global options. */
|
|
743
749
|
export function buildBaseProgram(version, io) {
|
|
744
|
-
return (new Command('
|
|
750
|
+
return (new Command('recursion')
|
|
745
751
|
// Route commander's own output (help text, unknown-command errors, invalid
|
|
746
752
|
// option values) through the caller's sinks, and turn its `process.exit`
|
|
747
753
|
// calls into thrown `CommanderError`s.
|
|
@@ -753,13 +759,13 @@ export function buildBaseProgram(version, io) {
|
|
|
753
759
|
// process streams and still calling `process.exit`.
|
|
754
760
|
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr })
|
|
755
761
|
.exitOverride()
|
|
756
|
-
.description('Command-line interface for the Recursion
|
|
762
|
+
.description('Command-line interface for the Recursion Platform')
|
|
757
763
|
.version(version)
|
|
758
764
|
.option('--api-key <key>', 'API key (defaults to the LABELBOX_API_KEY env var)')
|
|
759
765
|
.option('--base-url <url>', 'Recursion API base URL (defaults to the RECURSION_BASE_URL env var, then the production host)')
|
|
760
766
|
.option('--quiet', 'Output only the resulting resource id (JSON otherwise)'));
|
|
761
767
|
}
|
|
762
|
-
/** Build the full `
|
|
768
|
+
/** Build the full `recursion` program from a fetched manifest. */
|
|
763
769
|
export function buildProgram(manifest, ctx) {
|
|
764
770
|
// `ctx` structurally satisfies CliIo, so the root program — and by inheritance
|
|
765
771
|
// every command built below — writes to the caller's sinks and never exits.
|
|
@@ -859,7 +865,7 @@ export function buildProgram(manifest, ctx) {
|
|
|
859
865
|
for (const [parent, gatedCount] of gatedCountByParent) {
|
|
860
866
|
parent.addHelpText('before', permissionGroupBanner(gatedCount));
|
|
861
867
|
}
|
|
862
|
-
// The docs browse groups + the bespoke `
|
|
868
|
+
// The docs browse groups + the bespoke `recursion skills` action group.
|
|
863
869
|
addResourcesCommand(program, manifest, ctx);
|
|
864
870
|
addRecipesCommand(program, manifest, ctx);
|
|
865
871
|
addExplainCommand(program, manifest, ctx);
|
|
@@ -873,7 +879,7 @@ export function buildProgram(manifest, ctx) {
|
|
|
873
879
|
// Lazy implicit help skips _initCommandGroup unless created via helpCommand(true).
|
|
874
880
|
program.helpCommand(true);
|
|
875
881
|
program.addHelpText('after', `
|
|
876
|
-
Run \`
|
|
877
|
-
Browse the full reference with \`
|
|
882
|
+
Run \`recursion <command> --help\` for details and flags on any command.
|
|
883
|
+
Browse the full reference with \`recursion resources\`, or guided walkthroughs with \`recursion recipes\`.`);
|
|
878
884
|
return program;
|
|
879
885
|
}
|
package/dist/resolve.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare function flagValue(argv: string[], name: string): string | undefi
|
|
|
4
4
|
export declare function resolveApiKey(argv: string[]): string | undefined;
|
|
5
5
|
/**
|
|
6
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 `
|
|
7
|
+
* deprecated `RL_GYM_BASE_URL`. Shared by `resolveBaseUrl` here and the `recursion skills`
|
|
8
8
|
* resolver so the precedence lives — and is tested — in one place.
|
|
9
9
|
*/
|
|
10
10
|
export declare function envBaseUrl(): string | undefined;
|
package/dist/resolve.js
CHANGED
|
@@ -13,7 +13,7 @@ export function flagValue(argv, name) {
|
|
|
13
13
|
const index = argv.indexOf(`--${name}`);
|
|
14
14
|
if (index >= 0) {
|
|
15
15
|
// The space-form value is the next token — but only if it isn't itself an
|
|
16
|
-
// option (`
|
|
16
|
+
// option (`recursion --api-key --quiet`): a dangling `--api-key` has no value, so we
|
|
17
17
|
// return undefined and let the env fallback apply rather than silently
|
|
18
18
|
// consuming `--quiet` as a bogus (non-empty) key. Values that legitimately
|
|
19
19
|
// start with `-` use the `--name=value` form above.
|
|
@@ -30,7 +30,7 @@ export function resolveApiKey(argv) {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
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 `
|
|
33
|
+
* deprecated `RL_GYM_BASE_URL`. Shared by `resolveBaseUrl` here and the `recursion skills`
|
|
34
34
|
* resolver so the precedence lives — and is tested — in one place.
|
|
35
35
|
*/
|
|
36
36
|
export function envBaseUrl() {
|
package/dist/run.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface RunDeps {
|
|
|
31
31
|
*
|
|
32
32
|
* **Never throws and never exits.** It returns the exit code the caller should
|
|
33
33
|
* use, having already rendered any failure to `deps.stderr`. That contract is
|
|
34
|
-
* what lets the MCP endpoint run one `
|
|
34
|
+
* what lets the MCP endpoint run one `recursion` invocation in-process on behalf of an
|
|
35
35
|
* agent: a thrown error would become a 500 instead of a tool result, and a
|
|
36
36
|
* `process.exit` would take the API server down mid-request. `bin.ts` is the only
|
|
37
37
|
* caller that turns the returned code back into a real exit.
|
package/dist/run.js
CHANGED
|
@@ -12,7 +12,7 @@ const MISSING_KEY_MESSAGE = 'missing API key — set LABELBOX_API_KEY or pass --
|
|
|
12
12
|
*
|
|
13
13
|
* **Never throws and never exits.** It returns the exit code the caller should
|
|
14
14
|
* use, having already rendered any failure to `deps.stderr`. That contract is
|
|
15
|
-
* what lets the MCP endpoint run one `
|
|
15
|
+
* what lets the MCP endpoint run one `recursion` invocation in-process on behalf of an
|
|
16
16
|
* agent: a thrown error would become a 500 instead of a tool result, and a
|
|
17
17
|
* `process.exit` would take the API server down mid-request. `bin.ts` is the only
|
|
18
18
|
* caller that turns the returned code back into a real exit.
|
package/dist/skills.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare function listSkills(opts: {
|
|
|
41
41
|
apiKey?: string | undefined;
|
|
42
42
|
baseUrl: string;
|
|
43
43
|
}): Promise<SkillSummary[]>;
|
|
44
|
-
/** Programmatic core of `
|
|
44
|
+
/** Programmatic core of `recursion skills check` — fetch latest, read installed, compare. */
|
|
45
45
|
export declare function checkSkill(name: string, opts: {
|
|
46
46
|
apiKey: string;
|
|
47
47
|
baseUrl: string;
|
|
@@ -53,7 +53,7 @@ export declare function checkSkill(name: string, opts: {
|
|
|
53
53
|
/** Raised when `install` refuses to clobber a locally-modified file (no `--force`). */
|
|
54
54
|
export declare class HandEditedError extends Error {
|
|
55
55
|
}
|
|
56
|
-
/** Programmatic core of `
|
|
56
|
+
/** Programmatic core of `recursion skills install` — fetch latest and write it (guarded). */
|
|
57
57
|
export declare function installSkill(name: string, opts: {
|
|
58
58
|
apiKey: string;
|
|
59
59
|
baseUrl: string;
|
package/dist/skills.js
CHANGED
|
@@ -6,14 +6,14 @@ import process from 'node:process';
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { DEFAULT_BASE_URL, supportUrl } from './manifest.js';
|
|
8
8
|
import { envBaseUrl } from './resolve.js';
|
|
9
|
-
// ── bespoke `
|
|
9
|
+
// ── bespoke `recursion skills` group (hand-written, not spec-derived) ────────────────
|
|
10
10
|
//
|
|
11
11
|
// Keeps a downloaded Claude skill current. The platform serves each skill from
|
|
12
12
|
// `GET /skills/:skillName` with a content-hash version; this group fetches that,
|
|
13
13
|
// compares it to the installed `~/.claude/skills/<name>/SKILL.md`, and refreshes
|
|
14
|
-
// it. The skill's own doctor preflight runs `
|
|
15
|
-
// refreshes via `
|
|
16
|
-
// endpoint — that would auto-generate a colliding `
|
|
14
|
+
// it. The skill's own doctor preflight runs `recursion skills check` each session and
|
|
15
|
+
// refreshes via `recursion skills install` when stale. Deliberately NOT an `@SdkRoute`
|
|
16
|
+
// endpoint — that would auto-generate a colliding `recursion skills` command.
|
|
17
17
|
/** The skill installed by default when no name is given. */
|
|
18
18
|
const DEFAULT_SKILL = 'recursion';
|
|
19
19
|
// ── hash contract — MUST stay byte-identical to apps/recursion/api/src/skills/skill-hash.ts ──
|
|
@@ -79,9 +79,9 @@ function resolveAuth(program) {
|
|
|
79
79
|
}
|
|
80
80
|
return { apiKey, baseUrl: resolveBaseUrl(program) };
|
|
81
81
|
}
|
|
82
|
-
// Each `
|
|
82
|
+
// Each `recursion skills` fetch is bounded for the same reason as the manifest fetch
|
|
83
83
|
// (see manifest.ts): a server that accepts the connection but never responds would
|
|
84
|
-
// otherwise hang `
|
|
84
|
+
// otherwise hang `recursion skills check`/`install`/`list` indefinitely. The timeout makes
|
|
85
85
|
// `fetch` reject so the caller surfaces a clear error rather than blocking forever.
|
|
86
86
|
const SKILLS_FETCH_TIMEOUT_MS = 30_000;
|
|
87
87
|
/**
|
|
@@ -143,7 +143,7 @@ function readInstalled(path) {
|
|
|
143
143
|
return undefined;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
/** Programmatic core of `
|
|
146
|
+
/** Programmatic core of `recursion skills check` — fetch latest, read installed, compare. */
|
|
147
147
|
export async function checkSkill(name, opts) {
|
|
148
148
|
const latest = await fetchSkill(name, opts.apiKey, opts.baseUrl);
|
|
149
149
|
const installed = readInstalled(installedPath(name, opts.skillFile));
|
|
@@ -152,7 +152,7 @@ export async function checkSkill(name, opts) {
|
|
|
152
152
|
/** Raised when `install` refuses to clobber a locally-modified file (no `--force`). */
|
|
153
153
|
export class HandEditedError extends Error {
|
|
154
154
|
}
|
|
155
|
-
/** Programmatic core of `
|
|
155
|
+
/** Programmatic core of `recursion skills install` — fetch latest and write it (guarded). */
|
|
156
156
|
export async function installSkill(name, opts) {
|
|
157
157
|
const latest = await fetchSkill(name, opts.apiKey, opts.baseUrl);
|
|
158
158
|
const path = installedPath(name, opts.skillFile);
|
|
@@ -197,7 +197,7 @@ export function addSkillsCommands(program, docsHelpGroup) {
|
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
const lines = available.map((s) => s.description ? ` ${s.name} — ${s.description}` : ` ${s.name}`);
|
|
200
|
-
process.stdout.write(`Installable skills (run \`
|
|
200
|
+
process.stdout.write(`Installable skills (run \`recursion skills install <name>\`):\n${lines.join('\n')}\n`);
|
|
201
201
|
}
|
|
202
202
|
catch (err) {
|
|
203
203
|
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -218,7 +218,7 @@ export function addSkillsCommands(program, docsHelpGroup) {
|
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
220
|
const lead = status === 'missing' ? 'is not installed' : 'is out of date';
|
|
221
|
-
process.stdout.write(`${skill} ${lead} — run \`
|
|
221
|
+
process.stdout.write(`${skill} ${lead} — run \`recursion skills install ${skill}\`.\n`);
|
|
222
222
|
process.exit(1);
|
|
223
223
|
}
|
|
224
224
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@labelbox/recursion-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Labelbox/recursion-platform.git"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
+
"recursion": "./dist/bin.js",
|
|
10
11
|
"rl": "./dist/bin.js"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|