@afromero/kin3o 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/index.js +21 -6
- package/dist/providers/claude.d.ts +1 -1
- package/dist/providers/claude.js +6 -3
- package/dist/providers/codex.d.ts +1 -1
- package/dist/providers/codex.js +7 -3
- package/dist/providers/registry.d.ts +11 -1
- package/dist/providers/registry.js +33 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
**Text to Motion. From your terminal.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@afromero/kin3o)
|
|
8
|
+
[](https://www.npmjs.com/package/@afromero/kin3o)
|
|
8
9
|
[](https://github.com/affromero/kin3o/actions/workflows/ci.yml)
|
|
9
10
|
[](https://www.typescriptlang.org/)
|
|
10
11
|
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[](https://socket.dev)
|
|
13
|
+
[](https://docs.npmjs.com/cli/v10/using-npm/config#min-release-age)
|
|
11
14
|
[](https://github.com/affromero/kin3o/pulls)
|
|
12
15
|
|
|
13
16
|
AI-powered Lottie animation generator. Turns natural language prompts into valid, playable Lottie JSON and interactive dotLottie state machines using your existing Claude or Codex subscription.
|
|
@@ -237,6 +240,14 @@ npm run ci # typecheck + test
|
|
|
237
240
|
npm run build # Compile to dist/
|
|
238
241
|
```
|
|
239
242
|
|
|
243
|
+
## Related Projects
|
|
244
|
+
|
|
245
|
+
| Project | Description |
|
|
246
|
+
|---------|-------------|
|
|
247
|
+
| [**Fairtrail**](https://github.com/affromero/fairtrail) | Flight price evolution tracker with natural language search |
|
|
248
|
+
| [**PriceToken**](https://github.com/affromero/pricetoken) | Real-time LLM pricing API, npm/PyPI packages, and live dashboard |
|
|
249
|
+
| [**gitpane**](https://github.com/affromero/gitpane) | Multi-repo Git workspace dashboard for the terminal |
|
|
250
|
+
|
|
240
251
|
## License
|
|
241
252
|
|
|
242
253
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { PROVIDERS, detectAvailableProviders, getDefaultProvider } from './providers/registry.js';
|
|
5
|
+
import { PROVIDERS, detectAvailableProviders, getDefaultProvider, diagnoseProvider } from './providers/registry.js';
|
|
6
6
|
import { buildSystemPrompt, buildInteractiveSystemPrompt, buildRefinementUserPrompt, buildInteractiveRefinementUserPrompt, loadDesignTokens } from './prompts/index.js';
|
|
7
7
|
import { validateLottie, autoFix } from './validator.js';
|
|
8
8
|
import { validateStateMachine } from './state-machine-validator.js';
|
|
@@ -28,6 +28,7 @@ program
|
|
|
28
28
|
.option('--no-preview', 'Skip opening preview in browser')
|
|
29
29
|
.option('-t, --tokens <path>', 'Path to design tokens JSON (or "sotto" for built-in)')
|
|
30
30
|
.option('-i, --interactive', 'Generate interactive state machine (.lottie output)', false)
|
|
31
|
+
.option('--timeout <ms>', 'CLI subprocess timeout in milliseconds (default: auto per model)', parseInt)
|
|
31
32
|
.action(async (prompt, options) => {
|
|
32
33
|
const mode = options.interactive ? 'interactive' : 'static';
|
|
33
34
|
console.log(`\nkin3o — Generating ${mode} animation...`);
|
|
@@ -36,7 +37,7 @@ program
|
|
|
36
37
|
if (!providerKey) {
|
|
37
38
|
providerKey = await getDefaultProvider() ?? undefined;
|
|
38
39
|
if (!providerKey) {
|
|
39
|
-
console.error(' ✗ No AI providers available.
|
|
40
|
+
console.error(' ✗ No AI providers available. Run `kin3o providers` for diagnostics.');
|
|
40
41
|
process.exit(1);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -56,7 +57,7 @@ program
|
|
|
56
57
|
: buildSystemPrompt(tokens);
|
|
57
58
|
// 4. Generate
|
|
58
59
|
try {
|
|
59
|
-
const result = await provider.generate(model, systemPrompt, prompt);
|
|
60
|
+
const result = await provider.generate(model, systemPrompt, prompt, options.timeout);
|
|
60
61
|
console.log(` ✓ Generated in ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
61
62
|
if (options.interactive) {
|
|
62
63
|
await handleInteractiveOutput(result.content, prompt, options);
|
|
@@ -166,6 +167,7 @@ program
|
|
|
166
167
|
.option('-o, --output <path>', 'Output file path')
|
|
167
168
|
.option('--no-preview', 'Skip opening preview in browser')
|
|
168
169
|
.option('-t, --tokens <path>', 'Path to design tokens JSON (or "sotto" for built-in)')
|
|
170
|
+
.option('--timeout <ms>', 'CLI subprocess timeout in milliseconds (default: 600000)', parseInt)
|
|
169
171
|
.action(async (file, prompt, rawOptions) => {
|
|
170
172
|
const resolvedPath = resolve(file);
|
|
171
173
|
// 1. Validate input file
|
|
@@ -202,7 +204,7 @@ program
|
|
|
202
204
|
if (!providerKey) {
|
|
203
205
|
providerKey = await getDefaultProvider() ?? undefined;
|
|
204
206
|
if (!providerKey) {
|
|
205
|
-
console.error(' ✗ No AI providers available.
|
|
207
|
+
console.error(' ✗ No AI providers available. Run `kin3o providers` for diagnostics.');
|
|
206
208
|
process.exit(1);
|
|
207
209
|
}
|
|
208
210
|
}
|
|
@@ -225,7 +227,7 @@ program
|
|
|
225
227
|
: buildRefinementUserPrompt(currentJson, prompt);
|
|
226
228
|
// 6. Generate refined output
|
|
227
229
|
try {
|
|
228
|
-
const result = await provider.generate(model, systemPrompt, userPrompt);
|
|
230
|
+
const result = await provider.generate(model, systemPrompt, userPrompt, rawOptions.timeout);
|
|
229
231
|
console.log(` ✓ Refined in ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
230
232
|
// 7. Compute output path
|
|
231
233
|
const outputDir = ensureOutputDir();
|
|
@@ -369,9 +371,22 @@ program
|
|
|
369
371
|
const available = await detectAvailableProviders();
|
|
370
372
|
console.log('\nAvailable AI Providers:\n');
|
|
371
373
|
for (const [key, config] of Object.entries(PROVIDERS)) {
|
|
372
|
-
const
|
|
374
|
+
const isAvail = available.includes(key);
|
|
375
|
+
const status = isAvail ? '✓' : '✗';
|
|
373
376
|
console.log(` ${status} ${config.displayName} (${key})`);
|
|
374
377
|
console.log(` Models: ${config.models.join(', ')}`);
|
|
378
|
+
if (!isAvail) {
|
|
379
|
+
const diag = diagnoseProvider(key);
|
|
380
|
+
if (!diag.binaryFound) {
|
|
381
|
+
console.log(` Problem: "${diag.binaryName}" not found on PATH`);
|
|
382
|
+
console.log(` Fix: Install ${config.displayName} and ensure "${diag.binaryName}" is on your PATH`);
|
|
383
|
+
}
|
|
384
|
+
else if (!diag.authFound) {
|
|
385
|
+
const cmd = key === 'codex' ? 'codex auth' : 'claude';
|
|
386
|
+
console.log(` Problem: CLI found but not authenticated`);
|
|
387
|
+
console.log(` Fix: Run \`${cmd}\` to log in`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
375
390
|
}
|
|
376
391
|
console.log('');
|
|
377
392
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { GenerationResult } from './registry.js';
|
|
2
|
-
export declare function generateWithClaude(model: string, systemPrompt: string, userPrompt: string): Promise<GenerationResult>;
|
|
2
|
+
export declare function generateWithClaude(model: string, systemPrompt: string, userPrompt: string, timeoutMs?: number): Promise<GenerationResult>;
|
package/dist/providers/claude.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { getTimeoutMs } from './registry.js';
|
|
2
3
|
import { filterCliStderr } from '../utils.js';
|
|
3
|
-
export async function generateWithClaude(model, systemPrompt, userPrompt) {
|
|
4
|
+
export async function generateWithClaude(model, systemPrompt, userPrompt, timeoutMs) {
|
|
4
5
|
const start = Date.now();
|
|
5
6
|
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
|
|
7
|
+
const timeout = getTimeoutMs(timeoutMs);
|
|
6
8
|
const content = await new Promise((resolve, reject) => {
|
|
7
9
|
const env = { ...process.env };
|
|
8
10
|
delete env.ANTHROPIC_API_KEY;
|
|
9
11
|
const proc = spawn('claude', ['--print', '--model', model], {
|
|
10
|
-
timeout
|
|
12
|
+
timeout,
|
|
11
13
|
env,
|
|
12
14
|
});
|
|
13
15
|
let stdout = '';
|
|
@@ -21,7 +23,8 @@ export async function generateWithClaude(model, systemPrompt, userPrompt) {
|
|
|
21
23
|
proc.on('close', (code) => {
|
|
22
24
|
if (code !== 0) {
|
|
23
25
|
const filtered = filterCliStderr(stderr);
|
|
24
|
-
|
|
26
|
+
const hint = code === 143 ? ` (timed out after ${Math.round(timeout / 1000)}s — try --timeout <ms> or a faster model)` : '';
|
|
27
|
+
reject(new Error(`claude CLI exited ${code}: ${filtered}${hint}`));
|
|
25
28
|
}
|
|
26
29
|
else {
|
|
27
30
|
resolve(stdout.trim());
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { GenerationResult } from './registry.js';
|
|
2
|
-
export declare function generateWithCodex(model: string, systemPrompt: string, userPrompt: string): Promise<GenerationResult>;
|
|
2
|
+
export declare function generateWithCodex(model: string, systemPrompt: string, userPrompt: string, timeoutMs?: number): Promise<GenerationResult>;
|
package/dist/providers/codex.js
CHANGED
|
@@ -2,11 +2,13 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { mkdtempSync, readFileSync, unlinkSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
+
import { getTimeoutMs } from './registry.js';
|
|
5
6
|
import { filterCliStderr } from '../utils.js';
|
|
6
|
-
export async function generateWithCodex(model, systemPrompt, userPrompt) {
|
|
7
|
+
export async function generateWithCodex(model, systemPrompt, userPrompt, timeoutMs) {
|
|
7
8
|
const start = Date.now();
|
|
8
9
|
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
|
|
9
10
|
const tmpFile = join(mkdtempSync(join(tmpdir(), 'codex-')), 'output.txt');
|
|
11
|
+
const timeout = getTimeoutMs(timeoutMs);
|
|
10
12
|
const args = [
|
|
11
13
|
'exec', '-',
|
|
12
14
|
'--skip-git-repo-check',
|
|
@@ -16,7 +18,7 @@ export async function generateWithCodex(model, systemPrompt, userPrompt) {
|
|
|
16
18
|
];
|
|
17
19
|
const content = await new Promise((resolve, reject) => {
|
|
18
20
|
const proc = spawn('codex', args, {
|
|
19
|
-
timeout
|
|
21
|
+
timeout,
|
|
20
22
|
env: { ...process.env },
|
|
21
23
|
});
|
|
22
24
|
let stderr = '';
|
|
@@ -27,7 +29,9 @@ export async function generateWithCodex(model, systemPrompt, userPrompt) {
|
|
|
27
29
|
const filtered = filterCliStderr(stderr);
|
|
28
30
|
const hint = filtered.includes('401') || filtered.includes('Unauthorized')
|
|
29
31
|
? ' (ensure codex is authenticated via `codex auth`)'
|
|
30
|
-
:
|
|
32
|
+
: code === 143
|
|
33
|
+
? ` (timed out after ${Math.round(timeout / 1000)}s — try --timeout <ms> or a faster model)`
|
|
34
|
+
: '';
|
|
31
35
|
try {
|
|
32
36
|
const output = readFileSync(tmpFile, 'utf-8').trim();
|
|
33
37
|
unlinkSync(tmpFile);
|
|
@@ -9,9 +9,19 @@ export interface ProviderConfig {
|
|
|
9
9
|
models: string[];
|
|
10
10
|
defaultModel: string;
|
|
11
11
|
isAvailable: () => Promise<boolean>;
|
|
12
|
-
generate: (model: string, systemPrompt: string, userPrompt: string) => Promise<GenerationResult>;
|
|
12
|
+
generate: (model: string, systemPrompt: string, userPrompt: string, timeoutMs?: number) => Promise<GenerationResult>;
|
|
13
|
+
}
|
|
14
|
+
/** Get timeout with optional user override */
|
|
15
|
+
export declare function getTimeoutMs(userOverride?: number): number;
|
|
16
|
+
export interface ProviderDiagnosis {
|
|
17
|
+
binaryFound: boolean;
|
|
18
|
+
authFound: boolean;
|
|
19
|
+
binaryName: string;
|
|
20
|
+
authPaths: string[];
|
|
13
21
|
}
|
|
14
22
|
export declare const PROVIDERS: Record<string, ProviderConfig>;
|
|
23
|
+
/** Diagnose why a provider is or isn't available */
|
|
24
|
+
export declare function diagnoseProvider(key: string): ProviderDiagnosis;
|
|
15
25
|
/** Detect which providers are available (binary + auth) */
|
|
16
26
|
export declare function detectAvailableProviders(): Promise<string[]>;
|
|
17
27
|
/** Get default provider (first available in priority order) */
|
|
@@ -4,6 +4,12 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { generateWithClaude } from './claude.js';
|
|
6
6
|
import { generateWithCodex } from './codex.js';
|
|
7
|
+
/** Default CLI subprocess timeout: 10 minutes */
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
9
|
+
/** Get timeout with optional user override */
|
|
10
|
+
export function getTimeoutMs(userOverride) {
|
|
11
|
+
return userOverride ?? DEFAULT_TIMEOUT_MS;
|
|
12
|
+
}
|
|
7
13
|
const home = homedir();
|
|
8
14
|
function hasBinary(name) {
|
|
9
15
|
try {
|
|
@@ -23,7 +29,8 @@ export const PROVIDERS = {
|
|
|
23
29
|
if (!hasBinary('claude'))
|
|
24
30
|
return false;
|
|
25
31
|
return existsSync(join(home, '.claude.json'))
|
|
26
|
-
|| existsSync(join(home, '.claude', 'credentials.json'))
|
|
32
|
+
|| existsSync(join(home, '.claude', 'credentials.json'))
|
|
33
|
+
|| existsSync(join(home, '.claude', '.credentials.json'));
|
|
27
34
|
},
|
|
28
35
|
generate: generateWithClaude,
|
|
29
36
|
},
|
|
@@ -39,6 +46,31 @@ export const PROVIDERS = {
|
|
|
39
46
|
generate: generateWithCodex,
|
|
40
47
|
},
|
|
41
48
|
};
|
|
49
|
+
const PROVIDER_BINARIES = {
|
|
50
|
+
'claude-code': 'claude',
|
|
51
|
+
codex: 'codex',
|
|
52
|
+
};
|
|
53
|
+
const PROVIDER_AUTH_PATHS = {
|
|
54
|
+
'claude-code': [
|
|
55
|
+
join(home, '.claude.json'),
|
|
56
|
+
join(home, '.claude', 'credentials.json'),
|
|
57
|
+
join(home, '.claude', '.credentials.json'),
|
|
58
|
+
],
|
|
59
|
+
codex: [
|
|
60
|
+
join(home, '.codex', 'auth.json'),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
/** Diagnose why a provider is or isn't available */
|
|
64
|
+
export function diagnoseProvider(key) {
|
|
65
|
+
const binaryName = PROVIDER_BINARIES[key] ?? key;
|
|
66
|
+
const authPaths = PROVIDER_AUTH_PATHS[key] ?? [];
|
|
67
|
+
return {
|
|
68
|
+
binaryFound: hasBinary(binaryName),
|
|
69
|
+
authFound: authPaths.some(p => existsSync(p)),
|
|
70
|
+
binaryName,
|
|
71
|
+
authPaths,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
42
74
|
/** Detect which providers are available (binary + auth) */
|
|
43
75
|
export async function detectAvailableProviders() {
|
|
44
76
|
const available = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afromero/kin3o",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "AI-powered Lottie animation generator — text to motion from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
42
42
|
"@dotlottie/dotlottie-js": "^1.6.3",
|
|
43
43
|
"commander": "^13.0.0",
|
|
44
|
+
"lottie-react": "^2.4.1",
|
|
44
45
|
"open": "^10.1.0",
|
|
45
46
|
"puppeteer-core": "^24.0.0"
|
|
46
47
|
},
|