@afromero/kin3o 0.2.3 → 0.2.4

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 CHANGED
@@ -5,6 +5,7 @@
5
5
  **Text to Motion. From your terminal.**
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@afromero/kin3o)](https://www.npmjs.com/package/@afromero/kin3o)
8
+ [![downloads](https://img.shields.io/npm/dm/@afromero/kin3o)](https://www.npmjs.com/package/@afromero/kin3o)
8
9
  [![CI](https://github.com/affromero/kin3o/actions/workflows/ci.yml/badge.svg)](https://github.com/affromero/kin3o/actions/workflows/ci.yml)
9
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
10
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
@@ -237,6 +238,14 @@ npm run ci # typecheck + test
237
238
  npm run build # Compile to dist/
238
239
  ```
239
240
 
241
+ ## Related Projects
242
+
243
+ | Project | Description |
244
+ |---------|-------------|
245
+ | [**Fairtrail**](https://github.com/affromero/fairtrail) | Flight price evolution tracker with natural language search |
246
+ | [**PriceToken**](https://github.com/affromero/pricetoken) | Real-time LLM pricing API, npm/PyPI packages, and live dashboard |
247
+ | [**gitpane**](https://github.com/affromero/gitpane) | Multi-repo Git workspace dashboard for the terminal |
248
+
240
249
  ## License
241
250
 
242
251
  MIT
package/dist/index.js CHANGED
@@ -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...`);
@@ -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
@@ -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();
@@ -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>;
@@ -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: 240_000,
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
- reject(new Error(`claude CLI exited ${code}: ${filtered}`));
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>;
@@ -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: 240_000,
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,8 +9,10 @@ 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
13
  }
14
+ /** Get timeout with optional user override */
15
+ export declare function getTimeoutMs(userOverride?: number): number;
14
16
  export declare const PROVIDERS: Record<string, ProviderConfig>;
15
17
  /** Detect which providers are available (binary + auth) */
16
18
  export declare function detectAvailableProviders(): Promise<string[]>;
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afromero/kin3o",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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
  },