vargai 0.3.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/.claude/settings.local.json +7 -0
- package/.env.example +27 -0
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +6 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +6 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CLAUDE.md +125 -0
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +78 -0
- package/SKILLS.md +173 -0
- package/STRUCTURE.md +92 -0
- package/biome.json +34 -0
- package/bun.lock +1254 -0
- package/commitlint.config.js +22 -0
- package/docs/plan.md +66 -0
- package/docs/todo.md +14 -0
- package/docs/varg-sdk.md +812 -0
- package/ffmpeg/CLAUDE.md +68 -0
- package/package.json +69 -0
- package/pipeline/cookbooks/SKILL.md +285 -0
- package/pipeline/cookbooks/remotion-video.md +585 -0
- package/pipeline/cookbooks/round-video-character.md +337 -0
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/talking-character.md +59 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/pipeline/cookbooks/trendwatching.md +156 -0
- package/plan.md +281 -0
- package/scripts/.gitkeep +0 -0
- package/src/ai-sdk/cache.ts +142 -0
- package/src/ai-sdk/examples/cached-generation.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
- package/src/ai-sdk/examples/duet-video.ts +56 -0
- package/src/ai-sdk/examples/editly-composition.ts +63 -0
- package/src/ai-sdk/examples/editly-test.ts +57 -0
- package/src/ai-sdk/examples/editly-video-test.ts +52 -0
- package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
- package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
- package/src/ai-sdk/examples/music-generation.ts +19 -0
- package/src/ai-sdk/examples/openai-sora.ts +34 -0
- package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
- package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
- package/src/ai-sdk/examples/talking-lion.ts +55 -0
- package/src/ai-sdk/examples/video-generation.ts +39 -0
- package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
- package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
- package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
- package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
- package/src/ai-sdk/file-cache.ts +112 -0
- package/src/ai-sdk/file.ts +238 -0
- package/src/ai-sdk/generate-element.ts +92 -0
- package/src/ai-sdk/generate-music.ts +46 -0
- package/src/ai-sdk/generate-video.ts +165 -0
- package/src/ai-sdk/index.ts +72 -0
- package/src/ai-sdk/music-model.ts +110 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
- package/src/ai-sdk/providers/editly/index.ts +817 -0
- package/src/ai-sdk/providers/editly/layers.ts +772 -0
- package/src/ai-sdk/providers/editly/plan.md +144 -0
- package/src/ai-sdk/providers/editly/types.ts +328 -0
- package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
- package/src/ai-sdk/providers/fal-provider.ts +512 -0
- package/src/ai-sdk/providers/higgsfield.ts +379 -0
- package/src/ai-sdk/providers/openai.ts +251 -0
- package/src/ai-sdk/providers/replicate.ts +16 -0
- package/src/ai-sdk/video-model.ts +185 -0
- package/src/cli/commands/find.tsx +137 -0
- package/src/cli/commands/help.tsx +85 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/list.tsx +238 -0
- package/src/cli/commands/run.tsx +511 -0
- package/src/cli/commands/which.tsx +253 -0
- package/src/cli/index.ts +112 -0
- package/src/cli/quiet.ts +44 -0
- package/src/cli/types.ts +32 -0
- package/src/cli/ui/components/Badge.tsx +29 -0
- package/src/cli/ui/components/DataTable.tsx +51 -0
- package/src/cli/ui/components/Header.tsx +23 -0
- package/src/cli/ui/components/HelpBlock.tsx +44 -0
- package/src/cli/ui/components/KeyValue.tsx +33 -0
- package/src/cli/ui/components/OptionRow.tsx +81 -0
- package/src/cli/ui/components/Separator.tsx +23 -0
- package/src/cli/ui/components/StatusBox.tsx +108 -0
- package/src/cli/ui/components/VargBox.tsx +51 -0
- package/src/cli/ui/components/VargProgress.tsx +36 -0
- package/src/cli/ui/components/VargSpinner.tsx +34 -0
- package/src/cli/ui/components/VargText.tsx +56 -0
- package/src/cli/ui/components/index.ts +19 -0
- package/src/cli/ui/index.ts +12 -0
- package/src/cli/ui/render.ts +35 -0
- package/src/cli/ui/theme.ts +63 -0
- package/src/cli/utils.ts +78 -0
- package/src/core/executor/executor.ts +201 -0
- package/src/core/executor/index.ts +13 -0
- package/src/core/executor/job.ts +214 -0
- package/src/core/executor/pipeline.ts +222 -0
- package/src/core/index.ts +11 -0
- package/src/core/registry/index.ts +9 -0
- package/src/core/registry/loader.ts +149 -0
- package/src/core/registry/registry.ts +221 -0
- package/src/core/registry/resolver.ts +206 -0
- package/src/core/schema/helpers.ts +134 -0
- package/src/core/schema/index.ts +8 -0
- package/src/core/schema/shared.ts +102 -0
- package/src/core/schema/types.ts +279 -0
- package/src/core/schema/validator.ts +92 -0
- package/src/definitions/actions/captions.ts +261 -0
- package/src/definitions/actions/edit.ts +298 -0
- package/src/definitions/actions/image.ts +125 -0
- package/src/definitions/actions/index.ts +114 -0
- package/src/definitions/actions/music.ts +205 -0
- package/src/definitions/actions/sync.ts +128 -0
- package/src/definitions/actions/transcribe.ts +200 -0
- package/src/definitions/actions/upload.ts +111 -0
- package/src/definitions/actions/video.ts +163 -0
- package/src/definitions/actions/voice.ts +119 -0
- package/src/definitions/index.ts +23 -0
- package/src/definitions/models/elevenlabs.ts +50 -0
- package/src/definitions/models/flux.ts +56 -0
- package/src/definitions/models/index.ts +36 -0
- package/src/definitions/models/kling.ts +56 -0
- package/src/definitions/models/llama.ts +54 -0
- package/src/definitions/models/nano-banana-pro.ts +102 -0
- package/src/definitions/models/sonauto.ts +68 -0
- package/src/definitions/models/soul.ts +65 -0
- package/src/definitions/models/wan.ts +54 -0
- package/src/definitions/models/whisper.ts +44 -0
- package/src/definitions/skills/index.ts +12 -0
- package/src/definitions/skills/talking-character.ts +87 -0
- package/src/definitions/skills/text-to-tiktok.ts +97 -0
- package/src/index.ts +118 -0
- package/src/providers/apify.ts +269 -0
- package/src/providers/base.ts +264 -0
- package/src/providers/elevenlabs.ts +217 -0
- package/src/providers/fal.ts +392 -0
- package/src/providers/ffmpeg.ts +544 -0
- package/src/providers/fireworks.ts +193 -0
- package/src/providers/groq.ts +149 -0
- package/src/providers/higgsfield.ts +145 -0
- package/src/providers/index.ts +143 -0
- package/src/providers/replicate.ts +147 -0
- package/src/providers/storage.ts +206 -0
- package/src/tests/all.test.ts +509 -0
- package/src/tests/index.ts +33 -0
- package/src/tests/unit.test.ts +403 -0
- package/tsconfig.json +45 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Definition Loader
|
|
3
|
+
* Auto-loads definitions from the definitions/ directory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdir } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import type { Definition } from "../schema/types";
|
|
9
|
+
import { registry } from "./registry";
|
|
10
|
+
|
|
11
|
+
const DEFINITIONS_DIR = join(import.meta.dir, "..", "..", "definitions");
|
|
12
|
+
|
|
13
|
+
export interface LoaderOptions {
|
|
14
|
+
/** Additional paths to scan for definitions */
|
|
15
|
+
additionalPaths?: string[];
|
|
16
|
+
/** If true, log loading progress */
|
|
17
|
+
verbose?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load all definitions from the definitions directory
|
|
22
|
+
*/
|
|
23
|
+
export async function loadDefinitions(options?: LoaderOptions): Promise<void> {
|
|
24
|
+
const { verbose = false } = options ?? {};
|
|
25
|
+
|
|
26
|
+
// Load built-in definitions
|
|
27
|
+
await loadFromDirectory(join(DEFINITIONS_DIR, "models"), verbose);
|
|
28
|
+
await loadFromDirectory(join(DEFINITIONS_DIR, "actions"), verbose);
|
|
29
|
+
await loadFromDirectory(join(DEFINITIONS_DIR, "skills"), verbose);
|
|
30
|
+
|
|
31
|
+
// Load from additional paths
|
|
32
|
+
if (options?.additionalPaths) {
|
|
33
|
+
for (const path of options.additionalPaths) {
|
|
34
|
+
await loadFromDirectory(path, verbose);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (verbose) {
|
|
39
|
+
console.log(`[loader] loaded: ${JSON.stringify(registry.stats)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load definitions from a directory
|
|
45
|
+
*/
|
|
46
|
+
async function loadFromDirectory(
|
|
47
|
+
dirPath: string,
|
|
48
|
+
verbose: boolean,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
52
|
+
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
// Skip non-TypeScript files
|
|
55
|
+
if (!entry.isFile() || !entry.name.endsWith(".ts")) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Skip index files
|
|
60
|
+
if (entry.name === "index.ts") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const modulePath = join(dirPath, entry.name);
|
|
66
|
+
const mod = await import(modulePath);
|
|
67
|
+
|
|
68
|
+
// Check for definition export (named 'definition' or default)
|
|
69
|
+
const definition: Definition | undefined =
|
|
70
|
+
mod.definition ?? mod.default;
|
|
71
|
+
|
|
72
|
+
if (definition && isValidDefinition(definition)) {
|
|
73
|
+
registry.register(definition);
|
|
74
|
+
if (verbose) {
|
|
75
|
+
console.log(
|
|
76
|
+
`[loader] loaded: ${definition.name} (${definition.type})`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Also check for meta export (backward compatibility with action format)
|
|
82
|
+
if (mod.meta && isValidDefinition(mod.meta)) {
|
|
83
|
+
registry.register(mod.meta);
|
|
84
|
+
if (verbose) {
|
|
85
|
+
console.log(`[loader] loaded: ${mod.meta.name} (${mod.meta.type})`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (verbose) {
|
|
90
|
+
console.warn(
|
|
91
|
+
`[loader] failed to load ${entry.name}:`,
|
|
92
|
+
error instanceof Error ? error.message : error,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Directory doesn't exist - that's okay
|
|
99
|
+
if (verbose && (error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
100
|
+
console.warn(`[loader] error reading ${dirPath}:`, error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if an object is a valid definition
|
|
107
|
+
*/
|
|
108
|
+
function isValidDefinition(obj: unknown): obj is Definition {
|
|
109
|
+
if (!obj || typeof obj !== "object") return false;
|
|
110
|
+
|
|
111
|
+
const def = obj as Record<string, unknown>;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
typeof def.type === "string" &&
|
|
115
|
+
["model", "action", "skill"].includes(def.type) &&
|
|
116
|
+
typeof def.name === "string" &&
|
|
117
|
+
typeof def.description === "string" &&
|
|
118
|
+
typeof def.schema === "object"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load user skills from ~/.varg/skills/
|
|
124
|
+
*/
|
|
125
|
+
export async function loadUserSkills(verbose = false): Promise<void> {
|
|
126
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
127
|
+
const userSkillsDir = join(homeDir, ".varg", "skills");
|
|
128
|
+
|
|
129
|
+
await loadFromDirectory(userSkillsDir, verbose);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reload all definitions (useful for hot-reloading)
|
|
134
|
+
*/
|
|
135
|
+
export async function reloadDefinitions(
|
|
136
|
+
options?: LoaderOptions,
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
// Clear existing definitions (but keep providers)
|
|
139
|
+
const stats = registry.stats;
|
|
140
|
+
|
|
141
|
+
if (options?.verbose) {
|
|
142
|
+
console.log(
|
|
143
|
+
`[loader] clearing ${stats.models + stats.actions + stats.skills} definitions...`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Re-load everything
|
|
148
|
+
await loadDefinitions(options);
|
|
149
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central Registry
|
|
3
|
+
* The brain that knows about all models, actions, skills, and providers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getCliSchemaInfo, toJsonSchema } from "../schema/helpers";
|
|
7
|
+
import type {
|
|
8
|
+
ActionDefinition,
|
|
9
|
+
Definition,
|
|
10
|
+
ModelDefinition,
|
|
11
|
+
Provider,
|
|
12
|
+
SearchOptions,
|
|
13
|
+
SkillDefinition,
|
|
14
|
+
} from "../schema/types";
|
|
15
|
+
|
|
16
|
+
export class Registry {
|
|
17
|
+
private models = new Map<string, ModelDefinition>();
|
|
18
|
+
private actions = new Map<string, ActionDefinition>();
|
|
19
|
+
private skills = new Map<string, SkillDefinition>();
|
|
20
|
+
private providers = new Map<string, Provider>();
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Registration
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a definition (model, action, or skill)
|
|
28
|
+
*/
|
|
29
|
+
register(definition: Definition): void {
|
|
30
|
+
switch (definition.type) {
|
|
31
|
+
case "model":
|
|
32
|
+
this.models.set(definition.name, definition);
|
|
33
|
+
break;
|
|
34
|
+
case "action":
|
|
35
|
+
this.actions.set(definition.name, definition);
|
|
36
|
+
break;
|
|
37
|
+
case "skill":
|
|
38
|
+
this.skills.set(definition.name, definition);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register a provider
|
|
45
|
+
*/
|
|
46
|
+
registerProvider(provider: Provider): void {
|
|
47
|
+
this.providers.set(provider.name, provider);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Unregister a definition by name
|
|
52
|
+
*/
|
|
53
|
+
unregister(name: string): boolean {
|
|
54
|
+
return (
|
|
55
|
+
this.models.delete(name) ||
|
|
56
|
+
this.actions.delete(name) ||
|
|
57
|
+
this.skills.delete(name)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Resolution
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a name to its definition
|
|
67
|
+
* Resolution order: models -> actions -> skills
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
resolve(name: string): Definition | null {
|
|
71
|
+
// Handle explicit namespace prefixes
|
|
72
|
+
if (name.startsWith("model/")) {
|
|
73
|
+
return this.models.get(name.slice(6)) ?? null;
|
|
74
|
+
}
|
|
75
|
+
if (name.startsWith("action/")) {
|
|
76
|
+
return this.actions.get(name.slice(7)) ?? null;
|
|
77
|
+
}
|
|
78
|
+
if (name.startsWith("skill/")) {
|
|
79
|
+
return this.skills.get(name.slice(6)) ?? null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Try each type in order
|
|
83
|
+
return (
|
|
84
|
+
this.models.get(name) ??
|
|
85
|
+
this.actions.get(name) ??
|
|
86
|
+
this.skills.get(name) ??
|
|
87
|
+
null
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a provider by name
|
|
93
|
+
*/
|
|
94
|
+
getProvider(name: string): Provider | undefined {
|
|
95
|
+
return this.providers.get(name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if a name exists
|
|
100
|
+
*/
|
|
101
|
+
has(name: string): boolean {
|
|
102
|
+
return this.resolve(name) !== null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Search
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Search definitions by query string
|
|
111
|
+
*/
|
|
112
|
+
search(query: string, options?: SearchOptions): Definition[] {
|
|
113
|
+
const q = query.toLowerCase();
|
|
114
|
+
const results: Definition[] = [];
|
|
115
|
+
|
|
116
|
+
// Get all definitions based on type filter
|
|
117
|
+
const definitions = this.list(options?.type);
|
|
118
|
+
|
|
119
|
+
for (const def of definitions) {
|
|
120
|
+
// Match by name or description
|
|
121
|
+
if (
|
|
122
|
+
def.name.toLowerCase().includes(q) ||
|
|
123
|
+
def.description.toLowerCase().includes(q)
|
|
124
|
+
) {
|
|
125
|
+
results.push(def);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Match by input/output type (from schema)
|
|
130
|
+
const inputSchema = getCliSchemaInfo(def.schema.input);
|
|
131
|
+
const outputSchema = toJsonSchema(def.schema.output);
|
|
132
|
+
const inputType = inputSchema.properties?.type?.type ?? "";
|
|
133
|
+
const outputType = outputSchema.type ?? "";
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
options?.inputType &&
|
|
137
|
+
inputType.toLowerCase().includes(options.inputType.toLowerCase())
|
|
138
|
+
) {
|
|
139
|
+
results.push(def);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
options?.outputType &&
|
|
145
|
+
outputType.toLowerCase().includes(options.outputType.toLowerCase())
|
|
146
|
+
) {
|
|
147
|
+
results.push(def);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Match by provider (for models)
|
|
152
|
+
if (
|
|
153
|
+
options?.provider &&
|
|
154
|
+
def.type === "model" &&
|
|
155
|
+
(def as ModelDefinition).providers.includes(options.provider)
|
|
156
|
+
) {
|
|
157
|
+
results.push(def);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* List all definitions, optionally filtered by type
|
|
166
|
+
*/
|
|
167
|
+
list(type?: "model" | "action" | "skill"): Definition[] {
|
|
168
|
+
const results: Definition[] = [];
|
|
169
|
+
|
|
170
|
+
if (!type || type === "model") {
|
|
171
|
+
results.push(...this.models.values());
|
|
172
|
+
}
|
|
173
|
+
if (!type || type === "action") {
|
|
174
|
+
results.push(...this.actions.values());
|
|
175
|
+
}
|
|
176
|
+
if (!type || type === "skill") {
|
|
177
|
+
results.push(...this.skills.values());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* List all providers
|
|
185
|
+
*/
|
|
186
|
+
listProviders(): Provider[] {
|
|
187
|
+
return Array.from(this.providers.values());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Getters
|
|
192
|
+
// ============================================================================
|
|
193
|
+
|
|
194
|
+
getModel(name: string): ModelDefinition | undefined {
|
|
195
|
+
return this.models.get(name);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getAction(name: string): ActionDefinition | undefined {
|
|
199
|
+
return this.actions.get(name);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getSkill(name: string): SkillDefinition | undefined {
|
|
203
|
+
return this.skills.get(name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// Stats
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
get stats() {
|
|
211
|
+
return {
|
|
212
|
+
models: this.models.size,
|
|
213
|
+
actions: this.actions.size,
|
|
214
|
+
skills: this.skills.size,
|
|
215
|
+
providers: this.providers.size,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Global registry instance
|
|
221
|
+
export const registry = new Registry();
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name Resolution Logic
|
|
3
|
+
* Handles smart resolution of names to definitions with fuzzy matching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Definition, SearchOptions } from "../schema/types";
|
|
7
|
+
import { registry } from "./registry";
|
|
8
|
+
|
|
9
|
+
export interface ResolveOptions {
|
|
10
|
+
/** If true, throw an error if not found */
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Preferred type to resolve to */
|
|
13
|
+
preferType?: "model" | "action" | "skill";
|
|
14
|
+
/** If true, allow fuzzy matching */
|
|
15
|
+
fuzzy?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResolveResult {
|
|
19
|
+
definition: Definition | null;
|
|
20
|
+
matchType: "exact" | "namespace" | "fuzzy" | "alias" | null;
|
|
21
|
+
suggestions?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Common aliases for actions/models
|
|
25
|
+
const ALIASES: Record<string, string> = {
|
|
26
|
+
// Video generation
|
|
27
|
+
i2v: "image-to-video",
|
|
28
|
+
t2v: "text-to-video",
|
|
29
|
+
img2vid: "image-to-video",
|
|
30
|
+
txt2vid: "text-to-video",
|
|
31
|
+
|
|
32
|
+
// Image generation
|
|
33
|
+
i2i: "image-to-image",
|
|
34
|
+
t2i: "text-to-image",
|
|
35
|
+
img2img: "image-to-image",
|
|
36
|
+
txt2img: "text-to-image",
|
|
37
|
+
|
|
38
|
+
// Voice/Audio
|
|
39
|
+
tts: "text-to-speech",
|
|
40
|
+
stt: "speech-to-text",
|
|
41
|
+
voice: "text-to-speech",
|
|
42
|
+
|
|
43
|
+
// Video editing
|
|
44
|
+
concat: "merge",
|
|
45
|
+
join: "merge",
|
|
46
|
+
combine: "merge",
|
|
47
|
+
crop: "trim",
|
|
48
|
+
clip: "trim",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a name to a definition with smart matching
|
|
53
|
+
*/
|
|
54
|
+
export function resolve(name: string, options?: ResolveOptions): ResolveResult {
|
|
55
|
+
// 1. Check direct/exact match
|
|
56
|
+
const exact = registry.resolve(name);
|
|
57
|
+
if (exact) {
|
|
58
|
+
return { definition: exact, matchType: "exact" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Check aliases
|
|
62
|
+
const aliasTarget = ALIASES[name.toLowerCase()];
|
|
63
|
+
if (aliasTarget) {
|
|
64
|
+
const aliased = registry.resolve(aliasTarget);
|
|
65
|
+
if (aliased) {
|
|
66
|
+
return { definition: aliased, matchType: "alias" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Try with namespace prefix if preferType is set
|
|
71
|
+
if (options?.preferType) {
|
|
72
|
+
const namespaced = registry.resolve(`${options.preferType}/${name}`);
|
|
73
|
+
if (namespaced) {
|
|
74
|
+
return { definition: namespaced, matchType: "namespace" };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 4. Fuzzy matching if enabled
|
|
79
|
+
if (options?.fuzzy) {
|
|
80
|
+
const suggestions = findSimilar(name);
|
|
81
|
+
const firstSuggestion = suggestions[0];
|
|
82
|
+
if (firstSuggestion) {
|
|
83
|
+
const topMatch = registry.resolve(firstSuggestion);
|
|
84
|
+
if (topMatch) {
|
|
85
|
+
return {
|
|
86
|
+
definition: topMatch,
|
|
87
|
+
matchType: "fuzzy",
|
|
88
|
+
suggestions,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 5. Not found
|
|
95
|
+
if (options?.required) {
|
|
96
|
+
const suggestions = findSimilar(name);
|
|
97
|
+
const suggestionText =
|
|
98
|
+
suggestions.length > 0
|
|
99
|
+
? ` Did you mean: ${suggestions.slice(0, 3).join(", ")}?`
|
|
100
|
+
: "";
|
|
101
|
+
throw new Error(`Definition not found: "${name}".${suggestionText}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
definition: null,
|
|
106
|
+
matchType: null,
|
|
107
|
+
suggestions: findSimilar(name),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find similar names using Levenshtein distance
|
|
113
|
+
*/
|
|
114
|
+
export function findSimilar(query: string, limit = 5): string[] {
|
|
115
|
+
const q = query.toLowerCase();
|
|
116
|
+
const all = registry.list();
|
|
117
|
+
|
|
118
|
+
const scored = all.map((def) => ({
|
|
119
|
+
name: def.name,
|
|
120
|
+
score: similarity(q, def.name.toLowerCase()),
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
// Sort by similarity score (higher is better)
|
|
124
|
+
scored.sort((a, b) => b.score - a.score);
|
|
125
|
+
|
|
126
|
+
// Return top matches with reasonable similarity
|
|
127
|
+
return scored
|
|
128
|
+
.filter((s) => s.score > 0.3)
|
|
129
|
+
.slice(0, limit)
|
|
130
|
+
.map((s) => s.name);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Simple similarity score based on common substrings and Levenshtein
|
|
135
|
+
*/
|
|
136
|
+
function similarity(a: string, b: string): number {
|
|
137
|
+
// Exact match
|
|
138
|
+
if (a === b) return 1;
|
|
139
|
+
|
|
140
|
+
// Prefix match
|
|
141
|
+
if (b.startsWith(a) || a.startsWith(b)) return 0.9;
|
|
142
|
+
|
|
143
|
+
// Contains match
|
|
144
|
+
if (b.includes(a) || a.includes(b)) return 0.7;
|
|
145
|
+
|
|
146
|
+
// Levenshtein-based similarity
|
|
147
|
+
const distance = levenshteinDistance(a, b);
|
|
148
|
+
const maxLen = Math.max(a.length, b.length);
|
|
149
|
+
return 1 - distance / maxLen;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Calculate Levenshtein edit distance
|
|
154
|
+
*/
|
|
155
|
+
function levenshteinDistance(a: string, b: string): number {
|
|
156
|
+
if (a.length === 0) return b.length;
|
|
157
|
+
if (b.length === 0) return a.length;
|
|
158
|
+
|
|
159
|
+
// Create matrix with proper initialization
|
|
160
|
+
const matrix: number[][] = Array.from({ length: b.length + 1 }, (_, i) =>
|
|
161
|
+
Array.from({ length: a.length + 1 }, (_, j) =>
|
|
162
|
+
i === 0 ? j : j === 0 ? i : 0,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Fill matrix
|
|
167
|
+
for (let i = 1; i <= b.length; i++) {
|
|
168
|
+
for (let j = 1; j <= a.length; j++) {
|
|
169
|
+
const prevRow = matrix[i - 1];
|
|
170
|
+
const currRow = matrix[i];
|
|
171
|
+
if (!prevRow || !currRow) continue;
|
|
172
|
+
|
|
173
|
+
if (b[i - 1] === a[j - 1]) {
|
|
174
|
+
currRow[j] = prevRow[j - 1] ?? 0;
|
|
175
|
+
} else {
|
|
176
|
+
currRow[j] = Math.min(
|
|
177
|
+
(prevRow[j - 1] ?? 0) + 1, // substitution
|
|
178
|
+
(currRow[j - 1] ?? 0) + 1, // insertion
|
|
179
|
+
(prevRow[j] ?? 0) + 1, // deletion
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return matrix[b.length]?.[a.length] ?? Math.max(a.length, b.length);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Search with filters
|
|
190
|
+
*/
|
|
191
|
+
export function search(query: string, options?: SearchOptions): Definition[] {
|
|
192
|
+
return registry.search(query, options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get suggestions for partial input (autocomplete)
|
|
197
|
+
*/
|
|
198
|
+
export function suggest(partial: string, limit = 10): string[] {
|
|
199
|
+
const p = partial.toLowerCase();
|
|
200
|
+
const all = registry.list();
|
|
201
|
+
|
|
202
|
+
return all
|
|
203
|
+
.filter((def) => def.name.toLowerCase().startsWith(p))
|
|
204
|
+
.slice(0, limit)
|
|
205
|
+
.map((def) => def.name);
|
|
206
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema helpers for CLI introspection
|
|
3
|
+
* Uses Zod v4's native toJSONSchema() method
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JsonSchema, SchemaProperty } from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert a Zod schema to JSON Schema format
|
|
10
|
+
* Uses Zod v4's native toJSONSchema() method
|
|
11
|
+
*/
|
|
12
|
+
// biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
|
|
13
|
+
export function toJsonSchema(schema: any): JsonSchema {
|
|
14
|
+
// Zod v4 has native toJSONSchema() method
|
|
15
|
+
if (typeof schema.toJSONSchema === "function") {
|
|
16
|
+
try {
|
|
17
|
+
return schema.toJSONSchema() as JsonSchema;
|
|
18
|
+
} catch {
|
|
19
|
+
// Some Zod types (transforms, custom) can't be represented in JSON Schema
|
|
20
|
+
// Return a basic schema with description if available
|
|
21
|
+
return {
|
|
22
|
+
type: "object",
|
|
23
|
+
description: schema._def?.description,
|
|
24
|
+
} as JsonSchema;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Fallback for older Zod versions or non-Zod schemas
|
|
28
|
+
return schema as JsonSchema;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get CLI-friendly schema info for displaying help
|
|
33
|
+
* Returns properties and required fields in JSON Schema format
|
|
34
|
+
*/
|
|
35
|
+
// biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
|
|
36
|
+
export function getCliSchemaInfo(schema: any): {
|
|
37
|
+
properties: Record<string, SchemaProperty>;
|
|
38
|
+
required: string[];
|
|
39
|
+
} {
|
|
40
|
+
const jsonSchema = toJsonSchema(schema);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
properties: (jsonSchema.properties || {}) as Record<string, SchemaProperty>,
|
|
44
|
+
required: jsonSchema.required || [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate CLI help text from a Zod schema
|
|
50
|
+
*/
|
|
51
|
+
// biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
|
|
52
|
+
export function schemaToCliHelp(schema: any): string {
|
|
53
|
+
const { properties, required } = getCliSchemaInfo(schema);
|
|
54
|
+
const lines: string[] = [];
|
|
55
|
+
|
|
56
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
57
|
+
const isRequired = required.includes(key);
|
|
58
|
+
const parts: string[] = [`--${key.padEnd(15)}`];
|
|
59
|
+
|
|
60
|
+
if (prop.description) {
|
|
61
|
+
parts.push(prop.description);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isRequired) {
|
|
65
|
+
parts.push("(required)");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (prop.default !== undefined) {
|
|
69
|
+
parts.push(`[default: ${JSON.stringify(prop.default)}]`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
73
|
+
parts.push(`[${prop.enum.join(", ")}]`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lines.push(parts.join(" "));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Coerce a CLI string value to the proper type based on JSON Schema property
|
|
84
|
+
*/
|
|
85
|
+
export function coerceCliValue(value: string, prop: SchemaProperty): unknown {
|
|
86
|
+
switch (prop.type) {
|
|
87
|
+
case "number":
|
|
88
|
+
case "integer":
|
|
89
|
+
return Number(value);
|
|
90
|
+
case "boolean":
|
|
91
|
+
return value === "true" || value === "1";
|
|
92
|
+
case "array":
|
|
93
|
+
// Handle comma-separated values
|
|
94
|
+
return value.split(",").map((v) => v.trim());
|
|
95
|
+
default:
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse CLI arguments into an object based on schema
|
|
102
|
+
*/
|
|
103
|
+
export function parseCliArgs(
|
|
104
|
+
args: string[],
|
|
105
|
+
// biome-ignore lint/suspicious/noExplicitAny: Zod v4 type compatibility
|
|
106
|
+
schema: any,
|
|
107
|
+
): Record<string, unknown> {
|
|
108
|
+
const { properties } = getCliSchemaInfo(schema);
|
|
109
|
+
const result: Record<string, unknown> = {};
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < args.length; i++) {
|
|
112
|
+
const arg = args[i];
|
|
113
|
+
if (!arg?.startsWith("--")) continue;
|
|
114
|
+
|
|
115
|
+
const key = arg.slice(2);
|
|
116
|
+
const prop = properties[key];
|
|
117
|
+
|
|
118
|
+
if (!prop) continue;
|
|
119
|
+
|
|
120
|
+
// Boolean flags don't require a value
|
|
121
|
+
if (prop.type === "boolean") {
|
|
122
|
+
result[key] = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get the value
|
|
127
|
+
const value = args[++i];
|
|
128
|
+
if (value && !value.startsWith("--")) {
|
|
129
|
+
result[key] = coerceCliValue(value, prop);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|