@dpantani/tdmcp 0.1.0 → 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.
@@ -7,6 +7,12 @@ interface Logger {
7
7
  error(message: string, meta?: Record<string, unknown>): void;
8
8
  }
9
9
 
10
+ interface TdEvent {
11
+ event: string;
12
+ data?: unknown;
13
+ }
14
+ type TdEventHandler = (event: TdEvent) => void;
15
+
10
16
  interface OperatorParameter {
11
17
  name: string;
12
18
  label?: string;
@@ -201,6 +207,38 @@ declare class KnowledgeBase {
201
207
  stats(): KnowledgeStats;
202
208
  }
203
209
 
210
+ interface ParsedNote {
211
+ /** Parsed YAML frontmatter (empty object when the note has none). */
212
+ data: Record<string, unknown>;
213
+ /** Markdown body with the frontmatter stripped. */
214
+ body: string;
215
+ }
216
+
217
+ /**
218
+ * A thin, safe wrapper over an Obsidian vault (a folder of markdown files on the
219
+ * local filesystem). Every path is resolved relative to the vault root and is
220
+ * refused if it escapes that root, so user-supplied note names cannot reach
221
+ * outside the vault.
222
+ */
223
+ declare class Vault {
224
+ readonly root: string;
225
+ private readonly logger;
226
+ constructor(root: string, logger?: Logger);
227
+ /** Resolves a vault-relative path, throwing if it would escape the vault root. */
228
+ resolve(relPath: string): string;
229
+ exists(relPath: string): boolean;
230
+ read(relPath: string): string;
231
+ write(relPath: string, content: string): void;
232
+ writeBinary(relPath: string, data: Buffer): void;
233
+ ensureDir(relPath: string): void;
234
+ /** Lists file names directly inside `subdir`, optionally filtered by extension. */
235
+ list(subdir: string, ext?: string): string[];
236
+ /** Reads a markdown note and splits its frontmatter from its body. */
237
+ readNote(relPath: string): ParsedNote;
238
+ /** Writes a markdown note from frontmatter data + body. */
239
+ writeNote(relPath: string, data: Record<string, unknown>, body: string): void;
240
+ }
241
+
204
242
  declare const RecipeSchema: z.ZodObject<{
205
243
  id: z.ZodString;
206
244
  name: z.ZodString;
@@ -252,6 +290,24 @@ declare const RecipeSchema: z.ZodObject<{
252
290
  }, z.core.$strip>>>;
253
291
  glsl_code: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
254
292
  python_code: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
293
+ controls: z.ZodDefault<z.ZodArray<z.ZodObject<{
294
+ name: z.ZodString;
295
+ type: z.ZodDefault<z.ZodEnum<{
296
+ string: "string";
297
+ int: "int";
298
+ float: "float";
299
+ toggle: "toggle";
300
+ menu: "menu";
301
+ rgb: "rgb";
302
+ pulse: "pulse";
303
+ }>>;
304
+ label: z.ZodOptional<z.ZodString>;
305
+ min: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
306
+ max: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
307
+ default: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodBoolean, z.ZodString]>>;
308
+ menu_items: z.ZodOptional<z.ZodArray<z.ZodString>>;
309
+ bind_to: z.ZodOptional<z.ZodArray<z.ZodString>>;
310
+ }, z.core.$strip>>>;
255
311
  preview_description: z.ZodDefault<z.ZodString>;
256
312
  }, z.core.$strip>;
257
313
  type Recipe = z.infer<typeof RecipeSchema>;
@@ -266,11 +322,14 @@ interface RecipeSummary {
266
322
  interface RecipeLibraryOptions {
267
323
  dir?: string;
268
324
  logger?: Logger;
325
+ /** Optional Obsidian vault; recipes in `<vault>/Recipes/*.md` are merged in (and override built-ins by id). */
326
+ vault?: Vault;
269
327
  }
270
- /** Loads and validates recipe JSON files from the recipes directory. */
328
+ /** Loads and validates recipe JSON files from the recipes directory, plus any vault recipes. */
271
329
  declare class RecipeLibrary {
272
330
  private readonly dir;
273
331
  private readonly logger;
332
+ private readonly vault?;
274
333
  private cache?;
275
334
  constructor(options?: RecipeLibraryOptions);
276
335
  private load;
@@ -320,6 +379,16 @@ interface TouchDesignerClientOptions {
320
379
  token?: string;
321
380
  /** Overridable for tests (defaults to global `fetch`). */
322
381
  fetchImpl?: typeof fetch;
382
+ /**
383
+ * Extra attempts for a transient connection failure on an **idempotent** (GET)
384
+ * request — e.g. TD briefly stalls mid-build. Default 2 (so up to 3 tries).
385
+ * Only `TdConnectionError` is retried; timeouts and bridge errors are not (a
386
+ * timeout may mean the request was received, and non-GET methods aren't safe
387
+ * to repeat). Set 0 to disable.
388
+ */
389
+ retries?: number;
390
+ /** Base backoff between retries, in ms (linear: delay × attempt). Default 150. */
391
+ retryDelayMs?: number;
323
392
  }
324
393
  /**
325
394
  * HTTP client for the TouchDesigner REST bridge. Every method maps to one of the
@@ -332,9 +401,12 @@ declare class TouchDesignerClient {
332
401
  private readonly logger;
333
402
  private readonly token;
334
403
  private readonly fetchImpl;
404
+ private readonly retries;
405
+ private readonly retryDelayMs;
335
406
  constructor(options: TouchDesignerClientOptions);
336
407
  get endpoint(): string;
337
408
  private request;
409
+ private attemptRequest;
338
410
  getInfo(): Promise<{
339
411
  td_version?: string | undefined;
340
412
  python_version?: string | undefined;
@@ -346,6 +418,7 @@ declare class TouchDesignerClient {
346
418
  path: string;
347
419
  type: string;
348
420
  name: string;
421
+ parameter_warnings?: string[] | undefined;
349
422
  }>;
350
423
  deleteNode(path: string): Promise<{
351
424
  deleted: string;
@@ -355,6 +428,7 @@ declare class TouchDesignerClient {
355
428
  path: string;
356
429
  type: string;
357
430
  name: string;
431
+ parameter_warnings?: string[] | undefined;
358
432
  }[];
359
433
  }>;
360
434
  getNode(path: string): Promise<{
@@ -362,6 +436,7 @@ declare class TouchDesignerClient {
362
436
  type: string;
363
437
  name: string;
364
438
  parameters: Record<string, unknown>;
439
+ parameter_warnings?: string[] | undefined;
365
440
  inputs?: string[] | undefined;
366
441
  outputs?: string[] | undefined;
367
442
  family?: string | undefined;
@@ -372,6 +447,7 @@ declare class TouchDesignerClient {
372
447
  type: string;
373
448
  name: string;
374
449
  parameters: Record<string, unknown>;
450
+ parameter_warnings?: string[] | undefined;
375
451
  inputs?: string[] | undefined;
376
452
  outputs?: string[] | undefined;
377
453
  family?: string | undefined;
@@ -420,6 +496,7 @@ declare class TouchDesignerClient {
420
496
  path: string;
421
497
  type: string;
422
498
  name: string;
499
+ parameter_warnings?: string[] | undefined;
423
500
  }[];
424
501
  connections: {
425
502
  source_path: string;
@@ -428,7 +505,7 @@ declare class TouchDesignerClient {
428
505
  target_input: number;
429
506
  }[];
430
507
  }>;
431
- getNetworkPerformance(path: string): Promise<{
508
+ getNetworkPerformance(path: string, recursive?: boolean): Promise<{
432
509
  nodes: {
433
510
  path: string;
434
511
  cook_time_ms: number;
@@ -445,6 +522,8 @@ interface ToolContext {
445
522
  knowledge: KnowledgeBase;
446
523
  recipes: RecipeLibrary;
447
524
  logger: Logger;
525
+ /** Optional Obsidian vault (set via TDMCP_VAULT_PATH); undefined when not configured. */
526
+ vault?: Vault;
448
527
  /**
449
528
  * Whether the raw Python escape-hatch tools may be registered. Undefined means
450
529
  * allowed (the default); only an explicit `false` locks them out.
@@ -452,6 +531,39 @@ interface ToolContext {
452
531
  allowRawPython?: boolean;
453
532
  }
454
533
 
534
+ declare const ConfigSchema: z.ZodObject<{
535
+ tdHost: z.ZodDefault<z.ZodString>;
536
+ tdPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
537
+ transport: z.ZodDefault<z.ZodEnum<{
538
+ stdio: "stdio";
539
+ http: "http";
540
+ }>>;
541
+ logLevel: z.ZodDefault<z.ZodEnum<{
542
+ error: "error";
543
+ debug: "debug";
544
+ info: "info";
545
+ warn: "warn";
546
+ silent: "silent";
547
+ }>>;
548
+ requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
549
+ httpPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
550
+ events: z.ZodDefault<z.ZodEnum<{
551
+ on: "on";
552
+ off: "off";
553
+ }>>;
554
+ rawPython: z.ZodDefault<z.ZodEnum<{
555
+ on: "on";
556
+ off: "off";
557
+ }>>;
558
+ bridgeToken: z.ZodOptional<z.ZodString>;
559
+ llmBaseUrl: z.ZodDefault<z.ZodString>;
560
+ llmModel: z.ZodDefault<z.ZodString>;
561
+ llmApiKey: z.ZodOptional<z.ZodString>;
562
+ chatPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
563
+ vaultPath: z.ZodOptional<z.ZodString>;
564
+ }, z.core.$strip>;
565
+ type TdmcpConfig = z.infer<typeof ConfigSchema>;
566
+
455
567
  interface CliResult {
456
568
  stdout: string;
457
569
  stderr: string;
@@ -462,5 +574,29 @@ interface RunCliOptions {
462
574
  makeCtx?: () => ToolContext;
463
575
  }
464
576
  declare function runCli(argv: string[], opts?: RunCliOptions): Promise<CliResult>;
577
+ interface RunWatchOptions {
578
+ config?: TdmcpConfig;
579
+ includeHighFrequency?: boolean;
580
+ /** Where each event line goes; defaults to stdout. Overridable for tests. */
581
+ write?: (line: string) => void;
582
+ /** Inject a stream factory for tests; defaults to a real `TdEventStream`. */
583
+ makeStream?: (args: {
584
+ url: string;
585
+ onEvent: TdEventHandler;
586
+ includeHighFrequency: boolean;
587
+ }) => {
588
+ start: () => void;
589
+ close: () => void;
590
+ };
591
+ /** Resolve the returned promise when aborted; defaults to listening for SIGINT. */
592
+ signal?: AbortSignal;
593
+ }
594
+ /**
595
+ * Streams TouchDesigner bridge events to stdout as ndjson until interrupted.
596
+ * Runs outside `runCli` because it is a long-lived stream, not a request/response.
597
+ */
598
+ declare function runWatch(opts?: RunWatchOptions): Promise<void>;
599
+ /** Interactive read-eval-print loop: each line is tokenized and run through runCli. */
600
+ declare function runRepl(opts?: RunCliOptions): Promise<void>;
465
601
 
466
- export { type CliResult, type RunCliOptions, runCli };
602
+ export { type CliResult, type RunCliOptions, type RunWatchOptions, runCli, runRepl, runWatch };