@eigenpal/cli 0.5.7 → 0.5.9
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/CHANGELOG.md +12 -1
- package/dist/cli.js +251 -30
- package/dist/skill/reference/cli/workflow.md +18 -12
- package/dist/skill/reference/workflow-yaml.md +70 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @eigenpal/cli
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.5.9
|
|
4
4
|
|
|
5
5
|
### Major Changes
|
|
6
6
|
|
|
@@ -317,6 +317,12 @@ actual) { ... }` declaration around your statements. Parameter order is
|
|
|
317
317
|
- **`init agent` and bare `agent` now exit 2** instead of 0 when invoked. Both subcommands are reserved-but-unimplemented placeholders, and exiting 0 made it possible for a CI script that misrouted into one of them to silently no-op. POSIX exit 2 ("command exists but isn't usable as invoked") makes the misroute fail loudly. The human-readable "coming soon" stderr message is unchanged.
|
|
318
318
|
- **New `--no-color` global flag.** Disables ANSI colors in all output, matching the convention shared by `gh`, `kubectl`, `npm`, and `terraform`. Equivalent to setting `NO_COLOR=1` in the environment, but takes effect for the current invocation only. Implemented as a lazy picocolors wrapper so the flip propagates to every helper in `lib/ui.ts` (and every downstream caller of `pc`) regardless of when in the command lifecycle the flag is parsed.
|
|
319
319
|
|
|
320
|
+
- 809f3d4: `auth list`: the org name is now the primary label (bold, immediately after
|
|
321
|
+
the marker), the profile slug is dimmed beside it (still the value you feed
|
|
322
|
+
to `auth use <name>` or `EIGENPAL_PROFILE=<name>`), and the base URL trails
|
|
323
|
+
on the right. The redundant `(tenantId)` column was dropped — the profile
|
|
324
|
+
slug already disambiguates. `auth use` (interactive picker) now prints the
|
|
325
|
+
non-interactive form on success so you learn the slug you just picked.
|
|
320
326
|
- 49a8f94: Fixed a stray `undefined` in the `eigenpal auth login` outro. The success message used `dim(...)` (a stderr printer that returns `void`) inside a template literal where `ui.dim(...)` (the inline string formatter) was meant. The dim "Saved profile X — switch with" hint now renders correctly instead of literal `undefined`.
|
|
321
327
|
- 16c6e83: Updated `@eigenpal/cli` author from `Eigenpal <hello@eigenpal.com>` to `Jedr Blaszyk <jedr@eigenpal.com>`. Surfaces on the npm package page and in `npm view @eigenpal/cli`.
|
|
322
328
|
- b1d2d77: CLI: default `baseUrl` is now `https://studio.eigenpal.com` instead of `http://localhost:3000`.
|
|
@@ -327,6 +333,10 @@ actual) { ... }` declaration around your statements. Parameter order is
|
|
|
327
333
|
|
|
328
334
|
Also scrubs internal references from CLI source ahead of open-sourcing — generic placeholder names in docstrings and test fixtures, internal-tracker IDs stripped from comments, and one user-facing error message updated to point at the public issue tracker.
|
|
329
335
|
|
|
336
|
+
- 75caaec: `eigenpal workflow push` and `eigenpal workflow validate` now surface non-fatal **schema-quality warnings** when an output schema is loose enough to hurt downstream typing: categorical fields like `category`/`status`/`kind` declared as plain strings (no `enum`), `transform.script` functions returning `any`/`unknown`, untyped objects (`type: object` with no `properties`), and untyped arrays (`type: array` with no `items`). Warnings print to stderr and never block the push.
|
|
337
|
+
|
|
338
|
+
Updated the bundled CLI skill (`workflow-yaml.md`) with a "Be specific with types" section teaching `enum` fields, per-value meanings inline in `description`, and how `transform.script` TS literal unions auto-produce JSON Schema enums.
|
|
339
|
+
|
|
330
340
|
- be693a3: `EIGENPAL_API_KEY` is now a complete profile bypass — when set, the active profile's `baseUrl` is also ignored (previously the env key was paired with the profile's URL, which is always wrong because an API key is provisioned for one server).
|
|
331
341
|
|
|
332
342
|
In CI:
|
|
@@ -467,6 +477,7 @@ actual) { ... }` declaration around your statements. Parameter order is
|
|
|
467
477
|
|
|
468
478
|
- 71361fd: `workflow get-experiment-status --watch` now prints a per-example failure summary on stderr when the batch finishes with any failed/cancelled execution: example name, top-level error (truncated), and the exact `eigenpal execution get <exec-id>` command to drill into step-by-step detail. Removes the need for the agent to grep through a 1000-line JSON dump to find which example broke and why. `reference/debugging.md` documents the discovery path.
|
|
469
479
|
- 71361fd: `eigenpal workflow get-experiment-status --watch` now polls until every execution in the batch reaches a terminal state (`completed` / `failed` / `cancelled`), then exits non-zero if any execution failed or was cancelled. Removes the need for agent-side polling loops that didn't notice all-terminal-already and kept ticking. Renders a one-line `N/M terminal (counts)` progress summary on stderr (in-place rewrite when stderr is a TTY), dumps the full JSON on stdout when done. `--interval <s>` and `--max-wait <s>` are configurable; defaults are 5s/30min.
|
|
480
|
+
- c3bc4e9: Fix Git-backed source release and status edge cases.
|
|
470
481
|
- 04e4018: Harden hidden Git materialization commands for package-ref installs, frozen lockfile replay, dependency materialization, and source authoring smoke flows.
|
|
471
482
|
- 71361fd: `install-skill`: multi-tool support via interactive multiselect.
|
|
472
483
|
- Opens a `@clack/prompts` multiselect picker over supported agent tools
|
package/dist/cli.js
CHANGED
|
@@ -28107,7 +28107,8 @@ var init_parser = __esm(() => {
|
|
|
28107
28107
|
maxConcurrency: exports_external.number().min(1).max(30).default(3),
|
|
28108
28108
|
pagesPerBatch: exports_external.number().min(1).max(20).default(5),
|
|
28109
28109
|
prompt: exports_external.string().optional().describe("Custom prompt for LLM-based parsing"),
|
|
28110
|
-
languages: exports_external.array(exports_external.string()).optional().describe("OCR language hints")
|
|
28110
|
+
languages: exports_external.array(exports_external.string()).optional().describe("OCR language hints"),
|
|
28111
|
+
tenantId: exports_external.string().optional()
|
|
28111
28112
|
});
|
|
28112
28113
|
ParserInputSchema = exports_external.object({
|
|
28113
28114
|
data: exports_external.instanceof(Buffer).or(exports_external.instanceof(Uint8Array)),
|
|
@@ -65788,8 +65789,16 @@ function convertTsTypeToJsonSchema(node, ctx) {
|
|
|
65788
65789
|
case "TSVoidKeyword":
|
|
65789
65790
|
return {};
|
|
65790
65791
|
case "TSAnyKeyword":
|
|
65791
|
-
case "TSUnknownKeyword":
|
|
65792
|
+
case "TSUnknownKeyword": {
|
|
65793
|
+
const keyword = node.type === "TSAnyKeyword" ? "any" : "unknown";
|
|
65794
|
+
ctx.warnings.push({
|
|
65795
|
+
code: "weak-return-type",
|
|
65796
|
+
message: `\`${keyword}\` produces an empty schema, so downstream steps can't autocomplete this field or its descendants. Describe the actual shape (\`{ name: string; total: number }\`) or a literal union (\`"low" | "medium" | "high"\`) for the strongest downstream typing.`,
|
|
65797
|
+
start: node.start ?? undefined,
|
|
65798
|
+
end: node.end ?? undefined
|
|
65799
|
+
});
|
|
65792
65800
|
return {};
|
|
65801
|
+
}
|
|
65793
65802
|
case "TSNeverKeyword":
|
|
65794
65803
|
return { not: {} };
|
|
65795
65804
|
case "TSArrayType":
|
|
@@ -66138,13 +66147,15 @@ function compileTypedScript(opts) {
|
|
|
66138
66147
|
});
|
|
66139
66148
|
}
|
|
66140
66149
|
const convertIssues = [];
|
|
66150
|
+
const convertWarnings = [];
|
|
66141
66151
|
const inScopeSchemas = new Map(Object.entries(opts.inScopeSchemas ?? {}));
|
|
66142
66152
|
const localAliases = collectLocalAliases(aliasDecls);
|
|
66143
66153
|
const returnSchema = opts.kind === "evaluator" ? { type: "number" } : convertTsTypeToJsonSchema(returnTsType, {
|
|
66144
66154
|
localAliases,
|
|
66145
66155
|
inScopeSchemas,
|
|
66146
66156
|
resolving: new Set,
|
|
66147
|
-
issues: convertIssues
|
|
66157
|
+
issues: convertIssues,
|
|
66158
|
+
warnings: convertWarnings
|
|
66148
66159
|
});
|
|
66149
66160
|
if (convertIssues.length > 0) {
|
|
66150
66161
|
return {
|
|
@@ -66157,10 +66168,7 @@ function compileTypedScript(opts) {
|
|
|
66157
66168
|
}
|
|
66158
66169
|
let stripped;
|
|
66159
66170
|
try {
|
|
66160
|
-
|
|
66161
|
-
transforms: ["typescript"]
|
|
66162
|
-
});
|
|
66163
|
-
stripped = out.code;
|
|
66171
|
+
stripped = import_sucrase.transform(opts.source, { transforms: ["typescript"] }).code;
|
|
66164
66172
|
} catch (err) {
|
|
66165
66173
|
const e2 = err;
|
|
66166
66174
|
return failWith({
|
|
@@ -66174,7 +66182,8 @@ function compileTypedScript(opts) {
|
|
|
66174
66182
|
js: stripped,
|
|
66175
66183
|
returnSchema,
|
|
66176
66184
|
paramNames: actualParamNames
|
|
66177
|
-
}
|
|
66185
|
+
},
|
|
66186
|
+
warnings: convertWarnings
|
|
66178
66187
|
};
|
|
66179
66188
|
}
|
|
66180
66189
|
function failWith(issue2) {
|
|
@@ -181601,7 +181610,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
181601
181610
|
// package.json
|
|
181602
181611
|
var package_default = {
|
|
181603
181612
|
name: "@eigenpal/cli",
|
|
181604
|
-
version: "0.5.
|
|
181613
|
+
version: "0.5.9",
|
|
181605
181614
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
181606
181615
|
keywords: [
|
|
181607
181616
|
"eigenpal",
|
|
@@ -181641,6 +181650,8 @@ var package_default = {
|
|
|
181641
181650
|
scripts: {
|
|
181642
181651
|
dev: "bun src/cli.ts",
|
|
181643
181652
|
build: "bun run generate:skill && bun build src/cli.ts --outdir dist --target node --format esm && rm -rf dist/templates dist/skill && cp -R src/templates dist/templates && cp -R src/skill dist/skill",
|
|
181653
|
+
generate: "bun run generate:cli-docs && bun run generate:skill",
|
|
181654
|
+
"generate:check": "bun run generate:cli-docs:check && bun run generate:skill:check",
|
|
181644
181655
|
"generate:skill": "bun scripts/generate-skill-reference.ts",
|
|
181645
181656
|
"generate:skill:check": "bun scripts/generate-skill-reference.ts --check",
|
|
181646
181657
|
"generate:cli-docs": "bun scripts/generate-cli-docs.ts",
|
|
@@ -183847,16 +183858,19 @@ async function authList() {
|
|
|
183847
183858
|
dim("No profiles configured. Run `eigenpal auth login` to add one.");
|
|
183848
183859
|
return;
|
|
183849
183860
|
}
|
|
183850
|
-
const
|
|
183851
|
-
for (const name of names.sort()) {
|
|
183861
|
+
const rows = names.sort().map((name) => {
|
|
183852
183862
|
const p2 = profiles[name];
|
|
183853
|
-
const marker = name === current ? ui.ok("●") : ui.dim("○");
|
|
183854
183863
|
const tenant = p2.tenantName ?? p2.tenantId ?? "?";
|
|
183855
|
-
|
|
183856
|
-
|
|
183864
|
+
return { name, baseUrl: p2.baseUrl, tenant };
|
|
183865
|
+
});
|
|
183866
|
+
const tenantWidth = Math.max(16, ...rows.map((r) => r.tenant.length));
|
|
183867
|
+
const nameWidth = Math.max(16, ...rows.map((r) => r.name.length));
|
|
183868
|
+
for (const r of rows) {
|
|
183869
|
+
const marker = r.name === current ? ui.ok("●") : ui.dim("○");
|
|
183870
|
+
console.log(`${marker} ${ui.bold(r.tenant.padEnd(tenantWidth))} ${ui.dim(r.name.padEnd(nameWidth))} ${ui.dim(r.baseUrl)}`);
|
|
183857
183871
|
}
|
|
183858
183872
|
console.log("");
|
|
183859
|
-
dim(`(● = active. Switch with \`eigenpal auth use <name>\` or \`EIGENPAL_PROFILE=<name
|
|
183873
|
+
dim(`(● = active. Switch with \`eigenpal auth use <name>\` or \`EIGENPAL_PROFILE=<name>\` for one shell.)`);
|
|
183860
183874
|
}
|
|
183861
183875
|
async function authUse(profileName) {
|
|
183862
183876
|
const { current, profiles } = listProfiles();
|
|
@@ -183901,7 +183915,8 @@ async function authUse(profileName) {
|
|
|
183901
183915
|
if (profileName) {
|
|
183902
183916
|
success2(`Switched to ${ui.bold(target)}${tenantSuffix}`);
|
|
183903
183917
|
} else {
|
|
183904
|
-
ye(`${ui.ok("✓")} Switched to ${ui.bold(target)}${tenantSuffix}
|
|
183918
|
+
ye(`${ui.ok("✓")} Switched to ${ui.bold(target)}${tenantSuffix}
|
|
183919
|
+
${ui.dim(`In scripts: \`eigenpal auth use ${target}\` or \`EIGENPAL_PROFILE=${target}\``)}`);
|
|
183905
183920
|
}
|
|
183906
183921
|
}
|
|
183907
183922
|
|
|
@@ -184616,6 +184631,9 @@ function checkRepoVersion(gitRoot, mutation) {
|
|
|
184616
184631
|
function resolvePackagePath(target) {
|
|
184617
184632
|
return target.includes("/") ? SourcePackagePathSchema.parse(target) : dottedPackageNameToPath(DottedPackageNameSchema.parse(target));
|
|
184618
184633
|
}
|
|
184634
|
+
function isAutomationPackagePath(packagePath) {
|
|
184635
|
+
return packagePath.startsWith("agents/") || packagePath.startsWith("workflows/");
|
|
184636
|
+
}
|
|
184619
184637
|
function semverParts(version2) {
|
|
184620
184638
|
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version2);
|
|
184621
184639
|
if (!match)
|
|
@@ -184876,14 +184894,15 @@ function packageStatus(opts) {
|
|
|
184876
184894
|
const context = resolveGitSourceContext(opts);
|
|
184877
184895
|
if (context.gitRoot)
|
|
184878
184896
|
checkRepoVersion(context.gitRoot, false);
|
|
184879
|
-
const
|
|
184897
|
+
const isRepoRootStatus = context.gitRoot && context.packageRoot && context.gitRoot === context.packageRoot;
|
|
184898
|
+
const validation = context.packageRoot && !isRepoRootStatus ? validatePackage(context.packageRoot) : null;
|
|
184880
184899
|
const gitStatus = context.gitRoot ? gitOutput(["status", "--short"], context.gitRoot) ?? "" : "";
|
|
184881
184900
|
const payload = {
|
|
184882
184901
|
gitRoot: context.gitRoot,
|
|
184883
184902
|
packageRoot: context.packageRoot,
|
|
184884
184903
|
packagePath: context.packagePath,
|
|
184885
184904
|
clean: gitStatus.length === 0,
|
|
184886
|
-
valid: validation?.valid ??
|
|
184905
|
+
valid: validation?.valid ?? null,
|
|
184887
184906
|
errors: validation?.errors ?? []
|
|
184888
184907
|
};
|
|
184889
184908
|
if (opts.json)
|
|
@@ -185061,7 +185080,9 @@ async function release(versionOrBump, dir, opts) {
|
|
|
185061
185080
|
throw err;
|
|
185062
185081
|
}
|
|
185063
185082
|
success2(`Released ${tag}.`);
|
|
185064
|
-
|
|
185083
|
+
if (isAutomationPackagePath(context.packagePath)) {
|
|
185084
|
+
await syncLatestAutomation(client, context.packagePath);
|
|
185085
|
+
}
|
|
185065
185086
|
}
|
|
185066
185087
|
async function sync(target, opts) {
|
|
185067
185088
|
const context = resolveGitSourceContext({ dir: opts.dir });
|
|
@@ -185072,6 +185093,9 @@ async function sync(target, opts) {
|
|
|
185072
185093
|
if (!packagePath) {
|
|
185073
185094
|
throw new Error("Pass an automation target or run sync inside a source package.");
|
|
185074
185095
|
}
|
|
185096
|
+
if (!isAutomationPackagePath(packagePath)) {
|
|
185097
|
+
throw new Error("Sync only supports agent and workflow packages.");
|
|
185098
|
+
}
|
|
185075
185099
|
await syncLatestAutomation(new ApiClient(resolveConfig(opts)), packagePath);
|
|
185076
185100
|
}
|
|
185077
185101
|
async function install(packageRef, opts) {
|
|
@@ -188030,6 +188054,155 @@ function parseWorkflow(yaml) {
|
|
|
188030
188054
|
}
|
|
188031
188055
|
return result.data;
|
|
188032
188056
|
}
|
|
188057
|
+
// ../workflow-yaml/src/validate-quality.ts
|
|
188058
|
+
init_src();
|
|
188059
|
+
var CATEGORICAL_NAMES = new Set([
|
|
188060
|
+
"category",
|
|
188061
|
+
"subcategory",
|
|
188062
|
+
"kind",
|
|
188063
|
+
"status",
|
|
188064
|
+
"type",
|
|
188065
|
+
"subtype",
|
|
188066
|
+
"level",
|
|
188067
|
+
"severity",
|
|
188068
|
+
"priority",
|
|
188069
|
+
"state",
|
|
188070
|
+
"stage",
|
|
188071
|
+
"tier",
|
|
188072
|
+
"classification",
|
|
188073
|
+
"monitoring",
|
|
188074
|
+
"frequency",
|
|
188075
|
+
"reference",
|
|
188076
|
+
"sectionname",
|
|
188077
|
+
"code",
|
|
188078
|
+
"flag",
|
|
188079
|
+
"signal",
|
|
188080
|
+
"mode",
|
|
188081
|
+
"phase"
|
|
188082
|
+
]);
|
|
188083
|
+
var SEPARATOR_LIST_RE = /(?:^|[\s(])((?:[A-Za-z0-9_-]{1,20})(?:\s*[|,/]\s*[A-Za-z0-9_-]{1,20}){1,7})(?:$|[\s).])/;
|
|
188084
|
+
function validateSchemaQuality(workflow2) {
|
|
188085
|
+
const warnings = [];
|
|
188086
|
+
for (let i5 = 0;i5 < workflow2.steps.length; i5++) {
|
|
188087
|
+
const step = workflow2.steps[i5];
|
|
188088
|
+
if (!step)
|
|
188089
|
+
continue;
|
|
188090
|
+
const basePath = `steps.${i5}`;
|
|
188091
|
+
walkStep(step, basePath, warnings);
|
|
188092
|
+
}
|
|
188093
|
+
return warnings;
|
|
188094
|
+
}
|
|
188095
|
+
function walkStep(step, basePath, out) {
|
|
188096
|
+
if (step.type === "ai.extract") {
|
|
188097
|
+
const schema = step.with?.schema;
|
|
188098
|
+
if (schema && typeof schema === "object") {
|
|
188099
|
+
walkSchema(schema, `${basePath}.with.schema`, out);
|
|
188100
|
+
}
|
|
188101
|
+
return;
|
|
188102
|
+
}
|
|
188103
|
+
if (step.type === "transform.script") {
|
|
188104
|
+
const cfg = step.with;
|
|
188105
|
+
const source2 = typeof cfg?.function === "string" ? cfg.function : null;
|
|
188106
|
+
if (!source2)
|
|
188107
|
+
return;
|
|
188108
|
+
const paramNames = cfg?.inputs && typeof cfg.inputs === "object" && !Array.isArray(cfg.inputs) ? Object.keys(cfg.inputs) : [];
|
|
188109
|
+
const compiled = compileTypedScript({ kind: "transform", source: source2, paramNames });
|
|
188110
|
+
if (!compiled.ok)
|
|
188111
|
+
return;
|
|
188112
|
+
for (const w10 of compiled.warnings) {
|
|
188113
|
+
if (w10.code === "weak-return-type") {
|
|
188114
|
+
out.push({
|
|
188115
|
+
code: "weak-script-return",
|
|
188116
|
+
field: `${basePath}.with.function`,
|
|
188117
|
+
message: `transform.script return type is too loose: ${w10.message}`,
|
|
188118
|
+
hint: 'Replace `unknown`/`any` with a concrete shape, or use a TS literal union (e.g. `"low" | "medium" | "high"`) when the value belongs to a closed set.'
|
|
188119
|
+
});
|
|
188120
|
+
}
|
|
188121
|
+
}
|
|
188122
|
+
const schema = compiled.result.returnSchema;
|
|
188123
|
+
if (schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188124
|
+
out.push({
|
|
188125
|
+
code: "untyped-object",
|
|
188126
|
+
field: `${basePath}.with.function`,
|
|
188127
|
+
message: "transform.script returns an untyped object. Downstream steps cannot autocomplete its fields.",
|
|
188128
|
+
hint: "Describe the actual shape in the return annotation: `function script(...): { name: string; total: number }`."
|
|
188129
|
+
});
|
|
188130
|
+
}
|
|
188131
|
+
return;
|
|
188132
|
+
}
|
|
188133
|
+
const withAny = step.with;
|
|
188134
|
+
const nestedSteps = withAny?.steps;
|
|
188135
|
+
if (Array.isArray(nestedSteps)) {
|
|
188136
|
+
for (let i5 = 0;i5 < nestedSteps.length; i5++) {
|
|
188137
|
+
const s5 = nestedSteps[i5];
|
|
188138
|
+
if (s5)
|
|
188139
|
+
walkStep(s5, `${basePath}.with.steps.${i5}`, out);
|
|
188140
|
+
}
|
|
188141
|
+
}
|
|
188142
|
+
const nestedBranches = withAny?.branches;
|
|
188143
|
+
if (Array.isArray(nestedBranches)) {
|
|
188144
|
+
for (let b8 = 0;b8 < nestedBranches.length; b8++) {
|
|
188145
|
+
const branch = nestedBranches[b8];
|
|
188146
|
+
if (branch && Array.isArray(branch.steps)) {
|
|
188147
|
+
for (let i5 = 0;i5 < branch.steps.length; i5++) {
|
|
188148
|
+
const s5 = branch.steps[i5];
|
|
188149
|
+
if (s5)
|
|
188150
|
+
walkStep(s5, `${basePath}.with.branches.${b8}.steps.${i5}`, out);
|
|
188151
|
+
}
|
|
188152
|
+
}
|
|
188153
|
+
}
|
|
188154
|
+
}
|
|
188155
|
+
}
|
|
188156
|
+
function walkSchema(schema, basePath, out) {
|
|
188157
|
+
const type = schema.type;
|
|
188158
|
+
if (type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188159
|
+
out.push({
|
|
188160
|
+
code: "untyped-object",
|
|
188161
|
+
field: basePath,
|
|
188162
|
+
message: "Object schema has no properties. The LLM has no guidance on what fields to emit and downstream autocomplete is empty.",
|
|
188163
|
+
hint: "Add concrete fields under `properties`, or set `additionalProperties: false` if the object is intentionally open."
|
|
188164
|
+
});
|
|
188165
|
+
}
|
|
188166
|
+
if (type === "array" && (schema.items === undefined || isEmptyObject(schema.items))) {
|
|
188167
|
+
out.push({
|
|
188168
|
+
code: "untyped-array-items",
|
|
188169
|
+
field: basePath,
|
|
188170
|
+
message: "Array items are untyped. The LLM does not know what shape each element should take.",
|
|
188171
|
+
hint: "Add an `items:` block describing the element type, e.g. `items: { type: object, properties: { ... } }`."
|
|
188172
|
+
});
|
|
188173
|
+
}
|
|
188174
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
188175
|
+
for (const [name, raw] of Object.entries(schema.properties)) {
|
|
188176
|
+
if (!raw || typeof raw !== "object")
|
|
188177
|
+
continue;
|
|
188178
|
+
const prop = raw;
|
|
188179
|
+
const propType = prop.type;
|
|
188180
|
+
const propEnum = Array.isArray(prop.enum) && prop.enum.length > 0;
|
|
188181
|
+
const lower = name.toLowerCase();
|
|
188182
|
+
const description = typeof prop.description === "string" ? prop.description : "";
|
|
188183
|
+
const looksCategoricalByName = CATEGORICAL_NAMES.has(lower);
|
|
188184
|
+
const looksCategoricalByDescription = description.length > 0 && SEPARATOR_LIST_RE.test(description);
|
|
188185
|
+
if (propType === "string" && !propEnum && (looksCategoricalByName || looksCategoricalByDescription) && !looksLikeFreeform(prop)) {
|
|
188186
|
+
out.push({
|
|
188187
|
+
code: "categorical-missing-enum",
|
|
188188
|
+
field: `${basePath}.properties.${name}`,
|
|
188189
|
+
message: `Field \`${name}\` looks categorical but has no \`enum\`. Coded values extract more reliably when the LLM is constrained to a closed set.`,
|
|
188190
|
+
hint: "In the workflow builder, switch the field type to `Enum` and list the allowed values. In raw YAML, add `enum: ['value1', 'value2', ...]` to the field, and write the per-value meanings into the field's `description`."
|
|
188191
|
+
});
|
|
188192
|
+
}
|
|
188193
|
+
walkSchema(prop, `${basePath}.properties.${name}`, out);
|
|
188194
|
+
}
|
|
188195
|
+
}
|
|
188196
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
188197
|
+
walkSchema(schema.items, `${basePath}.items`, out);
|
|
188198
|
+
}
|
|
188199
|
+
}
|
|
188200
|
+
function isEmptyObject(v11) {
|
|
188201
|
+
return typeof v11 === "object" && v11 !== null && !Array.isArray(v11) && Object.keys(v11).length === 0;
|
|
188202
|
+
}
|
|
188203
|
+
function looksLikeFreeform(prop) {
|
|
188204
|
+
return prop.pattern !== undefined || prop.format !== undefined;
|
|
188205
|
+
}
|
|
188033
188206
|
// src/commands/validate.ts
|
|
188034
188207
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
188035
188208
|
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
@@ -188048,24 +188221,46 @@ function isDirSafe(path15, issues, field) {
|
|
|
188048
188221
|
}
|
|
188049
188222
|
function validateWorkflowYaml(path15) {
|
|
188050
188223
|
if (!existsSync10(path15)) {
|
|
188051
|
-
return [{ field: path15, message: "File not found." }];
|
|
188224
|
+
return { issues: [{ field: path15, message: "File not found." }], warnings: [] };
|
|
188052
188225
|
}
|
|
188053
188226
|
const text = readFileSync10(path15, "utf-8");
|
|
188054
188227
|
try {
|
|
188055
|
-
parseWorkflow(text);
|
|
188056
|
-
|
|
188228
|
+
const definition = parseWorkflow(text);
|
|
188229
|
+
let warnings = [];
|
|
188230
|
+
try {
|
|
188231
|
+
warnings = validateSchemaQuality(definition);
|
|
188232
|
+
} catch {
|
|
188233
|
+
warnings = [];
|
|
188234
|
+
}
|
|
188235
|
+
return { issues: [], warnings };
|
|
188057
188236
|
} catch (err2) {
|
|
188058
188237
|
if (err2 instanceof YamlParseError) {
|
|
188059
188238
|
const where = err2.line !== undefined ? ` (line ${err2.line})` : "";
|
|
188060
|
-
return [{ field: "yaml", message: `${err2.message}${where}` }];
|
|
188239
|
+
return { issues: [{ field: "yaml", message: `${err2.message}${where}` }], warnings: [] };
|
|
188061
188240
|
}
|
|
188062
188241
|
if (err2 instanceof WorkflowValidationError) {
|
|
188063
|
-
return
|
|
188064
|
-
|
|
188065
|
-
|
|
188066
|
-
|
|
188242
|
+
return {
|
|
188243
|
+
issues: err2.errors.map((e8) => ({
|
|
188244
|
+
field: e8.path.length > 0 ? e8.path.map(String).join(".") : "(root)",
|
|
188245
|
+
message: e8.message
|
|
188246
|
+
})),
|
|
188247
|
+
warnings: []
|
|
188248
|
+
};
|
|
188067
188249
|
}
|
|
188068
|
-
return
|
|
188250
|
+
return {
|
|
188251
|
+
issues: [{ field: "(root)", message: err2 instanceof Error ? err2.message : String(err2) }],
|
|
188252
|
+
warnings: []
|
|
188253
|
+
};
|
|
188254
|
+
}
|
|
188255
|
+
}
|
|
188256
|
+
function printSchemaQualityWarnings(path15, warnings) {
|
|
188257
|
+
if (warnings.length === 0)
|
|
188258
|
+
return;
|
|
188259
|
+
warn(`workflow.yaml ${ui.dim(`(${path15})`)}: ${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"}`);
|
|
188260
|
+
for (const w10 of warnings) {
|
|
188261
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
188262
|
+
if (w10.hint)
|
|
188263
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
188069
188264
|
}
|
|
188070
188265
|
}
|
|
188071
188266
|
function validateEvaluatorsYaml(path15) {
|
|
@@ -188248,9 +188443,23 @@ function rootDir(opts) {
|
|
|
188248
188443
|
return resolve6(opts.dir ?? process.cwd());
|
|
188249
188444
|
}
|
|
188250
188445
|
function registerAllValidateCommand(parent) {
|
|
188251
|
-
parent.command("validate").description("Local-only validation
|
|
188446
|
+
parent.command("validate [path]").description("Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`.").option("--dir <path>", "Project root (defaults to cwd; resolves the three default paths from here)").action((path15, opts) => {
|
|
188447
|
+
if (path15) {
|
|
188448
|
+
const target = resolve6(path15);
|
|
188449
|
+
const wfResult2 = validateWorkflowYaml(target);
|
|
188450
|
+
const wfOk2 = printIssues("workflow.yaml", target, wfResult2.issues);
|
|
188451
|
+
if (wfOk2)
|
|
188452
|
+
printSchemaQualityWarnings(target, wfResult2.warnings);
|
|
188453
|
+
if (!wfOk2)
|
|
188454
|
+
process.exit(1);
|
|
188455
|
+
return;
|
|
188456
|
+
}
|
|
188252
188457
|
const root2 = rootDir(opts);
|
|
188253
|
-
const
|
|
188458
|
+
const wfResult = validateWorkflowYaml(join12(root2, "workflow.yaml"));
|
|
188459
|
+
const wfPath = relPath(root2, join12(root2, "workflow.yaml"));
|
|
188460
|
+
const wfOk = printIssues("workflow.yaml", wfPath, wfResult.issues);
|
|
188461
|
+
if (wfOk)
|
|
188462
|
+
printSchemaQualityWarnings(wfPath, wfResult.warnings);
|
|
188254
188463
|
const evOk = printIssues("evaluators.yaml", relPath(root2, join12(root2, "evaluators.yaml")), validateEvaluatorsYaml(join12(root2, "evaluators.yaml")));
|
|
188255
188464
|
const dsOk = printIssues("dataset/", relPath(root2, join12(root2, "dataset")), validateDatasetFolder(join12(root2, "dataset")));
|
|
188256
188465
|
if (!wfOk || !evOk || !dsOk)
|
|
@@ -188903,6 +189112,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188903
189112
|
return;
|
|
188904
189113
|
}
|
|
188905
189114
|
success2(`Pushed ${ui.bold(workflowId)} (v${result.version ?? nextVersion})`);
|
|
189115
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188906
189116
|
} else {
|
|
188907
189117
|
if (opts.bump) {
|
|
188908
189118
|
throw new Error("--bump requires --workflow-id (need a current version to bump from). For a new workflow, use --set-version <X.Y.Z> or omit both.");
|
|
@@ -188920,6 +189130,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188920
189130
|
const id2 = result.workflow?.id ?? result.id ?? "?";
|
|
188921
189131
|
const ver = result.version?.version ?? result.workflow?.currentVersion?.version ?? result.currentVersion?.version ?? "?";
|
|
188922
189132
|
success2(`Created ${ui.bold(id2)} (v${ver})`);
|
|
189133
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188923
189134
|
}
|
|
188924
189135
|
}));
|
|
188925
189136
|
const moveCmd = parent.command("move <workflow-id>").description("Move a workflow to a folder path, creating folders as needed").requiredOption("--folder <path>", "Target folder path (`/` for root)");
|
|
@@ -188937,6 +189148,16 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188937
189148
|
}));
|
|
188938
189149
|
}
|
|
188939
189150
|
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
189151
|
+
function printSchemaQualityWarnings2(warnings) {
|
|
189152
|
+
if (!warnings || warnings.length === 0)
|
|
189153
|
+
return;
|
|
189154
|
+
warn(`${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"} (push succeeded; sharper types make downstream steps easier to wire):`);
|
|
189155
|
+
for (const w10 of warnings) {
|
|
189156
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
189157
|
+
if (w10.hint)
|
|
189158
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
189159
|
+
}
|
|
189160
|
+
}
|
|
188940
189161
|
function isBumpLevel(value) {
|
|
188941
189162
|
return value === "patch" || value === "minor" || value === "major";
|
|
188942
189163
|
}
|
|
@@ -20,7 +20,7 @@ Manage workflows: push, pull, run, evaluate.
|
|
|
20
20
|
- [`eigenpal workflow pull [options] <workflow-id>`](#eigenpal-workflow-pull-options-workflow-id)
|
|
21
21
|
- [`eigenpal workflow push [options]`](#eigenpal-workflow-push-options)
|
|
22
22
|
- [`eigenpal workflow move [options] <workflow-id>`](#eigenpal-workflow-move-options-workflow-id)
|
|
23
|
-
- [`eigenpal workflow validate [options]`](#eigenpal-workflow-validate-options)
|
|
23
|
+
- [`eigenpal workflow validate [options] [path]`](#eigenpal-workflow-validate-options-path)
|
|
24
24
|
- [`eigenpal workflow clear-local [options] [examples...]`](#eigenpal-workflow-clear-local-options-examples)
|
|
25
25
|
- [`eigenpal workflow evaluators pull [options] <workflow-id>`](#eigenpal-workflow-evaluators-pull-options-workflow-id)
|
|
26
26
|
- [`eigenpal workflow evaluators push [options] <workflow-id>`](#eigenpal-workflow-evaluators-push-options-workflow-id)
|
|
@@ -100,7 +100,7 @@ workflow
|
|
|
100
100
|
├── evaluator-type
|
|
101
101
|
│ ├── list|ls
|
|
102
102
|
│ └── get <type>
|
|
103
|
-
├── validate
|
|
103
|
+
├── validate [path]
|
|
104
104
|
├── clear-local [examples...]
|
|
105
105
|
└── step
|
|
106
106
|
└── exec <type>
|
|
@@ -110,14 +110,14 @@ workflow
|
|
|
110
110
|
|
|
111
111
|
### Core
|
|
112
112
|
|
|
113
|
-
| Command | Description
|
|
114
|
-
| ------------------------------------------------------- |
|
|
115
|
-
| `eigenpal workflow list | ls [options]`
|
|
116
|
-
| `eigenpal workflow pull [options] <workflow-id>` | Download the YAML definition of the workflow at its current version.
|
|
117
|
-
| `eigenpal workflow push [options]` | Create or update a workflow from a YAML file.
|
|
118
|
-
| `eigenpal workflow move [options] <workflow-id>` | Move a workflow to a folder path, creating folders as needed
|
|
119
|
-
| `eigenpal workflow validate [options]`
|
|
120
|
-
| `eigenpal workflow clear-local [options] [examples...]` | Delete local execution artifacts under ./dataset/examples/. Keeps the latest run per example by default.
|
|
113
|
+
| Command | Description |
|
|
114
|
+
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
115
|
+
| `eigenpal workflow list | ls [options]` | List workflows the caller can read. |
|
|
116
|
+
| `eigenpal workflow pull [options] <workflow-id>` | Download the YAML definition of the workflow at its current version. |
|
|
117
|
+
| `eigenpal workflow push [options]` | Create or update a workflow from a YAML file. |
|
|
118
|
+
| `eigenpal workflow move [options] <workflow-id>` | Move a workflow to a folder path, creating folders as needed |
|
|
119
|
+
| `eigenpal workflow validate [options] [path]` | Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`. |
|
|
120
|
+
| `eigenpal workflow clear-local [options] [examples...]` | Delete local execution artifacts under ./dataset/examples/. Keeps the latest run per example by default. |
|
|
121
121
|
|
|
122
122
|
### Evaluators
|
|
123
123
|
|
|
@@ -256,9 +256,15 @@ Move a workflow to a folder path, creating folders as needed
|
|
|
256
256
|
| `--base-url <url>` | no | | Server base URL |
|
|
257
257
|
| `--json` | no | | Output the raw server response as JSON |
|
|
258
258
|
|
|
259
|
-
### `eigenpal workflow validate [options]`
|
|
259
|
+
### `eigenpal workflow validate [options] [path]`
|
|
260
260
|
|
|
261
|
-
Local-only validation
|
|
261
|
+
Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`.
|
|
262
|
+
|
|
263
|
+
### Arguments
|
|
264
|
+
|
|
265
|
+
| Name | Required | Variadic | Description |
|
|
266
|
+
| ------ | -------- | -------- | ----------- |
|
|
267
|
+
| `path` | no | no | |
|
|
262
268
|
|
|
263
269
|
### Options
|
|
264
270
|
|
|
@@ -191,6 +191,76 @@ The script runs in a WASM sandbox: 5s wall clock, 10 MB heap, no network,
|
|
|
191
191
|
no filesystem. Trips show up as `script_timeout` / `script_memory_limit`
|
|
192
192
|
on the step execution.
|
|
193
193
|
|
|
194
|
+
## Be specific with types
|
|
195
|
+
|
|
196
|
+
Every output schema you write is read three times: by the LLM (for
|
|
197
|
+
`ai.extract`), by downstream steps (autocomplete + template resolution),
|
|
198
|
+
and by the runtime validator. **The more specific the type, the better
|
|
199
|
+
the workflow runs.**
|
|
200
|
+
|
|
201
|
+
### Use `enum` for coded values
|
|
202
|
+
|
|
203
|
+
Categorical fields like `category`, `status`, `kind`, `severity`,
|
|
204
|
+
`monitoring`, `frequency` should be enums whenever the value set is
|
|
205
|
+
closed. The LLM is constrained to emit only allowed values, and
|
|
206
|
+
downstream `transform.script` steps know exactly what they'll see.
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- name: classify
|
|
210
|
+
type: ai.extract
|
|
211
|
+
with:
|
|
212
|
+
input: '{{ input.text }}'
|
|
213
|
+
schema:
|
|
214
|
+
type: object
|
|
215
|
+
properties:
|
|
216
|
+
category:
|
|
217
|
+
type: string
|
|
218
|
+
enum: ['901', '902']
|
|
219
|
+
description: |
|
|
220
|
+
Covenant category.
|
|
221
|
+
901 = Odkladacie podmienky (conditions precedent).
|
|
222
|
+
902 = Následné podmienky (subsequent / ongoing covenants).
|
|
223
|
+
required: [category]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Per-value meanings go inline in the field's `description`. The LLM picks
|
|
227
|
+
them up reliably from there, and the scope browser renders the allowed
|
|
228
|
+
values when downstream steps reference the field.
|
|
229
|
+
|
|
230
|
+
### `transform.script` — TS literal unions become JSON Schema enums
|
|
231
|
+
|
|
232
|
+
A TS literal-union return type compiles to a JSON-Schema enum
|
|
233
|
+
automatically. Use it when the function returns a value from a closed set:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
- name: severity
|
|
237
|
+
type: transform.script
|
|
238
|
+
with:
|
|
239
|
+
inputs:
|
|
240
|
+
score: '{{ steps.score.output.value }}'
|
|
241
|
+
function: |
|
|
242
|
+
function script(score: number): { level: 'low' | 'medium' | 'high' } {
|
|
243
|
+
return { level: score > 0.8 ? 'high' : score > 0.4 ? 'medium' : 'low' };
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Downstream steps autocomplete `low | medium | high` for `level`.
|
|
248
|
+
|
|
249
|
+
### `eigenpal workflow push` warns on weak types
|
|
250
|
+
|
|
251
|
+
Push surfaces non-fatal **schema-quality warnings** when it sees patterns
|
|
252
|
+
that hurt downstream typing. Common ones:
|
|
253
|
+
|
|
254
|
+
| Warning code | What it means |
|
|
255
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `categorical-missing-enum` | A field named like `category` / `status` / `kind` is a plain `string`. Consider declaring an `enum`. |
|
|
257
|
+
| `weak-script-return` | A `transform.script` returns `any` or `unknown`. Replace with a concrete shape or a literal union. |
|
|
258
|
+
| `untyped-object` | A `type: object` field has no `properties`. The LLM has no guidance on what to emit. |
|
|
259
|
+
| `untyped-array-items` | A `type: array` field has no `items:` declared. |
|
|
260
|
+
|
|
261
|
+
Warnings do not block the push. They print to stderr as `! …` lines. Run
|
|
262
|
+
`eigenpal workflow validate` locally to surface them before pushing.
|
|
263
|
+
|
|
194
264
|
## Common patterns
|
|
195
265
|
|
|
196
266
|
### Conditional branches
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eigenpal/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eigenpal",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "bun src/cli.ts",
|
|
42
42
|
"build": "bun run generate:skill && bun build src/cli.ts --outdir dist --target node --format esm && rm -rf dist/templates dist/skill && cp -R src/templates dist/templates && cp -R src/skill dist/skill",
|
|
43
|
+
"generate": "bun run generate:cli-docs && bun run generate:skill",
|
|
44
|
+
"generate:check": "bun run generate:cli-docs:check && bun run generate:skill:check",
|
|
43
45
|
"generate:skill": "bun scripts/generate-skill-reference.ts",
|
|
44
46
|
"generate:skill:check": "bun scripts/generate-skill-reference.ts --check",
|
|
45
47
|
"generate:cli-docs": "bun scripts/generate-cli-docs.ts",
|