Skip to content

Toolcraft createSDK loses published command types under strict TypeScript #500

Description

@kamilio

Summary

A Toolcraft command tree exported from a published package loses usable SDK member/result types in a strict TypeScript consumer.

With toolcraft@0.0.93, the runtime SDK works, but a clean consumer compiling with strict, module: NodeNext, and moduleResolution: NodeNext sees generated SDK members as unknown. In less strict declaration probes, command results can instead collapse to HumanInLoopPending even though no human-in-loop mode is configured.

Reproduction

Package code:

import { defineCommand, defineGroup, S } from "toolcraft";

const lint = defineCommand({
  name: "lint",
  params: S.Object({ root: S.Optional(S.String()) }),
  handler: async () => ({ files: 1 }),
});

export const root = defineGroup({
  name: "example",
  scope: ["cli", "mcp", "sdk"],
  children: [lint] as const,
});

Build declarations, pack and install that package into a clean consumer, then compile:

import { createSDK } from "toolcraft/sdk";
import { root } from "example-package";

const sdk = createSDK(root);
const result = await sdk.lint({ root: "." });
const files: number = result.files;
tsc --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext consumer.ts

Observed error:

TS18046: 'sdk.lint' is of type 'unknown'.

A declaration-emission probe without strict checking can expose lint, but its return type becomes Promise<HumanInLoopPending> rather than the handler result.

Expected behavior

A command tree imported from a published package should preserve the same generated SDK parameter and result types as an in-repository tree:

sdk.lint({ root: 42 }); // type error
const result = await sdk.lint({ root: "." });
result.files; // number

A command with no human-in-loop configuration should resolve to its handler result, not HumanInLoopPending.

Acceptance criteria

  • A packed two-package fixture compiles under strict NodeNext settings.
  • Generated command members remain callable and preserve parameter types.
  • Generated command results preserve the handler/result-schema type.
  • Commands without human-in-loop configuration do not infer HumanInLoopPending.
  • Toolcraft has a regression test that builds declarations, installs the tarball into a clean consumer, and runs tsc there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions