@eigenpal/cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cli.js +314 -232
- package/dist/skill/reference/cli/agents.md +1 -1
- package/dist/skill/reference/cli/rerun.md +1 -1
- package/dist/skill/reference/cli/run.md +1 -1
- package/dist/skill/reference/cli/runs.md +2 -2
- package/dist/skill/reference/dataset-format.md +73 -240
- package/dist/skill/reference/evaluators.md +3 -3
- package/dist/skill/reference/step-types.md +2 -2
- package/dist/templates/blank/README.md +3 -2
- package/dist/templates/blank/evaluators.yaml +1 -1
- package/package.json +1 -1
- /package/dist/templates/blank/dataset/examples/sample/{expected/output.json → expected.json} +0 -0
- /package/dist/templates/blank/dataset/examples/sample/{input/arguments.json → input.json} +0 -0
- /package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/{expected/output.json → expected.json} +0 -0
- /package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/{input/arguments.json → input.json} +0 -0
- /package/dist/templates/text-classification/dataset/examples/negative/{expected/output.json → expected.json} +0 -0
- /package/dist/templates/text-classification/dataset/examples/negative/{input/arguments.json → input.json} +0 -0
- /package/dist/templates/text-classification/dataset/examples/positive/{expected/output.json → expected.json} +0 -0
- /package/dist/templates/text-classification/dataset/examples/positive/{input/arguments.json → input.json} +0 -0
package/dist/cli.js
CHANGED
|
@@ -17389,7 +17389,7 @@ var init_run_expand = __esm(() => {
|
|
|
17389
17389
|
observability: "use `expand=debug`",
|
|
17390
17390
|
feedback: "use `expand=execution` — feedback lives at `execution.feedback` (agent runs)",
|
|
17391
17391
|
steps: "use `expand=execution` — steps live at `execution.steps` (workflow runs)",
|
|
17392
|
-
definition: "use `
|
|
17392
|
+
definition: "use `expand=execution` — definitionSnapshot lives at `execution.definitionSnapshot` (workflow runs)",
|
|
17393
17393
|
files: "downloadable output refs live at top-level `files` on completed detail responses; `expand=execution` adds `execution.files.output` metadata for agent runs",
|
|
17394
17394
|
expected: "use `expand=execution` — expected output lives at `execution.expected`",
|
|
17395
17395
|
trace: "trace id: `expand=debug` (`debug.traceId`); trace files: `GET /api/v1/runs/{id}/artifacts`",
|
|
@@ -62369,7 +62369,7 @@ var init_step_configs = __esm(() => {
|
|
|
62369
62369
|
type: "transform.template",
|
|
62370
62370
|
category: "transform",
|
|
62371
62371
|
name: "Fill Template",
|
|
62372
|
-
description: "Fill a DOCX template with data.
|
|
62372
|
+
description: "Fill a DOCX template with data from previous steps. Select a template in the workflow builder or provide a template ID from your workspace.",
|
|
62373
62373
|
configSchema: TransformTemplateConfigSchema,
|
|
62374
62374
|
outputSchema: TransformTemplateOutputSchema,
|
|
62375
62375
|
configInWith: true
|
|
@@ -62767,7 +62767,7 @@ var init_expected_error = __esm(() => {
|
|
|
62767
62767
|
messageContains: exports_external.string().min(1).max(1000).optional(),
|
|
62768
62768
|
step: exports_external.string().min(1).max(200).optional()
|
|
62769
62769
|
}).refine((v) => v.code != null || v.messageContains != null || v.step != null, {
|
|
62770
|
-
message: "expected
|
|
62770
|
+
message: "expected.json $error must specify at least one of: code, messageContains, step (otherwise any failure would match)"
|
|
62771
62771
|
});
|
|
62772
62772
|
});
|
|
62773
62773
|
|
|
@@ -63266,7 +63266,7 @@ function formatZodError(source) {
|
|
|
63266
63266
|
severity: "error"
|
|
63267
63267
|
}));
|
|
63268
63268
|
}
|
|
63269
|
-
var DATASET_NAME_PATTERN, DatasetNameSchema, DatasetMetaSchema, DatasetImportModeSchema, DatasetImportRequestSchema, EvaluatorsImportRequestSchema, EvalResultsExportFormatSchema,
|
|
63269
|
+
var DATASET_NAME_PATTERN, DatasetNameSchema, DatasetMetaSchema, DatasetImportModeSchema, DatasetImportRequestSchema, EvaluatorsImportRequestSchema, EvalResultsExportFormatSchema, ExperimentExportQuerySchema, EvalResultsExportQuerySchema, EvalResultExportRowSchema, EvalResultsExportJsonSchema, ERROR_HINTS;
|
|
63270
63270
|
var init_dataset_archive = __esm(() => {
|
|
63271
63271
|
init_zod();
|
|
63272
63272
|
DATASET_NAME_PATTERN = /^[a-z0-9][a-z0-9-_]*$/;
|
|
@@ -63284,14 +63284,10 @@ var init_dataset_archive = __esm(() => {
|
|
|
63284
63284
|
yaml: exports_external.string().min(1, "yaml content is required").max(1048576, "evaluator YAML exceeds 1 MB")
|
|
63285
63285
|
});
|
|
63286
63286
|
EvalResultsExportFormatSchema = exports_external.enum(["csv", "json"]);
|
|
63287
|
-
|
|
63288
|
-
format: EvalResultsExportFormatSchema
|
|
63289
|
-
batchId: exports_external.string().min(1).optional()
|
|
63290
|
-
});
|
|
63291
|
-
EvalResultsExportByBatchQuerySchema = exports_external.object({
|
|
63292
|
-
format: EvalResultsExportFormatSchema,
|
|
63293
|
-
batchId: exports_external.string().min(1)
|
|
63287
|
+
ExperimentExportQuerySchema = exports_external.object({
|
|
63288
|
+
format: EvalResultsExportFormatSchema
|
|
63294
63289
|
});
|
|
63290
|
+
EvalResultsExportQuerySchema = ExperimentExportQuerySchema;
|
|
63295
63291
|
EvalResultExportRowSchema = exports_external.object({
|
|
63296
63292
|
executionId: exports_external.string().min(1),
|
|
63297
63293
|
exampleId: exports_external.string().min(1).nullable(),
|
|
@@ -63315,7 +63311,7 @@ var init_dataset_archive = __esm(() => {
|
|
|
63315
63311
|
"dataset-too-large": "Datasets are capped at 500 MB. Split into multiple Append imports.",
|
|
63316
63312
|
"execution-in-flight": "Wait for the in-flight execution to complete, or cancel it from the Experiments tab.",
|
|
63317
63313
|
"download-token-expired": "Re-request the resource to mint a new URL.",
|
|
63318
|
-
"legacy-dataset-format": "Re-export the dataset using the
|
|
63314
|
+
"legacy-dataset-format": "Re-export the dataset using the canonical automation convention (examples/<name>/input.json + input/<file> refs + expected.json)."
|
|
63319
63315
|
};
|
|
63320
63316
|
});
|
|
63321
63317
|
|
|
@@ -65041,6 +65037,7 @@ __export(exports_src, {
|
|
|
65041
65037
|
ExtractOutputSchema: () => ExtractOutputSchema,
|
|
65042
65038
|
ExtractInputSchema: () => ExtractInputSchema,
|
|
65043
65039
|
ExtractConfigSchema: () => ExtractConfigSchema,
|
|
65040
|
+
ExperimentExportQuerySchema: () => ExperimentExportQuerySchema,
|
|
65044
65041
|
ExpectedOutputSchema: () => ExpectedOutputSchema,
|
|
65045
65042
|
ExpectedOutputFilesSchema: () => ExpectedOutputFilesSchema,
|
|
65046
65043
|
ExpectedErrorSchema: () => ExpectedErrorSchema,
|
|
@@ -65062,7 +65059,6 @@ __export(exports_src, {
|
|
|
65062
65059
|
EvalResultsExportQuerySchema: () => EvalResultsExportQuerySchema,
|
|
65063
65060
|
EvalResultsExportJsonSchema: () => EvalResultsExportJsonSchema,
|
|
65064
65061
|
EvalResultsExportFormatSchema: () => EvalResultsExportFormatSchema,
|
|
65065
|
-
EvalResultsExportByBatchQuerySchema: () => EvalResultsExportByBatchQuerySchema,
|
|
65066
65062
|
EvalResultExportRowSchema: () => EvalResultExportRowSchema,
|
|
65067
65063
|
EvalOverridesSchema: () => EvalOverridesSchema,
|
|
65068
65064
|
EvalExampleMetaSchema: () => EvalExampleMetaSchema,
|
|
@@ -186797,7 +186793,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
|
|
|
186797
186793
|
// package.json
|
|
186798
186794
|
var package_default = {
|
|
186799
186795
|
name: "@eigenpal/cli",
|
|
186800
|
-
version: "0.
|
|
186796
|
+
version: "0.9.0",
|
|
186801
186797
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
186802
186798
|
keywords: [
|
|
186803
186799
|
"eigenpal",
|
|
@@ -188897,6 +188893,52 @@ function resolveAgentPackagePath(target) {
|
|
|
188897
188893
|
return resolvePackagePath(target);
|
|
188898
188894
|
return SourcePackagePathSchema.parse(`agents/${target}`);
|
|
188899
188895
|
}
|
|
188896
|
+
function publicAgentAutomationTarget(target) {
|
|
188897
|
+
if (target.includes("/")) {
|
|
188898
|
+
return pathToDottedPackageName(SourcePackagePathSchema.parse(target));
|
|
188899
|
+
}
|
|
188900
|
+
if (target.startsWith("agents."))
|
|
188901
|
+
return target;
|
|
188902
|
+
if (/^[a-z]+_[a-zA-Z0-9]/.test(target) || target.includes(" ") || target.includes(".")) {
|
|
188903
|
+
return target;
|
|
188904
|
+
}
|
|
188905
|
+
return `agents.${target}`;
|
|
188906
|
+
}
|
|
188907
|
+
function formatTriggerState(triggers2) {
|
|
188908
|
+
if (!triggers2)
|
|
188909
|
+
return "";
|
|
188910
|
+
return ["api", "email", "manual", "cron"].filter((key) => triggers2[key]).join(",");
|
|
188911
|
+
}
|
|
188912
|
+
async function findAgentAutomationBySearch(client, target) {
|
|
188913
|
+
const payload = await client.get("/api/v1/automations", {
|
|
188914
|
+
type: "agent",
|
|
188915
|
+
search: target,
|
|
188916
|
+
limit: "100"
|
|
188917
|
+
});
|
|
188918
|
+
const candidates = payload.data ?? [];
|
|
188919
|
+
return candidates.find((automation) => automation.id === target || automation.slug === target || automation.name === target) ?? (candidates.length === 1 ? candidates[0] : null);
|
|
188920
|
+
}
|
|
188921
|
+
async function getPublicAgentAutomation(client, target) {
|
|
188922
|
+
const publicTarget = publicAgentAutomationTarget(target);
|
|
188923
|
+
try {
|
|
188924
|
+
const automation = await client.get(`/api/v1/automations/${encodeURIComponent(publicTarget)}`);
|
|
188925
|
+
if (automation.type !== "agent") {
|
|
188926
|
+
throw new Error(`Automation ${target} is not an agent.`);
|
|
188927
|
+
}
|
|
188928
|
+
return automation;
|
|
188929
|
+
} catch (err) {
|
|
188930
|
+
if (!(err instanceof ApiError) || err.status !== 404)
|
|
188931
|
+
throw err;
|
|
188932
|
+
}
|
|
188933
|
+
const resolved = await findAgentAutomationBySearch(client, target);
|
|
188934
|
+
if (!resolved) {
|
|
188935
|
+
throw new Error(`Agent automation not found: ${target}`);
|
|
188936
|
+
}
|
|
188937
|
+
if (resolved.type !== "agent") {
|
|
188938
|
+
throw new Error(`Automation ${target} is not an agent.`);
|
|
188939
|
+
}
|
|
188940
|
+
return resolved;
|
|
188941
|
+
}
|
|
188900
188942
|
function isAutomationPackagePath(packagePath) {
|
|
188901
188943
|
return packagePath.startsWith("agents/") || packagePath.startsWith("workflows/");
|
|
188902
188944
|
}
|
|
@@ -188934,7 +188976,7 @@ function bumpReleaseVersion(releases, bump) {
|
|
|
188934
188976
|
}
|
|
188935
188977
|
async function syncLatestAutomation(client, packagePath) {
|
|
188936
188978
|
const automation = pathToDottedPackageName(packagePath);
|
|
188937
|
-
await client.post(`/api/automations/${encodeURIComponent(automation)}/sync`);
|
|
188979
|
+
await client.post(`/api/v1/automations/${encodeURIComponent(automation)}/sync`);
|
|
188938
188980
|
success2(`Synced ${automation} to latest release.`);
|
|
188939
188981
|
}
|
|
188940
188982
|
async function cloneSource(opts) {
|
|
@@ -188954,52 +188996,38 @@ async function cloneSource(opts) {
|
|
|
188954
188996
|
success2(`Cloned ${repo.remoteUrl}`);
|
|
188955
188997
|
}
|
|
188956
188998
|
async function show(target, opts) {
|
|
188957
|
-
const packagePath = resolveAgentPackagePath(target);
|
|
188958
|
-
const slug = packagePath.replace(/^agents\//, "");
|
|
188959
188999
|
const client = new ApiClient(resolveConfig(opts));
|
|
188960
|
-
const
|
|
188961
|
-
include: "files,dataset,runs"
|
|
188962
|
-
});
|
|
189000
|
+
const automation = await getPublicAgentAutomation(client, target);
|
|
188963
189001
|
if (opts.json)
|
|
188964
|
-
console.log(JSON.stringify(
|
|
189002
|
+
console.log(JSON.stringify(automation, null, 2));
|
|
188965
189003
|
else {
|
|
188966
|
-
const
|
|
189004
|
+
const slug = automation.slug ?? target;
|
|
188967
189005
|
console.log(table([
|
|
188968
189006
|
{
|
|
188969
189007
|
type: "agent",
|
|
188970
|
-
|
|
188971
|
-
|
|
188972
|
-
|
|
188973
|
-
|
|
188974
|
-
|
|
188975
|
-
|
|
188976
|
-
|
|
189008
|
+
id: automation.id ?? "",
|
|
189009
|
+
slug,
|
|
189010
|
+
path: `agents/${slug}`,
|
|
189011
|
+
name: automation.name ?? "",
|
|
189012
|
+
status: automation.status ?? "",
|
|
189013
|
+
version: automation.version ?? "",
|
|
189014
|
+
triggers: formatTriggerState(automation.triggers),
|
|
189015
|
+
implementation: automation.implementationAvailable === false ? "missing" : "available"
|
|
188977
189016
|
}
|
|
188978
189017
|
], [
|
|
188979
189018
|
{ key: "type", header: "type" },
|
|
189019
|
+
{ key: "id", header: "id" },
|
|
188980
189020
|
{ key: "slug", header: "slug" },
|
|
188981
189021
|
{ key: "path", header: "path" },
|
|
188982
189022
|
{ key: "name", header: "name" },
|
|
188983
189023
|
{ key: "status", header: "status" },
|
|
188984
|
-
{ key: "
|
|
188985
|
-
{ key: "
|
|
188986
|
-
{ key: "
|
|
189024
|
+
{ key: "version", header: "version" },
|
|
189025
|
+
{ key: "triggers", header: "triggers" },
|
|
189026
|
+
{ key: "implementation", header: "implementation" }
|
|
188987
189027
|
]));
|
|
188988
|
-
if (
|
|
189028
|
+
if (automation.description)
|
|
188989
189029
|
console.log(`
|
|
188990
|
-
description: ${
|
|
188991
|
-
const runs = agent.recentRuns ?? agent.runs ?? [];
|
|
188992
|
-
console.log(`
|
|
188993
|
-
recent runs`);
|
|
188994
|
-
console.log(table(runs.slice(0, 5), [
|
|
188995
|
-
{ key: "id", header: "id" },
|
|
188996
|
-
{ key: "createdAt", header: "time", format: (value) => formatTimestamp(value) },
|
|
188997
|
-
{ key: "durationMs", header: "duration", format: (value) => formatDuration(Number(value)) },
|
|
188998
|
-
{ key: "triggeredBy", header: "triggered-by" },
|
|
188999
|
-
{ key: "requestedVersion", header: "requested" },
|
|
189000
|
-
{ key: "resolvedVersion", header: "resolved" },
|
|
189001
|
-
{ key: "status", header: "status" }
|
|
189002
|
-
]));
|
|
189030
|
+
description: ${automation.description}`);
|
|
189003
189031
|
}
|
|
189004
189032
|
}
|
|
189005
189033
|
async function versions2(target, opts) {
|
|
@@ -190931,40 +190959,13 @@ function renderBatchDiffHuman(diff) {
|
|
|
190931
190959
|
process.stderr.write(`${ui.dim("mean Δ:")} ${meanDeltaText} ` + `${ui.dim("examples shared:")} ${diff.summary.sharedExamples} ` + `${ui.dim("only in A:")} ${diff.summary.onlyInA.length} ` + `${ui.dim("only in B:")} ${diff.summary.onlyInB.length}
|
|
190932
190960
|
`);
|
|
190933
190961
|
}
|
|
190934
|
-
async function fetchEvalResults(client,
|
|
190935
|
-
const
|
|
190936
|
-
const
|
|
190937
|
-
|
|
190938
|
-
|
|
190939
|
-
for (const result of payload.results ?? []) {
|
|
190940
|
-
results.push({
|
|
190941
|
-
exampleId: run.exampleId,
|
|
190942
|
-
exampleName: run.exampleName,
|
|
190943
|
-
evaluatorName: String(result.evaluatorName ?? ""),
|
|
190944
|
-
score: typeof result.score === "number" ? result.score : null
|
|
190945
|
-
});
|
|
190946
|
-
}
|
|
190962
|
+
async function fetchEvalResults(client, experimentId) {
|
|
190963
|
+
const ref = await client.get(`/api/v1/experiments/${encodeURIComponent(experimentId)}`);
|
|
190964
|
+
const raw = await client.get(`/api/v1/automations/${encodeURIComponent(ref.automationId)}/experiments/${encodeURIComponent(experimentId)}/export`, { format: "json" });
|
|
190965
|
+
if (!raw || !Array.isArray(raw.results)) {
|
|
190966
|
+
throw new Error(`Unexpected experiment export response for ${experimentId}: missing 'results' array`);
|
|
190947
190967
|
}
|
|
190948
|
-
return
|
|
190949
|
-
}
|
|
190950
|
-
async function listBatchRuns(client, batchId) {
|
|
190951
|
-
const out = [];
|
|
190952
|
-
let cursor;
|
|
190953
|
-
do {
|
|
190954
|
-
const query = { batchId, limit: "100" };
|
|
190955
|
-
if (cursor)
|
|
190956
|
-
query.cursor = cursor;
|
|
190957
|
-
const page = await client.get("/api/v1/runs", query);
|
|
190958
|
-
for (const run of page.runs ?? []) {
|
|
190959
|
-
out.push({
|
|
190960
|
-
id: run.id,
|
|
190961
|
-
exampleId: run.eval?.exampleId ?? null,
|
|
190962
|
-
exampleName: run.eval?.example ?? null
|
|
190963
|
-
});
|
|
190964
|
-
}
|
|
190965
|
-
cursor = page.nextCursor ?? undefined;
|
|
190966
|
-
} while (cursor);
|
|
190967
|
-
return out;
|
|
190968
|
+
return raw;
|
|
190968
190969
|
}
|
|
190969
190970
|
|
|
190970
190971
|
// src/commands/agents/experiments.ts
|
|
@@ -190974,7 +190975,7 @@ function registerExperimentCommands(agent) {
|
|
|
190974
190975
|
process.exit(2);
|
|
190975
190976
|
});
|
|
190976
190977
|
addJsonFlag(withBaseUrl(experiment.command("run <agent-id-or-slug>"))).description("Start an experiment over dataset examples.").option("--example-id <id>", "Run one dataset example").option("--wait", "Poll until the experiment reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).action(action(runExperiment));
|
|
190977
|
-
addJsonFlag(withBaseUrl(experiment.command("status <agent-id-or-slug> <batch-id>"))).description("Get experiment status.").option("--watch", "Poll until complete").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before
|
|
190978
|
+
addJsonFlag(withBaseUrl(experiment.command("status <agent-id-or-slug> <batch-id>"))).description("Get experiment status.").option("--watch", "Poll until complete").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).option("--include <parts>", "Reserved for future detailed parts").action(action(experimentStatus));
|
|
190978
190979
|
withBaseUrl(experiment.command("results <agent-id-or-slug> [batch-id]")).description("Print experiment results as JSON or CSV.").requiredOption("--format <csv|json>", "Output format", parseResultsFormat).option("--out <path>", "Write output to file").action(action(experimentResults));
|
|
190979
190980
|
addJsonFlag(withPagination(withBaseUrl(experiment.command("list <agent-id-or-slug>")), 50)).description("List experiments.").option("--batch-id <id>", "Filter to one batch id").action(action(listExperiments));
|
|
190980
190981
|
addJsonFlag(withBaseUrl(experiment.command("compare <batch-id-a> <batch-id-b>"))).description("Diff eval scores between two experiment batches.").option("--sort <abs-delta-desc|delta-asc|delta-desc|name>", "Row sort order (default: biggest movers first)", "abs-delta-desc").option("--regression-threshold <n>", "Δ below this is flagged as a regression (default 0.05)", Number.parseFloat, 0.05).action(action(compareExperiments));
|
|
@@ -191078,8 +191079,10 @@ function registerAgentFileCommands(agent) {
|
|
|
191078
191079
|
}
|
|
191079
191080
|
async function listAgentFiles(agentId, opts) {
|
|
191080
191081
|
const client = buildClient(opts);
|
|
191081
|
-
const
|
|
191082
|
-
|
|
191082
|
+
const packagePath = await resolveAgentPackagePath2(client, agentId);
|
|
191083
|
+
const payload = await client.get("/api/v1/source/tree", {
|
|
191084
|
+
packagePath,
|
|
191085
|
+
...opts.path ? { prefix: normalizeAgentFilePath(opts.path, { allowEmpty: true }) } : {}
|
|
191083
191086
|
});
|
|
191084
191087
|
if (opts.json)
|
|
191085
191088
|
return printJson(payload);
|
|
@@ -191087,9 +191090,7 @@ async function listAgentFiles(agentId, opts) {
|
|
|
191087
191090
|
}
|
|
191088
191091
|
async function getAgentFile(agentId, remotePath, opts) {
|
|
191089
191092
|
const client = buildClient(opts);
|
|
191090
|
-
const payload = await client
|
|
191091
|
-
path: remotePath
|
|
191092
|
-
});
|
|
191093
|
+
const payload = await readAgentFile(client, agentId, remotePath);
|
|
191093
191094
|
if (opts.json)
|
|
191094
191095
|
return printJson(payload);
|
|
191095
191096
|
const out = path10.resolve(opts.out ?? payload.path);
|
|
@@ -191106,11 +191107,9 @@ async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
|
191106
191107
|
const client = buildClient(opts);
|
|
191107
191108
|
const local = await fs6.readFile(localPath);
|
|
191108
191109
|
let remote = null;
|
|
191109
|
-
let serverPath = remotePath;
|
|
191110
|
+
let serverPath = normalizeAgentFilePath(remotePath);
|
|
191110
191111
|
try {
|
|
191111
|
-
const payload = await client
|
|
191112
|
-
path: remotePath
|
|
191113
|
-
});
|
|
191112
|
+
const payload = await readAgentFile(client, agentId, remotePath);
|
|
191114
191113
|
serverPath = String(payload.path ?? remotePath);
|
|
191115
191114
|
remote = Buffer.from(String(payload.contentBase64 ?? ""), "base64");
|
|
191116
191115
|
} catch (err2) {
|
|
@@ -191132,6 +191131,34 @@ async function buildAgentFileDiff(agentId, remotePath, localPath, opts) {
|
|
|
191132
191131
|
textPreview: remote && isLikelyText(local) && isLikelyText(remote) ? buildTextDiffPreview(remote.toString("utf-8"), local.toString("utf-8")) : null
|
|
191133
191132
|
};
|
|
191134
191133
|
}
|
|
191134
|
+
async function resolveAgentPackagePath2(client, agentId) {
|
|
191135
|
+
const automation = await client.get(`/api/v1/automations/${encodeURIComponent(agentAutomationId(agentId))}`);
|
|
191136
|
+
if (automation.type !== "agent" || !automation.slug) {
|
|
191137
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
191138
|
+
}
|
|
191139
|
+
return `agents/${automation.slug}`;
|
|
191140
|
+
}
|
|
191141
|
+
async function readAgentFile(client, agentId, remotePath) {
|
|
191142
|
+
const packagePath = await resolveAgentPackagePath2(client, agentId);
|
|
191143
|
+
const relativePath = normalizeAgentFilePath(remotePath);
|
|
191144
|
+
const sourcePath = `${packagePath}/${relativePath}`;
|
|
191145
|
+
const payload = await client.get("/api/v1/source/raw", {
|
|
191146
|
+
path: sourcePath
|
|
191147
|
+
});
|
|
191148
|
+
return {
|
|
191149
|
+
path: relativePath,
|
|
191150
|
+
contentBase64: Buffer.from(payload.content).toString("base64"),
|
|
191151
|
+
contentType: payload.contentType
|
|
191152
|
+
};
|
|
191153
|
+
}
|
|
191154
|
+
function normalizeAgentFilePath(filePath, options = {}) {
|
|
191155
|
+
const raw = filePath.trim();
|
|
191156
|
+
const trimmed = raw.replace(/^agent\/+/, "").replace(/^\/+|\/+$/g, "");
|
|
191157
|
+
if (!trimmed && !options.allowEmpty || raw !== filePath || trimmed.includes("\x00") || trimmed.includes("\\") || trimmed.includes("..") || trimmed !== "" && trimmed.split("/").some((part) => part === "")) {
|
|
191158
|
+
throw new Error("Invalid agent file path");
|
|
191159
|
+
}
|
|
191160
|
+
return trimmed;
|
|
191161
|
+
}
|
|
191135
191162
|
function renderAgentFileDiff(report) {
|
|
191136
191163
|
const label = `File ${report.status}`;
|
|
191137
191164
|
if (report.status === "match")
|
|
@@ -191259,7 +191286,7 @@ function levenshtein(a, b2, maxDistance) {
|
|
|
191259
191286
|
}
|
|
191260
191287
|
async function findSimilarSlug(client, slug) {
|
|
191261
191288
|
try {
|
|
191262
|
-
const res = await client.get("/api/
|
|
191289
|
+
const res = await client.get("/api/workflows", {
|
|
191263
191290
|
search: slug,
|
|
191264
191291
|
limit: "20"
|
|
191265
191292
|
});
|
|
@@ -191284,13 +191311,13 @@ function notFound(idOrSlug, suggestion) {
|
|
|
191284
191311
|
async function resolveWorkflowId(client, idOrSlug) {
|
|
191285
191312
|
if (looksLikeWorkflowId(idOrSlug)) {
|
|
191286
191313
|
try {
|
|
191287
|
-
const wf = await client.get(`/api/
|
|
191314
|
+
const wf = await client.get(`/api/workflows/${idOrSlug}`);
|
|
191288
191315
|
return wf.id;
|
|
191289
191316
|
} catch {
|
|
191290
191317
|
throw notFound(idOrSlug, null);
|
|
191291
191318
|
}
|
|
191292
191319
|
}
|
|
191293
|
-
const res = await client.get("/api/
|
|
191320
|
+
const res = await client.get("/api/workflows", { name: idOrSlug });
|
|
191294
191321
|
const match = res.data?.[0];
|
|
191295
191322
|
if (!match) {
|
|
191296
191323
|
const suggestion = await findSimilarSlug(client, idOrSlug);
|
|
@@ -191428,7 +191455,7 @@ function registerRunsCommands(program2) {
|
|
|
191428
191455
|
listRunsCmd.action(action(listRuns));
|
|
191429
191456
|
addJsonFlag(withBaseUrl(runs.command("get <run-id>"))).description("Get one run.").option("--step <name>", "For workflow runs, show only this step (or comma-separated list)").option("--expand <sections>", "Comma-separated server expand sections for GET /api/v1/runs/:id: input, usage, execution, debug. Use execution for status/schemaValid; completed runs include top-level output/files/error.").option("--include <parts>", "Comma-separated local workflow step projection fields (error, duration, inputRef, …). Legacy expand names are mapped when possible; use --expand for server sections.", "feedback").action(action(getRun));
|
|
191430
191457
|
addJsonFlag(withBaseUrl(runs.command("compare <reference-run-id> <run-id>"))).description("Compare one run against another run. PDF/DOCX text comparison uses pdftotext/python3 and reports byte fallbacks.").option("--baseline", "Compare actual outputs from both runs instead of expected artifacts").option("--step <name>", "For workflow runs, restrict comparison to one step").option("--out <dir>", "Write comparison artifacts to this directory").option("--normalize-dates", "Normalize YYYYMMDD and YYYY-MM-DD tokens in filenames/text").option("--fail-on-diff", "Exit 1 when comparison status is fail").action(action(compareRun));
|
|
191431
|
-
addJsonFlag(withBaseUrl(runs.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--version <version>", "Version/source ref for the new run: latest (default), original, or an explicit ref (same grammar as eigenpal run)").option("--source-ref <ref>", "Alias for --version").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before
|
|
191458
|
+
addJsonFlag(withBaseUrl(runs.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--version <version>", "Version/source ref for the new run: latest (default), original, or an explicit ref (same grammar as eigenpal run)").option("--source-ref <ref>", "Alias for --version").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(rerunRun));
|
|
191432
191459
|
addJsonFlag(withBaseUrl(runs.command("promote <run-id>"))).description("Promote a run into a dataset example on the run's automation.").option("--name <name>", "Name for the created example").addHelpText("after", `
|
|
191433
191460
|
Examples
|
|
191434
191461
|
$ eigenpal runs promote run_abc123 --name golden-invoice
|
|
@@ -191446,7 +191473,7 @@ Use \`eigenpal runs feedback update\` first when you need to correct expected ou
|
|
|
191446
191473
|
withBaseUrl(runs.command("trace <run-id>")).description("Print raw trace.jsonl for a run, or write it with --out.").option("--out <file>", "Output file path").action(action(traceRun));
|
|
191447
191474
|
registerRunFeedbackCommands(runs);
|
|
191448
191475
|
registerRunExpectedCommands(runs);
|
|
191449
|
-
addJsonFlag(withBaseUrl(runs.command("watch <run-id>"))).description("Watch a run until it reaches a terminal status.").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before
|
|
191476
|
+
addJsonFlag(withBaseUrl(runs.command("watch <run-id>"))).description("Watch a run until it reaches a terminal status.").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(watchRunCommand));
|
|
191450
191477
|
addJsonFlag(withBaseUrl(runs.command("cancel <run-id>"))).description("Cancel a run.").option("--yes", "Required in non-interactive environments").action(action(cancelRun));
|
|
191451
191478
|
}
|
|
191452
191479
|
function registerRunFeedbackCommands(runs) {
|
|
@@ -192251,12 +192278,12 @@ async function uploadRunExpected(executionId, file2, opts) {
|
|
|
192251
192278
|
form.append("file", new Blob([data]), path11.basename(file2));
|
|
192252
192279
|
if (opts.name)
|
|
192253
192280
|
form.append("name", opts.name);
|
|
192254
|
-
const payload = await client.postFormData(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`, form);
|
|
192281
|
+
const payload = await client.postFormData(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback/expected`, form);
|
|
192255
192282
|
renderGeneric(payload, opts, `Uploaded expected file for ${executionId}`);
|
|
192256
192283
|
}
|
|
192257
192284
|
async function copyOutputToExpected(executionId, outputFile, opts) {
|
|
192258
192285
|
const client = buildClient(opts);
|
|
192259
|
-
const payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/expected`, {
|
|
192286
|
+
const payload = await client.post(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback/expected`, {
|
|
192260
192287
|
outputFileName: outputFile,
|
|
192261
192288
|
...opts.name ? { expectedName: opts.name } : {}
|
|
192262
192289
|
});
|
|
@@ -192264,7 +192291,7 @@ async function copyOutputToExpected(executionId, outputFile, opts) {
|
|
|
192264
192291
|
}
|
|
192265
192292
|
async function renameRunExpected(executionId, oldName, newName, opts) {
|
|
192266
192293
|
const client = buildClient(opts);
|
|
192267
|
-
const payload = await client.patch(`/api/v1/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(oldName)}`, { name: newName });
|
|
192294
|
+
const payload = await client.patch(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback/expected/${encodeURIComponent(oldName)}`, { name: newName });
|
|
192268
192295
|
renderGeneric(payload, opts, `Renamed expected file for ${executionId}`);
|
|
192269
192296
|
}
|
|
192270
192297
|
async function deleteRunExpected(executionId, name, opts) {
|
|
@@ -192272,7 +192299,7 @@ async function deleteRunExpected(executionId, name, opts) {
|
|
|
192272
192299
|
throw new Error("Delete cancelled");
|
|
192273
192300
|
}
|
|
192274
192301
|
const client = buildClient(opts);
|
|
192275
|
-
await client.delete(`/api/v1/runs/${encodeURIComponent(executionId)}/expected/${encodeURIComponent(name)}`);
|
|
192302
|
+
await client.delete(`/api/v1/runs/${encodeURIComponent(executionId)}/feedback/expected/${encodeURIComponent(name)}`);
|
|
192276
192303
|
renderGeneric({ ok: true }, opts, `Deleted expected file ${name}`);
|
|
192277
192304
|
}
|
|
192278
192305
|
async function watchRunCommand(executionId, opts) {
|
|
@@ -192557,7 +192584,10 @@ function registerAgentCommands(program2) {
|
|
|
192557
192584
|
}
|
|
192558
192585
|
async function listAgents(opts) {
|
|
192559
192586
|
const client = buildClient(opts);
|
|
192560
|
-
const payload = await client.get("/api/v1/
|
|
192587
|
+
const payload = await client.get("/api/v1/automations", {
|
|
192588
|
+
...compactParams(opts),
|
|
192589
|
+
type: "agent"
|
|
192590
|
+
});
|
|
192561
192591
|
if (opts.json)
|
|
192562
192592
|
return printJson(payload);
|
|
192563
192593
|
console.log(table(payload.data, [
|
|
@@ -193470,32 +193500,69 @@ function getExampleNames(dir, exampleNamesFilter) {
|
|
|
193470
193500
|
if (!baseDir) {
|
|
193471
193501
|
throw new Error(`No dataset/examples directory found at ${join8(dir, ...DATASET_EXAMPLES_DIR)}. Run \`eigenpal init workflow <name>\` to scaffold one.`);
|
|
193472
193502
|
}
|
|
193473
|
-
const all = readdirSync4(baseDir).filter((name) => statSync4(join8(baseDir, name)).isDirectory() && existsSync14(join8(baseDir, name, "input
|
|
193503
|
+
const all = readdirSync4(baseDir).filter((name) => statSync4(join8(baseDir, name)).isDirectory() && existsSync14(join8(baseDir, name, "input.json"))).sort((a5, b8) => a5.localeCompare(b8, undefined, { numeric: true }));
|
|
193474
193504
|
if (!exampleNamesFilter?.length)
|
|
193475
193505
|
return all;
|
|
193476
193506
|
return all.filter((n5) => exampleNamesFilter.includes(n5));
|
|
193477
193507
|
}
|
|
193478
193508
|
function readInput(exampleDir) {
|
|
193479
|
-
const
|
|
193480
|
-
|
|
193481
|
-
|
|
193509
|
+
const canonicalPath = join8(exampleDir, "input.json");
|
|
193510
|
+
return readCanonicalInput(exampleDir, canonicalPath);
|
|
193511
|
+
}
|
|
193512
|
+
function readCanonicalInput(exampleDir, inputPath) {
|
|
193513
|
+
const raw = JSON.parse(readFileSync13(inputPath, "utf-8"));
|
|
193514
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
193515
|
+
throw new Error(`${inputPath} must contain a JSON object.`);
|
|
193516
|
+
}
|
|
193517
|
+
const scalars = {};
|
|
193482
193518
|
const files = [];
|
|
193483
|
-
for (const
|
|
193484
|
-
const
|
|
193485
|
-
if (
|
|
193519
|
+
for (const [key2, value] of Object.entries(raw)) {
|
|
193520
|
+
const refs = topLevelFileRefs(value);
|
|
193521
|
+
if (refs.length === 0) {
|
|
193522
|
+
scalars[key2] = value;
|
|
193486
193523
|
continue;
|
|
193487
|
-
|
|
193488
|
-
for (const
|
|
193524
|
+
}
|
|
193525
|
+
for (const ref of refs) {
|
|
193526
|
+
const relative3 = parseDatasetInputFileRef(ref);
|
|
193527
|
+
if (!relative3) {
|
|
193528
|
+
throw new Error(`${inputPath}: ${key2} must reference files under input/ without path traversal.`);
|
|
193529
|
+
}
|
|
193530
|
+
const filePath = join8(exampleDir, "input", relative3);
|
|
193531
|
+
const filename = relative3.split("/").at(-1) ?? relative3;
|
|
193489
193532
|
files.push({
|
|
193490
|
-
argument:
|
|
193533
|
+
argument: key2,
|
|
193491
193534
|
filename,
|
|
193492
|
-
content: readFileSync13(
|
|
193535
|
+
content: readFileSync13(filePath),
|
|
193493
193536
|
mimeType: guessMimeType(filename)
|
|
193494
193537
|
});
|
|
193495
193538
|
}
|
|
193496
193539
|
}
|
|
193497
193540
|
return { scalars, files };
|
|
193498
193541
|
}
|
|
193542
|
+
function topLevelFileRefs(value) {
|
|
193543
|
+
if (isFileRef(value))
|
|
193544
|
+
return [value.$file];
|
|
193545
|
+
if (Array.isArray(value) && value.every(isFileRef))
|
|
193546
|
+
return value.map((item) => item.$file);
|
|
193547
|
+
if (Array.isArray(value) && value.some(isFileRef)) {
|
|
193548
|
+
throw new Error('file arrays must contain only { "$file": "input/..." } entries.');
|
|
193549
|
+
}
|
|
193550
|
+
return [];
|
|
193551
|
+
}
|
|
193552
|
+
function isFileRef(value) {
|
|
193553
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.$file === "string";
|
|
193554
|
+
}
|
|
193555
|
+
function parseDatasetInputFileRef(path15) {
|
|
193556
|
+
if (!path15.startsWith("input/"))
|
|
193557
|
+
return null;
|
|
193558
|
+
if (path15.startsWith("/") || path15.includes("\\") || path15.includes("\x00"))
|
|
193559
|
+
return null;
|
|
193560
|
+
const relative3 = path15.slice("input/".length);
|
|
193561
|
+
const parts = relative3.split("/");
|
|
193562
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
193563
|
+
return null;
|
|
193564
|
+
return parts.join("/");
|
|
193565
|
+
}
|
|
193499
193566
|
function readOverrides(exampleDir) {
|
|
193500
193567
|
const metaPath = join8(exampleDir, "meta.json");
|
|
193501
193568
|
if (!existsSync14(metaPath))
|
|
@@ -193772,8 +193839,8 @@ async function runSavedWorkflowExamples(workflow2, examples, opts) {
|
|
|
193772
193839
|
|
|
193773
193840
|
// src/commands/run.ts
|
|
193774
193841
|
function registerRunCommands(program2) {
|
|
193775
|
-
addJsonFlag(withBaseUrl(program2.command("run <target>"))).description("Start a workflow or agent run, e.g. workflows.extract-invoice.").option("--input-json <json>", "JSON input object").option("--input-file <field=path>", 'Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file".', collectRepeated, []).option("--example <name>", "Run one persisted example by name").option("--dir <dir>", "Local eigenpal directory for workflow examples", undefined).option("--wait", "Poll until the run reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before
|
|
193776
|
-
addJsonFlag(withBaseUrl(program2.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--version <version>", "Version/source ref for the new run: latest, original, or an explicit ref", "latest").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before
|
|
193842
|
+
addJsonFlag(withBaseUrl(program2.command("run <target>"))).description("Start a workflow or agent run, e.g. workflows.extract-invoice.").option("--input-json <json>", "JSON input object").option("--input-file <field=path>", 'Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file".', collectRepeated, []).option("--example <name>", "Run one persisted example by name").option("--dir <dir>", "Local eigenpal directory for workflow examples", undefined).option("--wait", "Poll until the run reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(runTarget));
|
|
193843
|
+
addJsonFlag(withBaseUrl(program2.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--version <version>", "Version/source ref for the new run: latest, original, or an explicit ref", "latest").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(rerunTarget));
|
|
193777
193844
|
}
|
|
193778
193845
|
async function runTarget(target, opts) {
|
|
193779
193846
|
const parsed = parseRunTarget(target);
|
|
@@ -194366,8 +194433,8 @@ async function status(opts) {
|
|
|
194366
194433
|
}
|
|
194367
194434
|
process.exit(1);
|
|
194368
194435
|
}
|
|
194369
|
-
const workflows = await fetchOrNull(client, "/api/
|
|
194370
|
-
const workflowCount = typeof workflows?.total === "number" ? workflows.total : Array.isArray(workflows?.items) ? workflows.items.length : null;
|
|
194436
|
+
const workflows = await fetchOrNull(client, "/api/workflows?limit=1");
|
|
194437
|
+
const workflowCount = typeof workflows?.total === "number" ? workflows.total : Array.isArray(workflows?.data) ? workflows.data.length : Array.isArray(workflows?.items) ? workflows.items.length : null;
|
|
194371
194438
|
if (opts.json) {
|
|
194372
194439
|
console.log(JSON.stringify({
|
|
194373
194440
|
authenticated: true,
|
|
@@ -195230,122 +195297,60 @@ function validateDatasetFolder(root2) {
|
|
|
195230
195297
|
});
|
|
195231
195298
|
continue;
|
|
195232
195299
|
}
|
|
195233
|
-
const
|
|
195234
|
-
|
|
195300
|
+
const inputJsonPath = join13(exampleFolder, "input.json");
|
|
195301
|
+
const hasInputJson = existsSync17(inputJsonPath);
|
|
195302
|
+
if (!hasInputJson) {
|
|
195235
195303
|
issues.push({
|
|
195236
|
-
field: `examples/${exampleName}/input
|
|
195237
|
-
message: "Missing required
|
|
195304
|
+
field: `examples/${exampleName}/input.json`,
|
|
195305
|
+
message: "Missing required input.json."
|
|
195238
195306
|
});
|
|
195239
195307
|
continue;
|
|
195240
195308
|
}
|
|
195241
195309
|
let args;
|
|
195242
195310
|
try {
|
|
195243
|
-
const parsed = JSON.parse(readFileSync15(
|
|
195311
|
+
const parsed = JSON.parse(readFileSync15(inputJsonPath, "utf-8"));
|
|
195244
195312
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
195245
195313
|
issues.push({
|
|
195246
|
-
field: `examples/${exampleName}/input
|
|
195247
|
-
message: "
|
|
195314
|
+
field: `examples/${exampleName}/input.json`,
|
|
195315
|
+
message: "input.json must be a JSON object."
|
|
195248
195316
|
});
|
|
195249
195317
|
continue;
|
|
195250
195318
|
}
|
|
195251
195319
|
args = parsed;
|
|
195252
195320
|
} catch {
|
|
195253
195321
|
issues.push({
|
|
195254
|
-
field: `examples/${exampleName}/input
|
|
195255
|
-
message: "
|
|
195322
|
+
field: `examples/${exampleName}/input.json`,
|
|
195323
|
+
message: "input.json is not valid JSON."
|
|
195256
195324
|
});
|
|
195257
195325
|
continue;
|
|
195258
195326
|
}
|
|
195259
195327
|
const inputDir = join13(exampleFolder, "input");
|
|
195260
|
-
|
|
195261
|
-
|
|
195262
|
-
|
|
195263
|
-
|
|
195264
|
-
|
|
195265
|
-
|
|
195266
|
-
|
|
195267
|
-
|
|
195268
|
-
|
|
195269
|
-
|
|
195270
|
-
|
|
195271
|
-
|
|
195272
|
-
|
|
195273
|
-
|
|
195274
|
-
}
|
|
195275
|
-
if (!isDir) {
|
|
195276
|
-
issues.push({
|
|
195277
|
-
field,
|
|
195278
|
-
message: "Entries under input/ must live inside an argument folder."
|
|
195279
|
-
});
|
|
195280
|
-
continue;
|
|
195281
|
-
}
|
|
195282
|
-
if (!NAME_RE.test(entry)) {
|
|
195283
|
-
issues.push({
|
|
195284
|
-
field: `examples/${exampleName}/input/${entry}`,
|
|
195285
|
-
message: "Argument folder name must be lowercase kebab/snake-case."
|
|
195286
|
-
});
|
|
195287
|
-
}
|
|
195288
|
-
if (Object.prototype.hasOwnProperty.call(args, entry)) {
|
|
195289
|
-
issues.push({
|
|
195290
|
-
field: `examples/${exampleName}/input/${entry}`,
|
|
195291
|
-
message: `Argument-name collision: "${entry}" appears in both arguments.json and as an input/ folder.`
|
|
195292
|
-
});
|
|
195293
|
-
}
|
|
195328
|
+
if (existsSync17(join13(inputDir, "arguments.json"))) {
|
|
195329
|
+
issues.push({
|
|
195330
|
+
field: `examples/${exampleName}/input/arguments.json`,
|
|
195331
|
+
message: "Legacy input/arguments.json is no longer accepted. Use input.json instead."
|
|
195332
|
+
});
|
|
195333
|
+
}
|
|
195334
|
+
const fileRefs = collectDatasetFileRefs(args);
|
|
195335
|
+
for (const issue2 of validateFileRefs({
|
|
195336
|
+
refs: fileRefs,
|
|
195337
|
+
filesRoot: inputDir,
|
|
195338
|
+
root: "input",
|
|
195339
|
+
fieldPrefix: `examples/${exampleName}/input.json`
|
|
195340
|
+
})) {
|
|
195341
|
+
issues.push(issue2);
|
|
195294
195342
|
}
|
|
195295
195343
|
const expectedDir = join13(exampleFolder, "expected");
|
|
195344
|
+
const expectedJsonPath = join13(exampleFolder, "expected.json");
|
|
195296
195345
|
if (existsSync17(expectedDir)) {
|
|
195297
|
-
const
|
|
195298
|
-
|
|
195299
|
-
const hasExpectedOutput = existsSync17(expectedPath);
|
|
195300
|
-
const hasExpectedError = existsSync17(expectedErrorPath);
|
|
195301
|
-
if (hasExpectedOutput && hasExpectedError) {
|
|
195302
|
-
issues.push({
|
|
195303
|
-
field: `examples/${exampleName}/expected`,
|
|
195304
|
-
message: "expected/output.json and expected/error.json are mutually exclusive (pick success-expected OR failure-expected)."
|
|
195305
|
-
});
|
|
195306
|
-
}
|
|
195307
|
-
if (hasExpectedOutput) {
|
|
195308
|
-
let parsed;
|
|
195309
|
-
try {
|
|
195310
|
-
parsed = JSON.parse(readFileSync15(expectedPath, "utf-8"));
|
|
195311
|
-
} catch {
|
|
195312
|
-
issues.push({
|
|
195313
|
-
field: `examples/${exampleName}/expected/output.json`,
|
|
195314
|
-
message: "expected/output.json is not valid JSON."
|
|
195315
|
-
});
|
|
195316
|
-
continue;
|
|
195317
|
-
}
|
|
195318
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
195319
|
-
issues.push({
|
|
195320
|
-
field: `examples/${exampleName}/expected/output.json`,
|
|
195321
|
-
message: "expected/output.json must be a JSON object (mirrors the workflow `output:` shape)."
|
|
195322
|
-
});
|
|
195323
|
-
}
|
|
195324
|
-
}
|
|
195325
|
-
if (hasExpectedError) {
|
|
195326
|
-
let parsed;
|
|
195327
|
-
try {
|
|
195328
|
-
parsed = JSON.parse(readFileSync15(expectedErrorPath, "utf-8"));
|
|
195329
|
-
} catch {
|
|
195346
|
+
for (const entry of readdirSync7(expectedDir)) {
|
|
195347
|
+
if (entry === "output.json" || entry === "error.json") {
|
|
195330
195348
|
issues.push({
|
|
195331
|
-
field: `examples/${exampleName}/expected
|
|
195332
|
-
message: "expected
|
|
195349
|
+
field: `examples/${exampleName}/expected/${entry}`,
|
|
195350
|
+
message: "Legacy expected files are no longer accepted. Use expected.json instead."
|
|
195333
195351
|
});
|
|
195334
195352
|
continue;
|
|
195335
195353
|
}
|
|
195336
|
-
const result = ExpectedErrorSchema.safeParse(parsed);
|
|
195337
|
-
if (!result.success) {
|
|
195338
|
-
for (const err2 of result.error.issues) {
|
|
195339
|
-
issues.push({
|
|
195340
|
-
field: `examples/${exampleName}/expected/error.json${err2.path.length > 0 ? `:${err2.path.join(".")}` : ""}`,
|
|
195341
|
-
message: err2.message
|
|
195342
|
-
});
|
|
195343
|
-
}
|
|
195344
|
-
}
|
|
195345
|
-
}
|
|
195346
|
-
for (const entry of readdirSync7(expectedDir)) {
|
|
195347
|
-
if (entry === "output.json" || entry === "error.json")
|
|
195348
|
-
continue;
|
|
195349
195354
|
const sub = join13(expectedDir, entry);
|
|
195350
195355
|
const field = `examples/${exampleName}/expected/${entry}`;
|
|
195351
195356
|
let isDir;
|
|
@@ -195361,7 +195366,7 @@ function validateDatasetFolder(root2) {
|
|
|
195361
195366
|
if (!isDir) {
|
|
195362
195367
|
issues.push({
|
|
195363
195368
|
field,
|
|
195364
|
-
message:
|
|
195369
|
+
message: 'Entries under expected/ must be file folders referenced from expected.json with { "$file": "expected/..." }.'
|
|
195365
195370
|
});
|
|
195366
195371
|
continue;
|
|
195367
195372
|
}
|
|
@@ -195373,6 +195378,43 @@ function validateDatasetFolder(root2) {
|
|
|
195373
195378
|
}
|
|
195374
195379
|
}
|
|
195375
195380
|
}
|
|
195381
|
+
if (existsSync17(expectedJsonPath)) {
|
|
195382
|
+
let parsed;
|
|
195383
|
+
try {
|
|
195384
|
+
parsed = JSON.parse(readFileSync15(expectedJsonPath, "utf-8"));
|
|
195385
|
+
} catch {
|
|
195386
|
+
issues.push({
|
|
195387
|
+
field: `examples/${exampleName}/expected.json`,
|
|
195388
|
+
message: "expected.json is not valid JSON."
|
|
195389
|
+
});
|
|
195390
|
+
continue;
|
|
195391
|
+
}
|
|
195392
|
+
if (isExpectedErrorRef(parsed)) {
|
|
195393
|
+
const result = ExpectedErrorSchema.safeParse(parsed.$error);
|
|
195394
|
+
if (!result.success) {
|
|
195395
|
+
for (const err2 of result.error.issues) {
|
|
195396
|
+
issues.push({
|
|
195397
|
+
field: `examples/${exampleName}/expected.json:$error${err2.path.length > 0 ? `.${err2.path.join(".")}` : ""}`,
|
|
195398
|
+
message: err2.message
|
|
195399
|
+
});
|
|
195400
|
+
}
|
|
195401
|
+
}
|
|
195402
|
+
} else if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
195403
|
+
issues.push({
|
|
195404
|
+
field: `examples/${exampleName}/expected.json`,
|
|
195405
|
+
message: "expected.json must be a JSON object."
|
|
195406
|
+
});
|
|
195407
|
+
} else {
|
|
195408
|
+
for (const issue2 of validateFileRefs({
|
|
195409
|
+
refs: collectDatasetFileRefs(parsed),
|
|
195410
|
+
filesRoot: join13(exampleFolder, "expected"),
|
|
195411
|
+
root: "expected",
|
|
195412
|
+
fieldPrefix: `examples/${exampleName}/expected.json`
|
|
195413
|
+
})) {
|
|
195414
|
+
issues.push(issue2);
|
|
195415
|
+
}
|
|
195416
|
+
}
|
|
195417
|
+
}
|
|
195376
195418
|
}
|
|
195377
195419
|
return issues;
|
|
195378
195420
|
}
|
|
@@ -195390,6 +195432,54 @@ function printIssues(label2, path15, issues) {
|
|
|
195390
195432
|
console.error("");
|
|
195391
195433
|
return false;
|
|
195392
195434
|
}
|
|
195435
|
+
function isFileRef2(value) {
|
|
195436
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.$file === "string";
|
|
195437
|
+
}
|
|
195438
|
+
function isExpectedErrorRef(value) {
|
|
195439
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 1 && Object.prototype.hasOwnProperty.call(value, "$error");
|
|
195440
|
+
}
|
|
195441
|
+
function collectDatasetFileRefs(value, jsonPath = "<root>") {
|
|
195442
|
+
if (isFileRef2(value))
|
|
195443
|
+
return [{ path: value.$file, jsonPath }];
|
|
195444
|
+
if (Array.isArray(value)) {
|
|
195445
|
+
return value.flatMap((item, index) => collectDatasetFileRefs(item, `${jsonPath}.${index}`));
|
|
195446
|
+
}
|
|
195447
|
+
if (value && typeof value === "object") {
|
|
195448
|
+
return Object.entries(value).flatMap(([key2, item]) => collectDatasetFileRefs(item, jsonPath === "<root>" ? key2 : `${jsonPath}.${key2}`));
|
|
195449
|
+
}
|
|
195450
|
+
return [];
|
|
195451
|
+
}
|
|
195452
|
+
function validateFileRefs(args) {
|
|
195453
|
+
const issues = [];
|
|
195454
|
+
for (const ref of args.refs) {
|
|
195455
|
+
const relative4 = parseDatasetFileRef(ref.path, args.root);
|
|
195456
|
+
if (!relative4) {
|
|
195457
|
+
issues.push({
|
|
195458
|
+
field: `${args.fieldPrefix}:${ref.jsonPath}`,
|
|
195459
|
+
message: `File reference must point inside ${args.root}/ and must not contain path traversal.`
|
|
195460
|
+
});
|
|
195461
|
+
continue;
|
|
195462
|
+
}
|
|
195463
|
+
if (!existsSync17(join13(args.filesRoot, relative4))) {
|
|
195464
|
+
issues.push({
|
|
195465
|
+
field: `${args.fieldPrefix}:${ref.jsonPath}`,
|
|
195466
|
+
message: `Referenced file does not exist: ${ref.path}.`
|
|
195467
|
+
});
|
|
195468
|
+
}
|
|
195469
|
+
}
|
|
195470
|
+
return issues;
|
|
195471
|
+
}
|
|
195472
|
+
function parseDatasetFileRef(path15, root2) {
|
|
195473
|
+
if (!path15.startsWith(`${root2}/`))
|
|
195474
|
+
return null;
|
|
195475
|
+
if (path15.startsWith("/") || path15.includes("\\") || path15.includes("\x00"))
|
|
195476
|
+
return null;
|
|
195477
|
+
const relative4 = path15.slice(root2.length + 1);
|
|
195478
|
+
const parts = relative4.split("/");
|
|
195479
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
195480
|
+
return null;
|
|
195481
|
+
return parts.join("/");
|
|
195482
|
+
}
|
|
195393
195483
|
function rootDir(opts) {
|
|
195394
195484
|
return resolve6(opts.dir ?? process.cwd());
|
|
195395
195485
|
}
|
|
@@ -195577,7 +195667,7 @@ function registerWorkflowCoreCommands(parent) {
|
|
|
195577
195667
|
};
|
|
195578
195668
|
if (opts.search)
|
|
195579
195669
|
params.search = opts.search;
|
|
195580
|
-
const raw = await client.get("/api/
|
|
195670
|
+
const raw = await client.get("/api/workflows", params);
|
|
195581
195671
|
renderListResult(raw, [
|
|
195582
195672
|
{ key: "id", header: "id" },
|
|
195583
195673
|
{ key: "name", header: "name", format: (v11) => typeof v11 === "string" ? v11 : "-" },
|
|
@@ -195588,7 +195678,7 @@ function registerWorkflowCoreCommands(parent) {
|
|
|
195588
195678
|
const pullCmd = parent.command("pull <workflow-id>").description(descriptionFor("workflow_get_definition")).option("--out <path>", "Write YAML to file instead of stdout");
|
|
195589
195679
|
withBaseUrl(pullCmd).action(action(async (workflow2, opts) => {
|
|
195590
195680
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
195591
|
-
const result = await client.get(`/api/
|
|
195681
|
+
const result = await client.get(`/api/workflows/${workflowId}`);
|
|
195592
195682
|
if (typeof result?.yamlContent !== "string" || result.yamlContent.length === 0) {
|
|
195593
195683
|
throw new Error(`Workflow ${workflowId} has no published version yet, nothing to pull. Run \`eigenpal workflow push\` first.`);
|
|
195594
195684
|
}
|
|
@@ -195659,7 +195749,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
195659
195749
|
if (opts.setVersion) {
|
|
195660
195750
|
nextVersion = opts.setVersion;
|
|
195661
195751
|
} else if (opts.bump) {
|
|
195662
|
-
const wf3 = await client.get(`/api/
|
|
195752
|
+
const wf3 = await client.get(`/api/workflows/${workflowId}`);
|
|
195663
195753
|
const current = wf3?.version;
|
|
195664
195754
|
if (!current) {
|
|
195665
195755
|
throw new Error(`Cannot --bump ${opts.bump} on ${workflowId}: server has no current version. Push an initial version (with \`--set-version 1.0.0\` or a YAML \`version:\` field) first.`);
|
|
@@ -195676,7 +195766,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
195676
195766
|
process.exit(1);
|
|
195677
195767
|
}
|
|
195678
195768
|
const yamlToSend = cliWantsRewrite ? spliceWorkflowVersion2(yamlOnDisk, nextVersion) : yamlOnDisk;
|
|
195679
|
-
const result = await client.post(`/api/
|
|
195769
|
+
const result = await client.post(`/api/workflows/${workflowId}/versions`, {
|
|
195680
195770
|
yaml: yamlToSend,
|
|
195681
195771
|
version: nextVersion
|
|
195682
195772
|
});
|
|
@@ -195692,7 +195782,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
195692
195782
|
}
|
|
195693
195783
|
const yamlToSend = opts.setVersion && !extractWorkflowVersion(yamlOnDisk) ? spliceWorkflowVersion2(yamlOnDisk, opts.setVersion) : yamlOnDisk;
|
|
195694
195784
|
const createVersion = opts.setVersion ?? extractWorkflowVersion(yamlToSend) ?? undefined;
|
|
195695
|
-
const result = await client.post("/api/
|
|
195785
|
+
const result = await client.post("/api/workflows", {
|
|
195696
195786
|
yaml: yamlToSend,
|
|
195697
195787
|
...createVersion ? { version: createVersion } : {}
|
|
195698
195788
|
});
|
|
@@ -195709,7 +195799,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
195709
195799
|
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)");
|
|
195710
195800
|
addJsonFlag(withBaseUrl(moveCmd)).action(action(async (workflow2, opts) => {
|
|
195711
195801
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
195712
|
-
const result = await client.patch(`/api/
|
|
195802
|
+
const result = await client.patch(`/api/workflows/${workflowId}`, {
|
|
195713
195803
|
folderPath: opts.folder
|
|
195714
195804
|
});
|
|
195715
195805
|
if (opts.json) {
|
|
@@ -195898,19 +195988,12 @@ skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset
|
|
|
195898
195988
|
process.stderr.write(` ${ui.dim(`size cap: ${errPayload.limitMb} MB, archive: ${errPayload.actualMb} MB`)}
|
|
195899
195989
|
`);
|
|
195900
195990
|
}
|
|
195901
|
-
|
|
195902
|
-
if (codes2.has("invalid_expected_output")) {
|
|
195991
|
+
if (issues.some((i5) => i5.code === "invalid_expected_output")) {
|
|
195903
195992
|
process.stderr.write(`
|
|
195904
|
-
${ui.dim("hint:")} ${ui.bold("expected
|
|
195905
|
-
` + ` For expected
|
|
195993
|
+
${ui.dim("hint:")} ${ui.bold("expected.json")} must be a JSON object mirroring your automation output.
|
|
195994
|
+
` + ` For expected files, reference binaries from JSON with ${ui.bold('{ "$file": "expected/<path>" }')}.
|
|
195906
195995
|
` + ` See ${ui.bold("reference/dataset-format.md")} or run ${ui.bold("eigenpal workflow dataset validate ./dataset")}.
|
|
195907
195996
|
|
|
195908
|
-
`);
|
|
195909
|
-
}
|
|
195910
|
-
if (codes2.has("argument_name_collision")) {
|
|
195911
|
-
process.stderr.write(`
|
|
195912
|
-
${ui.dim("hint:")} an arg cannot live in both ${ui.bold("arguments.json")} AND ${ui.bold("input/<arg>/")}; pick one.
|
|
195913
|
-
|
|
195914
195997
|
`);
|
|
195915
195998
|
}
|
|
195916
195999
|
}
|
|
@@ -195926,7 +196009,7 @@ skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset
|
|
|
195926
196009
|
const expectedSet = done.expectedSet ?? 0;
|
|
195927
196010
|
success2(`Imported ${created} example${created === 1 ? "" : "s"}` + (done.mode === "replace" && done.deleted != null ? ` ${ui.dim(`(replaced ${done.deleted}, freed ${done.filesDeleted ?? 0} files)`)}` : ""));
|
|
195928
196011
|
if (created > 0 && expectedSet < created) {
|
|
195929
|
-
warn(`${expectedSet}/${created} examples have expected
|
|
196012
|
+
warn(`${expectedSet}/${created} examples have expected.json — the rest will run un-graded.`);
|
|
195930
196013
|
} else if (expectedSet > 0) {
|
|
195931
196014
|
process.stderr.write(` ${ui.dim(`${expectedSet} with expected output`)}
|
|
195932
196015
|
`);
|
|
@@ -195980,8 +196063,8 @@ Examples:
|
|
|
195980
196063
|
$ cat expected.json | eigenpal workflow dataset example create wf_abc123 --name piped --input-json '{}' --expected-file -
|
|
195981
196064
|
|
|
195982
196065
|
\`--input-json\` and \`--input-file\` (same for \`--expected-*\`) are mutually
|
|
195983
|
-
exclusive. File
|
|
195984
|
-
handles
|
|
196066
|
+
exclusive. File uploads still require a full \`dataset push\`; CRUD only
|
|
196067
|
+
handles JSON input + \`expected.json\`-style outputs.
|
|
195985
196068
|
`);
|
|
195986
196069
|
addJsonFlag(withBaseUrl(createCmd)).action(action(async (workflow2, opts) => {
|
|
195987
196070
|
const triggerInput = await readJsonInput(opts.inputJson, opts.inputFile, "input");
|
|
@@ -196298,9 +196381,8 @@ Behavior:
|
|
|
196298
196381
|
withBaseUrl(resultsCmd).action(action(async (workflow2, batchId, opts) => {
|
|
196299
196382
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
196300
196383
|
const params = new URLSearchParams({ format: opts.format });
|
|
196301
|
-
|
|
196302
|
-
|
|
196303
|
-
await downloadStreamToFile2(client, `/api/v1/workflows/${workflowId}/eval-results/export?${params.toString()}`, opts.out);
|
|
196384
|
+
const exportPath = batchId ? `/api/v1/automations/${workflowId}/experiments/${batchId}/export?${params.toString()}` : `/api/v1/automations/${workflowId}/experiments/export?${params.toString()}`;
|
|
196385
|
+
await downloadStreamToFile2(client, exportPath, opts.out);
|
|
196304
196386
|
}));
|
|
196305
196387
|
const compareCmd = experiment.command("compare <batchIdA> <batchIdB>").description("Diff eval scores between two experiment batches.").option("--sort <abs-delta-desc|delta-asc|delta-desc|name>", "Row sort order (default: biggest movers first)", "abs-delta-desc").option("--regression-threshold <n>", "Δ below this is flagged as a regression (default 0.05)", Number.parseFloat, 0.05).addHelpText("after", `
|
|
196306
196388
|
Examples:
|
|
@@ -196386,9 +196468,9 @@ Behavior:
|
|
|
196386
196468
|
onTerminal: async () => {
|
|
196387
196469
|
if (!dest)
|
|
196388
196470
|
return;
|
|
196389
|
-
const params = new URLSearchParams({ format: opts.format
|
|
196471
|
+
const params = new URLSearchParams({ format: opts.format });
|
|
196390
196472
|
try {
|
|
196391
|
-
await downloadStreamToFile2(client, `/api/v1/
|
|
196473
|
+
await downloadStreamToFile2(client, `/api/v1/automations/${workflowId}/experiments/${batchId}/export?${params.toString()}`, dest);
|
|
196392
196474
|
} catch (err2) {
|
|
196393
196475
|
error51(`Watch finished but pulling results failed: ${err2 instanceof Error ? err2.message : String(err2)}. Re-run \`experiment results <wf> <batch>\` to retry.`);
|
|
196394
196476
|
process.exit(1);
|
|
@@ -196515,7 +196597,7 @@ function registerVersionsCommands(parent) {
|
|
|
196515
196597
|
const listCmd = versions3.command("list <workflow-id>").description(descriptionFor("workflow_list_versions"));
|
|
196516
196598
|
addJsonFlag(withBaseUrl(withPagination(listCmd))).action(action(async (workflow2, opts) => {
|
|
196517
196599
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
196518
|
-
const raw = await client.get(`/api/
|
|
196600
|
+
const raw = await client.get(`/api/workflows/${workflowId}/versions`, {
|
|
196519
196601
|
limit: String(opts.limit),
|
|
196520
196602
|
offset: String(opts.offset)
|
|
196521
196603
|
});
|
|
@@ -196529,7 +196611,7 @@ function registerVersionsCommands(parent) {
|
|
|
196529
196611
|
const restoreCmd = versions3.command("restore <workflow-id> <versionId>").description(descriptionFor("workflow_restore_version"));
|
|
196530
196612
|
addJsonFlag(withBaseUrl(restoreCmd)).action(action(async (workflow2, versionId, opts) => {
|
|
196531
196613
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
196532
|
-
const result = await client.post(`/api/
|
|
196614
|
+
const result = await client.post(`/api/workflows/${workflowId}/history/${versionId}/restore`, {});
|
|
196533
196615
|
if (opts.json) {
|
|
196534
196616
|
printJson2(result);
|
|
196535
196617
|
return;
|