@eigenpal/cli 0.5.8 → 0.5.10
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 +11 -1
- package/dist/cli.js +542 -25
- 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,12 @@
|
|
|
1
1
|
# @eigenpal/cli
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.5.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- aee7e0c: Add hidden Git source commands for editing trigger policy and encrypted secrets.
|
|
8
|
+
|
|
9
|
+
## 0.5.9
|
|
4
10
|
|
|
5
11
|
### Major Changes
|
|
6
12
|
|
|
@@ -333,6 +339,10 @@ actual) { ... }` declaration around your statements. Parameter order is
|
|
|
333
339
|
|
|
334
340
|
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.
|
|
335
341
|
|
|
342
|
+
- 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.
|
|
343
|
+
|
|
344
|
+
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.
|
|
345
|
+
|
|
336
346
|
- 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).
|
|
337
347
|
|
|
338
348
|
In CI:
|
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) {
|
|
@@ -68012,7 +68021,63 @@ var init_eval = __esm(() => {
|
|
|
68012
68021
|
init_workflow_tool_metadata();
|
|
68013
68022
|
});
|
|
68014
68023
|
|
|
68024
|
+
// ../types/src/git-source/cutover.ts
|
|
68025
|
+
function resolveAgentSourceCutover(settings) {
|
|
68026
|
+
const parsed = AgentSourceCutoverSettingsSchema.safeParse(settings ?? {});
|
|
68027
|
+
if (!parsed.success)
|
|
68028
|
+
return { mode: "legacy", reason: "invalid_settings" };
|
|
68029
|
+
const agents = parsed.data.gitBackedSource?.agents;
|
|
68030
|
+
if (!agents)
|
|
68031
|
+
return { mode: "legacy", reason: "settings_missing" };
|
|
68032
|
+
if (agents.migrationState === "rolled_back")
|
|
68033
|
+
return { mode: "legacy", reason: "rolled_back" };
|
|
68034
|
+
if (agents.runtime !== "git")
|
|
68035
|
+
return { mode: "legacy", reason: "runtime_legacy" };
|
|
68036
|
+
if (agents.migrationState !== "cutover") {
|
|
68037
|
+
return { mode: "legacy", reason: "migration_not_cutover" };
|
|
68038
|
+
}
|
|
68039
|
+
return { mode: "git", reason: "cutover_enabled" };
|
|
68040
|
+
}
|
|
68041
|
+
function isGitBackedAgentSourceEnabled(settings) {
|
|
68042
|
+
return resolveAgentSourceCutover(settings).mode === "git";
|
|
68043
|
+
}
|
|
68044
|
+
var AgentSourceRuntimeModeSchema, AgentSourceMigrationStateSchema, AgentSourceCutoverSettingsSchema;
|
|
68045
|
+
var init_cutover = __esm(() => {
|
|
68046
|
+
init_zod();
|
|
68047
|
+
AgentSourceRuntimeModeSchema = exports_external.enum(["legacy", "git"]);
|
|
68048
|
+
AgentSourceMigrationStateSchema = exports_external.enum([
|
|
68049
|
+
"disabled",
|
|
68050
|
+
"shadow",
|
|
68051
|
+
"validated",
|
|
68052
|
+
"cutover",
|
|
68053
|
+
"rolled_back"
|
|
68054
|
+
]);
|
|
68055
|
+
AgentSourceCutoverSettingsSchema = exports_external.object({
|
|
68056
|
+
gitBackedSource: exports_external.object({
|
|
68057
|
+
agents: exports_external.object({
|
|
68058
|
+
runtime: AgentSourceRuntimeModeSchema.optional(),
|
|
68059
|
+
migrationState: AgentSourceMigrationStateSchema.optional(),
|
|
68060
|
+
legacySourceRetained: exports_external.boolean().optional(),
|
|
68061
|
+
rollbackReason: exports_external.string().optional()
|
|
68062
|
+
}).passthrough().optional()
|
|
68063
|
+
}).passthrough().optional()
|
|
68064
|
+
}).passthrough();
|
|
68065
|
+
});
|
|
68066
|
+
|
|
68015
68067
|
// ../types/src/git-source/grammar.ts
|
|
68068
|
+
function parseAutomationTarget(target) {
|
|
68069
|
+
const parsed = AutomationTargetSchema.parse(target);
|
|
68070
|
+
const [rawPackageName, rawRef] = parsed.split("@");
|
|
68071
|
+
const packagePath = dottedPackageNameToPath(rawPackageName);
|
|
68072
|
+
const [type, ...slugParts] = packagePath.split("/");
|
|
68073
|
+
return {
|
|
68074
|
+
packageName: DottedPackageNameSchema.parse(rawPackageName),
|
|
68075
|
+
packagePath,
|
|
68076
|
+
type,
|
|
68077
|
+
slug: slugParts.join("/"),
|
|
68078
|
+
ref: SourceVersionRefSchema.parse(rawRef ?? "latest")
|
|
68079
|
+
};
|
|
68080
|
+
}
|
|
68016
68081
|
function pathToDottedPackageName(path2) {
|
|
68017
68082
|
const parsed = SourcePackagePathSchema.parse(path2);
|
|
68018
68083
|
return DottedPackageNameSchema.parse(parsed.split("/").join("."));
|
|
@@ -68344,7 +68409,16 @@ var init_manifests = __esm(() => {
|
|
|
68344
68409
|
api: exports_external.boolean().optional(),
|
|
68345
68410
|
email: exports_external.object({
|
|
68346
68411
|
enabled: exports_external.boolean().default(true),
|
|
68347
|
-
aliases: exports_external.array(exports_external.
|
|
68412
|
+
aliases: exports_external.array(exports_external.union([
|
|
68413
|
+
exports_external.string().includes("@"),
|
|
68414
|
+
exports_external.object({
|
|
68415
|
+
address: exports_external.string().includes("@"),
|
|
68416
|
+
allowlist: exports_external.array(exports_external.string()).default([]),
|
|
68417
|
+
replyConfig: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
|
|
68418
|
+
requireSenderAuth: exports_external.boolean().default(true),
|
|
68419
|
+
enabled: exports_external.boolean().optional()
|
|
68420
|
+
}).strict()
|
|
68421
|
+
])).default([])
|
|
68348
68422
|
}).strict().optional()
|
|
68349
68423
|
}).strict();
|
|
68350
68424
|
CommonPackageManifestFields = {
|
|
@@ -68413,10 +68487,63 @@ var init_manifests = __esm(() => {
|
|
|
68413
68487
|
SOURCE_TEMPLATE_FILE_PATTERN = /\.(?:docx|xlsx)$/i;
|
|
68414
68488
|
});
|
|
68415
68489
|
|
|
68490
|
+
// ../types/src/git-source/secrets.ts
|
|
68491
|
+
var SOURCE_SECRETS_FILENAME = "secrets.enc.yaml", SecretNameSchema, Base64LikeSchema, EncryptedSecretValueSchema, SourceSecretEntrySchema, SourceSecretsFileSchema;
|
|
68492
|
+
var init_secrets = __esm(() => {
|
|
68493
|
+
init_zod();
|
|
68494
|
+
init_manifests();
|
|
68495
|
+
SecretNameSchema = exports_external.string().regex(/^[A-Z][A-Z0-9_]*$/, "Secret names must be uppercase env-style identifiers");
|
|
68496
|
+
Base64LikeSchema = exports_external.string().min(1).regex(/^[A-Za-z0-9+/=_-]+$/, {
|
|
68497
|
+
message: "Encrypted secret fields must be encoded strings"
|
|
68498
|
+
});
|
|
68499
|
+
EncryptedSecretValueSchema = exports_external.object({
|
|
68500
|
+
algorithm: exports_external.literal("aes-256-gcm"),
|
|
68501
|
+
keyId: exports_external.string().min(1),
|
|
68502
|
+
nonce: Base64LikeSchema,
|
|
68503
|
+
ciphertext: Base64LikeSchema,
|
|
68504
|
+
tag: Base64LikeSchema
|
|
68505
|
+
}).strict();
|
|
68506
|
+
SourceSecretEntrySchema = exports_external.object({
|
|
68507
|
+
description: exports_external.string().optional(),
|
|
68508
|
+
encrypted: EncryptedSecretValueSchema.optional()
|
|
68509
|
+
}).passthrough().superRefine((entry, ctx) => {
|
|
68510
|
+
const raw = entry;
|
|
68511
|
+
if ("value" in raw || "plaintext" in raw || "secret" in raw) {
|
|
68512
|
+
ctx.addIssue({
|
|
68513
|
+
code: "custom",
|
|
68514
|
+
message: "secrets.enc.yaml must not contain plaintext secret values"
|
|
68515
|
+
});
|
|
68516
|
+
}
|
|
68517
|
+
if (!raw.encrypted) {
|
|
68518
|
+
ctx.addIssue({
|
|
68519
|
+
code: "custom",
|
|
68520
|
+
path: ["encrypted"],
|
|
68521
|
+
message: "Encrypted secret values are required"
|
|
68522
|
+
});
|
|
68523
|
+
}
|
|
68524
|
+
const extraKeys = Object.keys(raw).filter((key) => key !== "description" && key !== "encrypted");
|
|
68525
|
+
for (const key of extraKeys) {
|
|
68526
|
+
if (key === "value" || key === "plaintext" || key === "secret")
|
|
68527
|
+
continue;
|
|
68528
|
+
ctx.addIssue({
|
|
68529
|
+
code: "custom",
|
|
68530
|
+
path: [key],
|
|
68531
|
+
message: `Unknown secret metadata field: ${key}`
|
|
68532
|
+
});
|
|
68533
|
+
}
|
|
68534
|
+
});
|
|
68535
|
+
SourceSecretsFileSchema = exports_external.object({
|
|
68536
|
+
schemaVersion: SourceManifestSchemaVersionSchema,
|
|
68537
|
+
secrets: exports_external.record(SecretNameSchema, SourceSecretEntrySchema)
|
|
68538
|
+
}).strict();
|
|
68539
|
+
});
|
|
68540
|
+
|
|
68416
68541
|
// ../types/src/git-source/index.ts
|
|
68417
68542
|
var init_git_source = __esm(() => {
|
|
68543
|
+
init_cutover();
|
|
68418
68544
|
init_grammar();
|
|
68419
68545
|
init_manifests();
|
|
68546
|
+
init_secrets();
|
|
68420
68547
|
});
|
|
68421
68548
|
|
|
68422
68549
|
// ../types/src/storage/paths.ts
|
|
@@ -68720,11 +68847,13 @@ __export(exports_src, {
|
|
|
68720
68847
|
runInputKey: () => runInputKey,
|
|
68721
68848
|
rootS3Path: () => rootS3Path,
|
|
68722
68849
|
resolveEffectiveMimeType: () => resolveEffectiveMimeType,
|
|
68850
|
+
resolveAgentSourceCutover: () => resolveAgentSourceCutover,
|
|
68723
68851
|
requiresSpecializedParser: () => requiresSpecializedParser,
|
|
68724
68852
|
pathToDottedPackageName: () => pathToDottedPackageName,
|
|
68725
68853
|
parseStepType: () => parseStepType,
|
|
68726
68854
|
parseReleaseTag: () => parseReleaseTag,
|
|
68727
68855
|
parseFileKey: () => parseFileKey,
|
|
68856
|
+
parseAutomationTarget: () => parseAutomationTarget,
|
|
68728
68857
|
noopTracingContext: () => noopTracingContext,
|
|
68729
68858
|
mapOCRAttributes: () => mapOCRAttributes,
|
|
68730
68859
|
mapLLMAttributes: () => mapLLMAttributes,
|
|
@@ -68739,6 +68868,7 @@ __export(exports_src, {
|
|
|
68739
68868
|
isOutputSchemaUnknown: () => isOutputSchemaUnknown,
|
|
68740
68869
|
isNonSuccessTerminalExecutionStatus: () => isNonSuccessTerminalExecutionStatus,
|
|
68741
68870
|
isLocalAgentResourcePath: () => isLocalAgentResourcePath,
|
|
68871
|
+
isGitBackedAgentSourceEnabled: () => isGitBackedAgentSourceEnabled,
|
|
68742
68872
|
isFilePathDescriptor: () => isFilePathDescriptor,
|
|
68743
68873
|
isFileOutput: () => isFileOutput,
|
|
68744
68874
|
hasTriggerMethod: () => hasTriggerMethod,
|
|
@@ -68854,6 +68984,7 @@ __export(exports_src, {
|
|
|
68854
68984
|
SpanKindMap: () => SpanKindMap,
|
|
68855
68985
|
SpanKind: () => import_api.SpanKind,
|
|
68856
68986
|
SourceVersionRefSchema: () => SourceVersionRefSchema,
|
|
68987
|
+
SourceSecretsFileSchema: () => SourceSecretsFileSchema,
|
|
68857
68988
|
SourcePackageTypeSchema: () => SourcePackageTypeSchema,
|
|
68858
68989
|
SourcePackageSegmentSchema: () => SourcePackageSegmentSchema,
|
|
68859
68990
|
SourcePackagePathSchema: () => SourcePackagePathSchema,
|
|
@@ -68863,6 +68994,7 @@ __export(exports_src, {
|
|
|
68863
68994
|
SharedResourcePackageManifestSchema: () => SharedResourcePackageManifestSchema,
|
|
68864
68995
|
SharedEvaluatorReferenceSchema: () => SharedEvaluatorReferenceSchema,
|
|
68865
68996
|
SharedEvaluatorCompositionItemSchema: () => SharedEvaluatorCompositionItemSchema,
|
|
68997
|
+
SecretNameSchema: () => SecretNameSchema,
|
|
68866
68998
|
ScopeStackSchema: () => ScopeStackSchema,
|
|
68867
68999
|
ScopeEntryTypeSchema: () => ScopeEntryTypeSchema,
|
|
68868
69000
|
ScopeEntrySchema: () => ScopeEntrySchema,
|
|
@@ -68871,6 +69003,7 @@ __export(exports_src, {
|
|
|
68871
69003
|
STEP_EXECUTION_STATUSES: () => STEP_EXECUTION_STATUSES,
|
|
68872
69004
|
SOURCE_VERSION_RANGE_PATTERN: () => SOURCE_VERSION_RANGE_PATTERN,
|
|
68873
69005
|
SOURCE_SEMVER_PATTERN: () => SOURCE_SEMVER_PATTERN,
|
|
69006
|
+
SOURCE_SECRETS_FILENAME: () => SOURCE_SECRETS_FILENAME,
|
|
68874
69007
|
SOURCE_PACKAGE_SEGMENT_PATTERN: () => SOURCE_PACKAGE_SEGMENT_PATTERN,
|
|
68875
69008
|
SOURCE_MANIFEST_FILENAME: () => SOURCE_MANIFEST_FILENAME,
|
|
68876
69009
|
SOURCE_COMMIT_SHA_PATTERN: () => SOURCE_COMMIT_SHA_PATTERN,
|
|
@@ -68954,6 +69087,7 @@ __export(exports_src, {
|
|
|
68954
69087
|
EvalOverridesSchema: () => EvalOverridesSchema,
|
|
68955
69088
|
EvalExampleMetaSchema: () => EvalExampleMetaSchema,
|
|
68956
69089
|
EvalConfigYamlSchema: () => EvalConfigYamlSchema,
|
|
69090
|
+
EncryptedSecretValueSchema: () => EncryptedSecretValueSchema,
|
|
68957
69091
|
EmailTriggerMethodSchema: () => EmailTriggerMethodSchema,
|
|
68958
69092
|
Eigenpal: () => Eigenpal,
|
|
68959
69093
|
EXECUTION_TYPES: () => EXECUTION_TYPES,
|
|
@@ -69016,6 +69150,9 @@ __export(exports_src, {
|
|
|
69016
69150
|
AiParseConfigSchema: () => AiParseConfigSchema,
|
|
69017
69151
|
AiExtractOutputSchema: () => AiExtractOutputSchema,
|
|
69018
69152
|
AiExtractConfigSchema: () => AiExtractConfigSchema,
|
|
69153
|
+
AgentSourceRuntimeModeSchema: () => AgentSourceRuntimeModeSchema,
|
|
69154
|
+
AgentSourceMigrationStateSchema: () => AgentSourceMigrationStateSchema,
|
|
69155
|
+
AgentSourceCutoverSettingsSchema: () => AgentSourceCutoverSettingsSchema,
|
|
69019
69156
|
AgentPackageManifestSchema: () => AgentPackageManifestSchema,
|
|
69020
69157
|
ActionTypeValue: () => ActionTypeValue,
|
|
69021
69158
|
ActionTypeSchema: () => ActionTypeSchema,
|
|
@@ -181601,7 +181738,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
181601
181738
|
// package.json
|
|
181602
181739
|
var package_default = {
|
|
181603
181740
|
name: "@eigenpal/cli",
|
|
181604
|
-
version: "0.5.
|
|
181741
|
+
version: "0.5.10",
|
|
181605
181742
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
181606
181743
|
keywords: [
|
|
181607
181744
|
"eigenpal",
|
|
@@ -181641,6 +181778,8 @@ var package_default = {
|
|
|
181641
181778
|
scripts: {
|
|
181642
181779
|
dev: "bun src/cli.ts",
|
|
181643
181780
|
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",
|
|
181781
|
+
generate: "bun run generate:cli-docs && bun run generate:skill",
|
|
181782
|
+
"generate:check": "bun run generate:cli-docs:check && bun run generate:skill:check",
|
|
181644
181783
|
"generate:skill": "bun scripts/generate-skill-reference.ts",
|
|
181645
181784
|
"generate:skill:check": "bun scripts/generate-skill-reference.ts --check",
|
|
181646
181785
|
"generate:cli-docs": "bun scripts/generate-cli-docs.ts",
|
|
@@ -184106,7 +184245,7 @@ init_src();
|
|
|
184106
184245
|
init_zod();
|
|
184107
184246
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
184108
184247
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
184109
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
184248
|
+
import { createCipheriv, createHash as createHash2, randomBytes } from "node:crypto";
|
|
184110
184249
|
import {
|
|
184111
184250
|
existsSync as existsSync3,
|
|
184112
184251
|
mkdirSync as mkdirSync2,
|
|
@@ -184269,6 +184408,9 @@ function listRelativeFiles(dir) {
|
|
|
184269
184408
|
function readYamlFile(filePath) {
|
|
184270
184409
|
return import_yaml2.default.parse(readFileSync3(filePath, "utf8"));
|
|
184271
184410
|
}
|
|
184411
|
+
function writeYamlFile(filePath, value) {
|
|
184412
|
+
writeFileSync2(filePath, import_yaml2.default.stringify(value));
|
|
184413
|
+
}
|
|
184272
184414
|
function packageManifestExists(gitRoot, packagePath) {
|
|
184273
184415
|
return existsSync3(path2.join(gitRoot, packagePath, "eigenpal.yaml"));
|
|
184274
184416
|
}
|
|
@@ -184279,6 +184421,28 @@ function releaseTagExists(gitRoot, packagePath, version2) {
|
|
|
184279
184421
|
function readPackageManifest(packageRoot) {
|
|
184280
184422
|
return SourcePackageManifestSchema.parse(readYamlFile(path2.join(packageRoot, "eigenpal.yaml")));
|
|
184281
184423
|
}
|
|
184424
|
+
function requirePackageContext(opts = {}) {
|
|
184425
|
+
const context = resolveGitSourceContext(opts);
|
|
184426
|
+
if (!context.gitRoot || !context.packageRoot || !context.packagePath) {
|
|
184427
|
+
throw new Error("Run this command inside a source package, or pass --dir.");
|
|
184428
|
+
}
|
|
184429
|
+
return {
|
|
184430
|
+
gitRoot: context.gitRoot,
|
|
184431
|
+
packageRoot: context.packageRoot,
|
|
184432
|
+
packagePath: context.packagePath
|
|
184433
|
+
};
|
|
184434
|
+
}
|
|
184435
|
+
function readMutablePackageManifest(packageRoot) {
|
|
184436
|
+
const manifest = readYamlFile(path2.join(packageRoot, "eigenpal.yaml"));
|
|
184437
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
184438
|
+
throw new Error("Package eigenpal.yaml must be a YAML object.");
|
|
184439
|
+
}
|
|
184440
|
+
return manifest;
|
|
184441
|
+
}
|
|
184442
|
+
function writeMutablePackageManifest(packageRoot, manifest) {
|
|
184443
|
+
SourcePackageManifestSchema.parse(manifest);
|
|
184444
|
+
writeYamlFile(path2.join(packageRoot, "eigenpal.yaml"), manifest);
|
|
184445
|
+
}
|
|
184282
184446
|
function parsePackageRef(input) {
|
|
184283
184447
|
const [rawPackage, rawRef, extra] = input.split("@");
|
|
184284
184448
|
if (!rawPackage || extra !== undefined) {
|
|
@@ -185087,6 +185251,144 @@ async function sync(target, opts) {
|
|
|
185087
185251
|
}
|
|
185088
185252
|
await syncLatestAutomation(new ApiClient(resolveConfig(opts)), packagePath);
|
|
185089
185253
|
}
|
|
185254
|
+
function setEmailTriggerAlias(email3, opts) {
|
|
185255
|
+
const context = requirePackageContext(opts);
|
|
185256
|
+
const manifest = readMutablePackageManifest(context.packageRoot);
|
|
185257
|
+
const triggers = { ...manifest.triggers ?? {} };
|
|
185258
|
+
const emailConfig = { ...triggers.email ?? {} };
|
|
185259
|
+
const aliases = Array.isArray(emailConfig.aliases) ? [...emailConfig.aliases] : [];
|
|
185260
|
+
const nextAlias = {
|
|
185261
|
+
address: email3,
|
|
185262
|
+
enabled: !opts.disabled,
|
|
185263
|
+
allowlist: opts.allow ?? [],
|
|
185264
|
+
replyConfig: {
|
|
185265
|
+
...opts.replyOn ? { on: opts.replyOn } : {},
|
|
185266
|
+
...opts.replyMode ? { mode: opts.replyMode } : {}
|
|
185267
|
+
},
|
|
185268
|
+
requireSenderAuth: opts.requireSenderAuth ?? true
|
|
185269
|
+
};
|
|
185270
|
+
const filtered = aliases.filter((alias) => typeof alias === "string" ? alias.toLowerCase() !== email3.toLowerCase() : alias.address?.toLowerCase() !== email3.toLowerCase());
|
|
185271
|
+
manifest.triggers = {
|
|
185272
|
+
...triggers,
|
|
185273
|
+
email: { ...emailConfig, enabled: true, aliases: [...filtered, nextAlias] }
|
|
185274
|
+
};
|
|
185275
|
+
writeMutablePackageManifest(context.packageRoot, manifest);
|
|
185276
|
+
success2(`Updated email trigger ${ui.bold(email3)} in eigenpal.yaml.`);
|
|
185277
|
+
}
|
|
185278
|
+
function setApiTrigger(enabled, opts) {
|
|
185279
|
+
const context = requirePackageContext(opts);
|
|
185280
|
+
const manifest = readMutablePackageManifest(context.packageRoot);
|
|
185281
|
+
const triggers = { ...manifest.triggers ?? {} };
|
|
185282
|
+
manifest.triggers = { ...triggers, api: enabled };
|
|
185283
|
+
writeMutablePackageManifest(context.packageRoot, manifest);
|
|
185284
|
+
success2(`${enabled ? "Enabled" : "Disabled"} API trigger in eigenpal.yaml.`);
|
|
185285
|
+
}
|
|
185286
|
+
function encryptLocalSecret(input) {
|
|
185287
|
+
if (input.key.byteLength !== 32)
|
|
185288
|
+
throw new Error("Secret encryption key must be 32 bytes.");
|
|
185289
|
+
const nonce = randomBytes(12);
|
|
185290
|
+
const cipher = createCipheriv("aes-256-gcm", input.key, nonce);
|
|
185291
|
+
cipher.setAAD(Buffer.from(JSON.stringify({
|
|
185292
|
+
keyId: input.keyId,
|
|
185293
|
+
organizationId: input.organizationId,
|
|
185294
|
+
sourcePath: input.sourcePath,
|
|
185295
|
+
secretName: input.secretName
|
|
185296
|
+
})));
|
|
185297
|
+
const ciphertext = Buffer.concat([cipher.update(input.plaintext, "utf8"), cipher.final()]);
|
|
185298
|
+
return {
|
|
185299
|
+
algorithm: "aes-256-gcm",
|
|
185300
|
+
keyId: input.keyId,
|
|
185301
|
+
nonce: nonce.toString("base64url"),
|
|
185302
|
+
ciphertext: ciphertext.toString("base64url"),
|
|
185303
|
+
tag: cipher.getAuthTag().toString("base64url")
|
|
185304
|
+
};
|
|
185305
|
+
}
|
|
185306
|
+
function readSecretsFile(packageRoot) {
|
|
185307
|
+
const filePath = path2.join(packageRoot, SOURCE_SECRETS_FILENAME);
|
|
185308
|
+
if (!existsSync3(filePath))
|
|
185309
|
+
return { schemaVersion: 1, secrets: {} };
|
|
185310
|
+
return SourceSecretsFileSchema.parse(readYamlFile(filePath));
|
|
185311
|
+
}
|
|
185312
|
+
function writeSecretsFile(packageRoot, value) {
|
|
185313
|
+
const secretsFile = SourceSecretsFileSchema.parse(value);
|
|
185314
|
+
writeYamlFile(path2.join(packageRoot, SOURCE_SECRETS_FILENAME), secretsFile);
|
|
185315
|
+
}
|
|
185316
|
+
async function readSecretInput(opts) {
|
|
185317
|
+
const selected = [opts.stdin, opts.valueFile].filter(Boolean).length;
|
|
185318
|
+
if (selected > 1)
|
|
185319
|
+
throw new Error("Pass only one of --stdin or --value-file.");
|
|
185320
|
+
if (opts.stdin)
|
|
185321
|
+
return readFileSync3(0, "utf8").replace(/\n$/, "");
|
|
185322
|
+
if (opts.valueFile)
|
|
185323
|
+
return readFileSync3(opts.valueFile, "utf8").replace(/\n$/, "");
|
|
185324
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
185325
|
+
const { password, isCancel, cancel } = await Promise.resolve().then(() => (init_dist4(), exports_dist));
|
|
185326
|
+
const answer = await password({ message: "Secret value" });
|
|
185327
|
+
if (isCancel(answer)) {
|
|
185328
|
+
cancel("Cancelled");
|
|
185329
|
+
process.exit(1);
|
|
185330
|
+
}
|
|
185331
|
+
return String(answer);
|
|
185332
|
+
}
|
|
185333
|
+
throw new Error("Secret value input is required in noninteractive mode.");
|
|
185334
|
+
}
|
|
185335
|
+
async function setSecret(name, opts) {
|
|
185336
|
+
const context = requirePackageContext(opts);
|
|
185337
|
+
const sourcePath = `${context.packagePath}/${SOURCE_SECRETS_FILENAME}`;
|
|
185338
|
+
const secretsFile = readSecretsFile(context.packageRoot);
|
|
185339
|
+
const secrets = { ...secretsFile.secrets };
|
|
185340
|
+
secrets[name] = {
|
|
185341
|
+
...opts.description ? { description: opts.description } : {},
|
|
185342
|
+
encrypted: encryptLocalSecret({
|
|
185343
|
+
plaintext: await readSecretInput(opts),
|
|
185344
|
+
keyId: opts.keyId,
|
|
185345
|
+
key: readSecretKeyFile(opts.keyFile),
|
|
185346
|
+
organizationId: opts.organizationId,
|
|
185347
|
+
sourcePath,
|
|
185348
|
+
secretName: name
|
|
185349
|
+
})
|
|
185350
|
+
};
|
|
185351
|
+
writeSecretsFile(context.packageRoot, { schemaVersion: 1, secrets });
|
|
185352
|
+
success2(`Encrypted ${ui.bold(name)} into secrets.enc.yaml.`);
|
|
185353
|
+
}
|
|
185354
|
+
function unsetSecret(name, opts) {
|
|
185355
|
+
const context = requirePackageContext(opts);
|
|
185356
|
+
const secretsFile = readSecretsFile(context.packageRoot);
|
|
185357
|
+
const secrets = { ...secretsFile.secrets };
|
|
185358
|
+
delete secrets[name];
|
|
185359
|
+
writeSecretsFile(context.packageRoot, { schemaVersion: 1, secrets });
|
|
185360
|
+
success2(`Removed ${ui.bold(name)} from secrets.enc.yaml.`);
|
|
185361
|
+
}
|
|
185362
|
+
async function importSecrets(envFile, opts) {
|
|
185363
|
+
const content = readFileSync3(envFile, "utf8");
|
|
185364
|
+
for (const line of content.split(/\r?\n/)) {
|
|
185365
|
+
const trimmed = line.trim();
|
|
185366
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
185367
|
+
continue;
|
|
185368
|
+
const idx = trimmed.indexOf("=");
|
|
185369
|
+
if (idx === -1)
|
|
185370
|
+
continue;
|
|
185371
|
+
const context = requirePackageContext(opts);
|
|
185372
|
+
const sourcePath = `${context.packagePath}/${SOURCE_SECRETS_FILENAME}`;
|
|
185373
|
+
const secretsFile = readSecretsFile(context.packageRoot);
|
|
185374
|
+
const secrets = { ...secretsFile.secrets };
|
|
185375
|
+
const name = trimmed.slice(0, idx);
|
|
185376
|
+
secrets[name] = {
|
|
185377
|
+
encrypted: encryptLocalSecret({
|
|
185378
|
+
plaintext: trimmed.slice(idx + 1),
|
|
185379
|
+
keyId: opts.keyId,
|
|
185380
|
+
key: readSecretKeyFile(opts.keyFile),
|
|
185381
|
+
organizationId: opts.organizationId,
|
|
185382
|
+
sourcePath,
|
|
185383
|
+
secretName: name
|
|
185384
|
+
})
|
|
185385
|
+
};
|
|
185386
|
+
writeSecretsFile(context.packageRoot, { schemaVersion: 1, secrets });
|
|
185387
|
+
}
|
|
185388
|
+
}
|
|
185389
|
+
function readSecretKeyFile(keyFile) {
|
|
185390
|
+
return Buffer.from(readFileSync3(keyFile, "utf8").trim(), "base64url");
|
|
185391
|
+
}
|
|
185090
185392
|
async function install(packageRef, opts) {
|
|
185091
185393
|
const config2 = resolveConfig(opts);
|
|
185092
185394
|
const context = resolveGitSourceContext();
|
|
@@ -185390,6 +185692,16 @@ function parseReleaseVersion(value) {
|
|
|
185390
185692
|
}
|
|
185391
185693
|
return value;
|
|
185392
185694
|
}
|
|
185695
|
+
function parseBoolean(value) {
|
|
185696
|
+
if (value === "true" || value === "enabled" || value === "on")
|
|
185697
|
+
return true;
|
|
185698
|
+
if (value === "false" || value === "disabled" || value === "off")
|
|
185699
|
+
return false;
|
|
185700
|
+
throw new InvalidArgumentError("Expected true/false, enabled/disabled, or on/off");
|
|
185701
|
+
}
|
|
185702
|
+
function collect2(value, previous) {
|
|
185703
|
+
return [...previous, value];
|
|
185704
|
+
}
|
|
185393
185705
|
function parseType(value) {
|
|
185394
185706
|
if (!["agent", "workflow"].includes(value))
|
|
185395
185707
|
throw new InvalidArgumentError("type must be agent or workflow");
|
|
@@ -185420,6 +185732,14 @@ function registerGitCommands(program2) {
|
|
|
185420
185732
|
addJsonFlag(withBaseUrl(git.command("versions <package>"))).description("List package release versions.").action(action(versions2));
|
|
185421
185733
|
withBaseUrl(git.command("release")).description("Create and push a package release tag.").argument("<version>", "Version or bump level", parseReleaseVersion).argument("[dir]", "Package directory").addOption(new Option("-m, --message <message>", "Annotated tag message").makeOptionMandatory()).action(action(async (versionOrBump, dir, opts) => release(versionOrBump, dir, opts)));
|
|
185422
185734
|
withBaseUrl(git.command("sync [automation]")).description("Sync an automation from the latest Git source release.").option("--dir <dir>", "Directory to inspect").action(action(sync));
|
|
185735
|
+
const trigger = git.command("trigger").description("Edit trigger policy in eigenpal.yaml.");
|
|
185736
|
+
trigger.command("api <enabled>").description("Enable or disable API trigger policy in eigenpal.yaml.").option("--dir <dir>", "Directory to inspect").action(action(async (enabled, opts) => setApiTrigger(parseBoolean(enabled), opts)));
|
|
185737
|
+
const triggerEmail = trigger.command("email").description("Edit email trigger policy.");
|
|
185738
|
+
triggerEmail.command("set <email>").description("Add or update an email trigger alias in eigenpal.yaml.").option("--dir <dir>", "Directory to inspect").option("--disabled", "Write the alias as disabled").option("--allow <entry>", "Allowed sender entry; repeatable", collect2, []).option("--reply-on <never|always>", "Reply behavior").option("--reply-mode <sender|all>", "Reply recipient mode").option("--no-require-sender-auth", "Disable sender-auth checks for this alias").action(action(async (email3, opts) => setEmailTriggerAlias(email3, opts)));
|
|
185739
|
+
const secret = git.command("secret").description("Edit encrypted secrets.enc.yaml.");
|
|
185740
|
+
secret.command("set <name>").description("Encrypt and set a secret value in secrets.enc.yaml.").option("--dir <dir>", "Directory to inspect").option("--stdin", "Read the secret value from stdin").option("--value-file <path>", "Read the secret value from a file").requiredOption("--key-id <id>", "Organization decrypt key id").requiredOption("--key-file <path>", "File containing the base64url-encoded organization key").requiredOption("--organization-id <id>", "Organization id for authenticated data").option("--description <text>", "Secret description").action(action(setSecret));
|
|
185741
|
+
secret.command("unset <name>").description("Remove a secret from secrets.enc.yaml.").option("--dir <dir>", "Directory to inspect").action(action(async (name, opts) => unsetSecret(name, opts)));
|
|
185742
|
+
secret.command("import <env-file>").description("Import KEY=value entries from an env file into secrets.enc.yaml.").option("--dir <dir>", "Directory to inspect").requiredOption("--key-id <id>", "Organization decrypt key id").requiredOption("--key-file <path>", "File containing the base64url-encoded organization key").requiredOption("--organization-id <id>", "Organization id for authenticated data").action(action(importSecrets));
|
|
185423
185743
|
}
|
|
185424
185744
|
|
|
185425
185745
|
// src/commands/init.ts
|
|
@@ -186018,15 +186338,15 @@ function pruneEmptyDirs(root) {
|
|
|
186018
186338
|
if (!existsSync5(root))
|
|
186019
186339
|
return;
|
|
186020
186340
|
const stack = [];
|
|
186021
|
-
const
|
|
186341
|
+
const collect3 = (dir) => {
|
|
186022
186342
|
for (const entry of readdirSync3(dir)) {
|
|
186023
186343
|
const full = join3(dir, entry);
|
|
186024
186344
|
if (lstatSync(full).isDirectory())
|
|
186025
|
-
|
|
186345
|
+
collect3(full);
|
|
186026
186346
|
}
|
|
186027
186347
|
stack.push(dir);
|
|
186028
186348
|
};
|
|
186029
|
-
|
|
186349
|
+
collect3(root);
|
|
186030
186350
|
for (const dir of stack) {
|
|
186031
186351
|
try {
|
|
186032
186352
|
if (readdirSync3(dir).length === 0)
|
|
@@ -188043,6 +188363,155 @@ function parseWorkflow(yaml) {
|
|
|
188043
188363
|
}
|
|
188044
188364
|
return result.data;
|
|
188045
188365
|
}
|
|
188366
|
+
// ../workflow-yaml/src/validate-quality.ts
|
|
188367
|
+
init_src();
|
|
188368
|
+
var CATEGORICAL_NAMES = new Set([
|
|
188369
|
+
"category",
|
|
188370
|
+
"subcategory",
|
|
188371
|
+
"kind",
|
|
188372
|
+
"status",
|
|
188373
|
+
"type",
|
|
188374
|
+
"subtype",
|
|
188375
|
+
"level",
|
|
188376
|
+
"severity",
|
|
188377
|
+
"priority",
|
|
188378
|
+
"state",
|
|
188379
|
+
"stage",
|
|
188380
|
+
"tier",
|
|
188381
|
+
"classification",
|
|
188382
|
+
"monitoring",
|
|
188383
|
+
"frequency",
|
|
188384
|
+
"reference",
|
|
188385
|
+
"sectionname",
|
|
188386
|
+
"code",
|
|
188387
|
+
"flag",
|
|
188388
|
+
"signal",
|
|
188389
|
+
"mode",
|
|
188390
|
+
"phase"
|
|
188391
|
+
]);
|
|
188392
|
+
var SEPARATOR_LIST_RE = /(?:^|[\s(])((?:[A-Za-z0-9_-]{1,20})(?:\s*[|,/]\s*[A-Za-z0-9_-]{1,20}){1,7})(?:$|[\s).])/;
|
|
188393
|
+
function validateSchemaQuality(workflow2) {
|
|
188394
|
+
const warnings = [];
|
|
188395
|
+
for (let i5 = 0;i5 < workflow2.steps.length; i5++) {
|
|
188396
|
+
const step = workflow2.steps[i5];
|
|
188397
|
+
if (!step)
|
|
188398
|
+
continue;
|
|
188399
|
+
const basePath = `steps.${i5}`;
|
|
188400
|
+
walkStep(step, basePath, warnings);
|
|
188401
|
+
}
|
|
188402
|
+
return warnings;
|
|
188403
|
+
}
|
|
188404
|
+
function walkStep(step, basePath, out) {
|
|
188405
|
+
if (step.type === "ai.extract") {
|
|
188406
|
+
const schema = step.with?.schema;
|
|
188407
|
+
if (schema && typeof schema === "object") {
|
|
188408
|
+
walkSchema(schema, `${basePath}.with.schema`, out);
|
|
188409
|
+
}
|
|
188410
|
+
return;
|
|
188411
|
+
}
|
|
188412
|
+
if (step.type === "transform.script") {
|
|
188413
|
+
const cfg = step.with;
|
|
188414
|
+
const source2 = typeof cfg?.function === "string" ? cfg.function : null;
|
|
188415
|
+
if (!source2)
|
|
188416
|
+
return;
|
|
188417
|
+
const paramNames = cfg?.inputs && typeof cfg.inputs === "object" && !Array.isArray(cfg.inputs) ? Object.keys(cfg.inputs) : [];
|
|
188418
|
+
const compiled = compileTypedScript({ kind: "transform", source: source2, paramNames });
|
|
188419
|
+
if (!compiled.ok)
|
|
188420
|
+
return;
|
|
188421
|
+
for (const w10 of compiled.warnings) {
|
|
188422
|
+
if (w10.code === "weak-return-type") {
|
|
188423
|
+
out.push({
|
|
188424
|
+
code: "weak-script-return",
|
|
188425
|
+
field: `${basePath}.with.function`,
|
|
188426
|
+
message: `transform.script return type is too loose: ${w10.message}`,
|
|
188427
|
+
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.'
|
|
188428
|
+
});
|
|
188429
|
+
}
|
|
188430
|
+
}
|
|
188431
|
+
const schema = compiled.result.returnSchema;
|
|
188432
|
+
if (schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188433
|
+
out.push({
|
|
188434
|
+
code: "untyped-object",
|
|
188435
|
+
field: `${basePath}.with.function`,
|
|
188436
|
+
message: "transform.script returns an untyped object. Downstream steps cannot autocomplete its fields.",
|
|
188437
|
+
hint: "Describe the actual shape in the return annotation: `function script(...): { name: string; total: number }`."
|
|
188438
|
+
});
|
|
188439
|
+
}
|
|
188440
|
+
return;
|
|
188441
|
+
}
|
|
188442
|
+
const withAny = step.with;
|
|
188443
|
+
const nestedSteps = withAny?.steps;
|
|
188444
|
+
if (Array.isArray(nestedSteps)) {
|
|
188445
|
+
for (let i5 = 0;i5 < nestedSteps.length; i5++) {
|
|
188446
|
+
const s5 = nestedSteps[i5];
|
|
188447
|
+
if (s5)
|
|
188448
|
+
walkStep(s5, `${basePath}.with.steps.${i5}`, out);
|
|
188449
|
+
}
|
|
188450
|
+
}
|
|
188451
|
+
const nestedBranches = withAny?.branches;
|
|
188452
|
+
if (Array.isArray(nestedBranches)) {
|
|
188453
|
+
for (let b8 = 0;b8 < nestedBranches.length; b8++) {
|
|
188454
|
+
const branch = nestedBranches[b8];
|
|
188455
|
+
if (branch && Array.isArray(branch.steps)) {
|
|
188456
|
+
for (let i5 = 0;i5 < branch.steps.length; i5++) {
|
|
188457
|
+
const s5 = branch.steps[i5];
|
|
188458
|
+
if (s5)
|
|
188459
|
+
walkStep(s5, `${basePath}.with.branches.${b8}.steps.${i5}`, out);
|
|
188460
|
+
}
|
|
188461
|
+
}
|
|
188462
|
+
}
|
|
188463
|
+
}
|
|
188464
|
+
}
|
|
188465
|
+
function walkSchema(schema, basePath, out) {
|
|
188466
|
+
const type = schema.type;
|
|
188467
|
+
if (type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188468
|
+
out.push({
|
|
188469
|
+
code: "untyped-object",
|
|
188470
|
+
field: basePath,
|
|
188471
|
+
message: "Object schema has no properties. The LLM has no guidance on what fields to emit and downstream autocomplete is empty.",
|
|
188472
|
+
hint: "Add concrete fields under `properties`, or set `additionalProperties: false` if the object is intentionally open."
|
|
188473
|
+
});
|
|
188474
|
+
}
|
|
188475
|
+
if (type === "array" && (schema.items === undefined || isEmptyObject(schema.items))) {
|
|
188476
|
+
out.push({
|
|
188477
|
+
code: "untyped-array-items",
|
|
188478
|
+
field: basePath,
|
|
188479
|
+
message: "Array items are untyped. The LLM does not know what shape each element should take.",
|
|
188480
|
+
hint: "Add an `items:` block describing the element type, e.g. `items: { type: object, properties: { ... } }`."
|
|
188481
|
+
});
|
|
188482
|
+
}
|
|
188483
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
188484
|
+
for (const [name, raw] of Object.entries(schema.properties)) {
|
|
188485
|
+
if (!raw || typeof raw !== "object")
|
|
188486
|
+
continue;
|
|
188487
|
+
const prop = raw;
|
|
188488
|
+
const propType = prop.type;
|
|
188489
|
+
const propEnum = Array.isArray(prop.enum) && prop.enum.length > 0;
|
|
188490
|
+
const lower = name.toLowerCase();
|
|
188491
|
+
const description = typeof prop.description === "string" ? prop.description : "";
|
|
188492
|
+
const looksCategoricalByName = CATEGORICAL_NAMES.has(lower);
|
|
188493
|
+
const looksCategoricalByDescription = description.length > 0 && SEPARATOR_LIST_RE.test(description);
|
|
188494
|
+
if (propType === "string" && !propEnum && (looksCategoricalByName || looksCategoricalByDescription) && !looksLikeFreeform(prop)) {
|
|
188495
|
+
out.push({
|
|
188496
|
+
code: "categorical-missing-enum",
|
|
188497
|
+
field: `${basePath}.properties.${name}`,
|
|
188498
|
+
message: `Field \`${name}\` looks categorical but has no \`enum\`. Coded values extract more reliably when the LLM is constrained to a closed set.`,
|
|
188499
|
+
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`."
|
|
188500
|
+
});
|
|
188501
|
+
}
|
|
188502
|
+
walkSchema(prop, `${basePath}.properties.${name}`, out);
|
|
188503
|
+
}
|
|
188504
|
+
}
|
|
188505
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
188506
|
+
walkSchema(schema.items, `${basePath}.items`, out);
|
|
188507
|
+
}
|
|
188508
|
+
}
|
|
188509
|
+
function isEmptyObject(v11) {
|
|
188510
|
+
return typeof v11 === "object" && v11 !== null && !Array.isArray(v11) && Object.keys(v11).length === 0;
|
|
188511
|
+
}
|
|
188512
|
+
function looksLikeFreeform(prop) {
|
|
188513
|
+
return prop.pattern !== undefined || prop.format !== undefined;
|
|
188514
|
+
}
|
|
188046
188515
|
// src/commands/validate.ts
|
|
188047
188516
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
188048
188517
|
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
@@ -188061,24 +188530,46 @@ function isDirSafe(path15, issues, field) {
|
|
|
188061
188530
|
}
|
|
188062
188531
|
function validateWorkflowYaml(path15) {
|
|
188063
188532
|
if (!existsSync10(path15)) {
|
|
188064
|
-
return [{ field: path15, message: "File not found." }];
|
|
188533
|
+
return { issues: [{ field: path15, message: "File not found." }], warnings: [] };
|
|
188065
188534
|
}
|
|
188066
188535
|
const text = readFileSync10(path15, "utf-8");
|
|
188067
188536
|
try {
|
|
188068
|
-
parseWorkflow(text);
|
|
188069
|
-
|
|
188537
|
+
const definition = parseWorkflow(text);
|
|
188538
|
+
let warnings = [];
|
|
188539
|
+
try {
|
|
188540
|
+
warnings = validateSchemaQuality(definition);
|
|
188541
|
+
} catch {
|
|
188542
|
+
warnings = [];
|
|
188543
|
+
}
|
|
188544
|
+
return { issues: [], warnings };
|
|
188070
188545
|
} catch (err2) {
|
|
188071
188546
|
if (err2 instanceof YamlParseError) {
|
|
188072
188547
|
const where = err2.line !== undefined ? ` (line ${err2.line})` : "";
|
|
188073
|
-
return [{ field: "yaml", message: `${err2.message}${where}` }];
|
|
188548
|
+
return { issues: [{ field: "yaml", message: `${err2.message}${where}` }], warnings: [] };
|
|
188074
188549
|
}
|
|
188075
188550
|
if (err2 instanceof WorkflowValidationError) {
|
|
188076
|
-
return
|
|
188077
|
-
|
|
188078
|
-
|
|
188079
|
-
|
|
188551
|
+
return {
|
|
188552
|
+
issues: err2.errors.map((e8) => ({
|
|
188553
|
+
field: e8.path.length > 0 ? e8.path.map(String).join(".") : "(root)",
|
|
188554
|
+
message: e8.message
|
|
188555
|
+
})),
|
|
188556
|
+
warnings: []
|
|
188557
|
+
};
|
|
188080
188558
|
}
|
|
188081
|
-
return
|
|
188559
|
+
return {
|
|
188560
|
+
issues: [{ field: "(root)", message: err2 instanceof Error ? err2.message : String(err2) }],
|
|
188561
|
+
warnings: []
|
|
188562
|
+
};
|
|
188563
|
+
}
|
|
188564
|
+
}
|
|
188565
|
+
function printSchemaQualityWarnings(path15, warnings) {
|
|
188566
|
+
if (warnings.length === 0)
|
|
188567
|
+
return;
|
|
188568
|
+
warn(`workflow.yaml ${ui.dim(`(${path15})`)}: ${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"}`);
|
|
188569
|
+
for (const w10 of warnings) {
|
|
188570
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
188571
|
+
if (w10.hint)
|
|
188572
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
188082
188573
|
}
|
|
188083
188574
|
}
|
|
188084
188575
|
function validateEvaluatorsYaml(path15) {
|
|
@@ -188261,9 +188752,23 @@ function rootDir(opts) {
|
|
|
188261
188752
|
return resolve6(opts.dir ?? process.cwd());
|
|
188262
188753
|
}
|
|
188263
188754
|
function registerAllValidateCommand(parent) {
|
|
188264
|
-
parent.command("validate").description("Local-only validation
|
|
188755
|
+
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) => {
|
|
188756
|
+
if (path15) {
|
|
188757
|
+
const target = resolve6(path15);
|
|
188758
|
+
const wfResult2 = validateWorkflowYaml(target);
|
|
188759
|
+
const wfOk2 = printIssues("workflow.yaml", target, wfResult2.issues);
|
|
188760
|
+
if (wfOk2)
|
|
188761
|
+
printSchemaQualityWarnings(target, wfResult2.warnings);
|
|
188762
|
+
if (!wfOk2)
|
|
188763
|
+
process.exit(1);
|
|
188764
|
+
return;
|
|
188765
|
+
}
|
|
188265
188766
|
const root2 = rootDir(opts);
|
|
188266
|
-
const
|
|
188767
|
+
const wfResult = validateWorkflowYaml(join12(root2, "workflow.yaml"));
|
|
188768
|
+
const wfPath = relPath(root2, join12(root2, "workflow.yaml"));
|
|
188769
|
+
const wfOk = printIssues("workflow.yaml", wfPath, wfResult.issues);
|
|
188770
|
+
if (wfOk)
|
|
188771
|
+
printSchemaQualityWarnings(wfPath, wfResult.warnings);
|
|
188267
188772
|
const evOk = printIssues("evaluators.yaml", relPath(root2, join12(root2, "evaluators.yaml")), validateEvaluatorsYaml(join12(root2, "evaluators.yaml")));
|
|
188268
188773
|
const dsOk = printIssues("dataset/", relPath(root2, join12(root2, "dataset")), validateDatasetFolder(join12(root2, "dataset")));
|
|
188269
188774
|
if (!wfOk || !evOk || !dsOk)
|
|
@@ -188916,6 +189421,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188916
189421
|
return;
|
|
188917
189422
|
}
|
|
188918
189423
|
success2(`Pushed ${ui.bold(workflowId)} (v${result.version ?? nextVersion})`);
|
|
189424
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188919
189425
|
} else {
|
|
188920
189426
|
if (opts.bump) {
|
|
188921
189427
|
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.");
|
|
@@ -188933,6 +189439,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188933
189439
|
const id2 = result.workflow?.id ?? result.id ?? "?";
|
|
188934
189440
|
const ver = result.version?.version ?? result.workflow?.currentVersion?.version ?? result.currentVersion?.version ?? "?";
|
|
188935
189441
|
success2(`Created ${ui.bold(id2)} (v${ver})`);
|
|
189442
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188936
189443
|
}
|
|
188937
189444
|
}));
|
|
188938
189445
|
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)");
|
|
@@ -188950,6 +189457,16 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188950
189457
|
}));
|
|
188951
189458
|
}
|
|
188952
189459
|
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
189460
|
+
function printSchemaQualityWarnings2(warnings) {
|
|
189461
|
+
if (!warnings || warnings.length === 0)
|
|
189462
|
+
return;
|
|
189463
|
+
warn(`${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"} (push succeeded; sharper types make downstream steps easier to wire):`);
|
|
189464
|
+
for (const w10 of warnings) {
|
|
189465
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
189466
|
+
if (w10.hint)
|
|
189467
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
189468
|
+
}
|
|
189469
|
+
}
|
|
188953
189470
|
function isBumpLevel(value) {
|
|
188954
189471
|
return value === "patch" || value === "minor" || value === "major";
|
|
188955
189472
|
}
|
|
@@ -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.10",
|
|
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",
|