12 KiB
RFC: SDK follow-up capabilities
Status: proposed
English | 中文
Problem
The first SDK release creates and edits developer-owned Cordis projects through the shared model defined by the developer-project RFC and the project-editing architecture. Its create and config workflows are interactive, external Cordis plugins require manual dependency and configuration edits, command-line telemetry has no owning boundary, and interactive branches lack a stable test strategy.
These gaps are coupled. Create and config already share questions, feature configuration, and ProjectEditSession; adding separate automation paths would duplicate that domain logic. External-plugin installation must update both the package manager's files and cordis.yml. Telemetry must observe commands such as create and build that do not boot Cordis. Interactive testing must exercise Harness behavior without making terminal rendering a brittle product contract.
Proposal
The SDK extends the existing prompt and project-editing boundaries instead of creating parallel workflows. A non-interactive prompt port and structured feature plan drive create and config, dsh-sdk create <source> delegates dependency resolution to the project package manager before mounting the resolved package through ProjectEditSession, launcher-side telemetry wraps every dsh-sdk command, and injected prompt streams provide the primary interactive-test seam.
| Capability | Product entrypoint | Owning mechanism | Required outcome |
|---|---|---|---|
| Headless project creation | create-sdk --config <file> or --config-json <json> with optional --json |
HeadlessPromptPort, structured project answers, and a complete feature plan |
No terminal blocking; missing required input is explicit |
| External Cordis plugin installation | dsh-sdk create <source> |
Native package-manager add plus ProjectEditSession |
The dependency and cordis.yml entry identify the package manager's resolved package |
| Developer-cycle telemetry | Every dsh-sdk command |
Launcher-side consent, payload, redaction, anonymous identity, and delivery services | Reporting is best-effort and cannot change the command result |
| Interactive regression coverage | Create and config tests | Injected PromptPort input/output and filesystem assertions |
Tests cover Harness decisions and generated files without snapshotting terminal repainting |
Shared headless workflow
Structured input and lifecycle events
Headless create accepts a JSON object either inline through --config-json or from a file through --config. Scalar fields supply the ordinary create answers, while features supplies the complete selected feature set, feature options, secrets, and dedicated values. Defaults remain valid only where the owning question declares one; the headless path never invents an answer for a required prompt.
With --json, stdout is an NDJSON event stream. done means creation and any requested setup completed, action-required names an unanswered required prompt, and error reports another failure. Human-readable progress and package-manager output go to stderr so every stdout line remains parseable as one event. A caller responds to action-required by adding the missing value and running the command again.
Create and config consume the same feature-plan shape. Create exposes it through the command-line inputs above; config uses it at the shared workflow boundary so a later automation entrypoint does not need a second feature-selection model.
Prompt and project-editing boundaries
PromptPort remains the only boundary between SDK questions and an interaction implementation. ClackPromptPort handles terminals. HeadlessPromptPort consumes defaults exposed by the question contract and otherwise fails with the unanswered prompt; prefilled values normally prevent the port from being called.
Both paths use the same Question objects, FeatureConfigurator, SdkProject, and ProjectEditSession. The headless path therefore changes how answers arrive, not how features are interpreted or files are committed.
Agent skill
The repository ships a thin SKILL.md that teaches an agent to construct the structured input, request NDJSON, fill an action-required value, and retry. The skill invokes the public CLI and does not import an internal SDK API or introduce another project specification.
External Cordis plugin installation
dsh-sdk create <source> accepts a package-manager-native npm specifier such as pkg@version or a GitHub specifier such as github:owner/repo#ref. After confirmation, it asks the project's package manager to add the source, compares the direct dependency names before and after the operation, reopens the project, and mounts each newly resolved package in cordis.yml through ProjectEditSession.
The package manager owns source parsing, version or commit resolution, integrity data, lockfile updates, and any build policy. The SDK does not download or unpack a second copy through giget or pacote. An external plugin remains a dependency under node_modules; local plugin scaffolding remains a separate project-creation concern.
Launcher telemetry
Consent and collection
Telemetry wraps the dsh-sdk launcher command lifecycle because create and build do not reliably boot Cordis. One event records the command name, duration, success, a random per-user anonymous identifier, and redacted cordis.yml and package.json text when those project files are eligible.
Reporting is enabled unless a present telemetry config entry is explicitly disabled. DO_NOT_TRACK and CI deny reporting regardless of project configuration. A missing cordis.yml does not itself deny the event, but package.json content is included only when cordis.yml establishes that the directory is an SDK project.
Safety and delivery
The payload builder never reads .env. It redacts secret-shaped keys and values, known token forms, PEM blocks, URL credentials, and high-entropy opaque strings in the two eligible text files. Redaction is a safety backstop rather than a guarantee; SDK projects must keep credentials in .env.
The reporter uses a fixed endpoint and resolves every send path without throwing. Command dispatch records success or failure in a finally path, starts reporting after the command outcome is known, and drains within a bounded interval. Consent parsing, payload construction, storage, or network failures are swallowed only at this telemetry boundary and never alter the command's exit code.
Interactive workflow testing
Create and config tests inject a PromptPort and scripted input/output streams into the existing workflows. Parameterized scenarios cover feature selection, feature options, secrets, cancellation, review, and apply behavior, then assert the resulting cordis.yml and other project files. The stable product assertion is the generated project state, not clack's ANSI redraw sequence.
One or two optional real-PTY smoke tests may cover the shipped binary and TTY guard that injection cannot reproduce. Native PTY tooling does not belong on the required path unless it is reliable across the repository's supported Node and host versions.
Deferred work
- Extend the headless create specification to express local
pluginortoolscaffolding instead of defaulting that interactive choice to none. - Expose the telemetry opt-out in create and config while preserving the consent representation in which only a disabled telemetry entry is written.
- Define whether GitHub source dependencies must be prebuilt or may run package-manager-controlled preparation scripts, and surface the policy before installation.
- Replace the telemetry package's
.invalidendpoint placeholder with the production endpoint before release.
Alternatives considered
Build a separate headless creation engine. This would duplicate questions, feature requirements, configuration behavior, and project-editing rules. Reusing the prompt and edit-session boundaries keeps one implementation of project semantics.
Make a specification file the primary automation interface. Agents can pass the same typed JSON object inline, while people and CI may still use a file. A file-only protocol adds persistence and cleanup without adding semantics.
Use npx skills add as the project creator. The skills CLI installs Markdown skills; it does not create SDK projects or install npm packages. The agent skill therefore drives the SDK initializer instead of replacing it.
Fetch GitHub and npm sources through giget or pacote. A second fetch layer would duplicate package-manager resolution, integrity, lockfile, and lifecycle policy. Native dependency specifiers keep those decisions in the selected package manager.
Implement telemetry as a Cordis runtime plugin. Create and build do not necessarily boot Cordis, so a runtime plugin cannot observe the complete developer command cycle. The launcher is the boundary shared by those commands.
Derive the anonymous identifier from git metadata. Repository remotes can identify a project or organization. A random per-user identifier supports aggregation without encoding repository identity.
Collect only aggregate counters. Aggregate-only events reduce exposure but cannot answer which plugins, dependencies, and configuration shapes developers actually use. This proposal accepts collection of redacted project text and makes that exposure explicit.
Use real PTYs and transcript snapshots as the primary test strategy. Native PTY dependencies and terminal repaint sequences add platform and rendering instability while mostly testing clack. Injected interaction plus generated-file assertions tests the SDK-owned behavior directly.
Acceptance criteria
- Create runs without a TTY from a complete structured input, emits only NDJSON on stdout under
--json, and reports missing required input asaction-requiredwithout writing a partial project. - Create and config resolve the same feature-plan contract through the shared question, feature-configuration, and project-editing code paths.
dsh-sdk create <source>uses the selected project package manager, mounts the dependency name that operation actually added, and fails loudly when no new dependency can be identified.- Every
dsh-sdkcommand reaches one best-effort telemetry completion path; an explicit disabled entry,DO_NOT_TRACK, or CI prevents delivery, and telemetry failures never change the command result. - Telemetry never reads
.env, withholds unrelatedpackage.jsoncontent when nocordis.ymlexists, redacts both eligible text payloads, and uses an identifier unrelated to git metadata. - Interactive tests cover create and config decisions through injected interaction and assert committed project files; any real-PTY coverage remains a narrow smoke layer.
- The agent skill documents the public structured-input and event contracts without depending on private package exports.
Risks
- Full redacted
cordis.ymlandpackage.jsontext still reveals plugin and dependency names, URLs, paths, and configuration values to the endpoint operator, and heuristic redaction can miss a secret. - Default-on reporting may surprise developers when no telemetry entry exists; the CLI must make the opt-out discoverable before release.
- A package-manager add can change
package.json, the lockfile, and installed files beforeProjectEditSessionmounts the plugin, so a later mount failure can leave dependency changes that require manual recovery. - GitHub dependencies may execute preparation or lifecycle code according to package-manager policy; an unresolved build policy is a supply-chain and reproducibility risk.
- Injected prompt tests do not prove raw-mode, signal, or repaint behavior in a real terminal; the optional smoke layer must cover only those residual contracts.
References
- Vercel Eve and Vercel Labs Skills for the distinction between a headless initializer and skill distribution.
- npm package specifications, pnpm add, and Yarn add for package-manager-native sources.
- Console Do Not Track for the environment-level opt-out convention.
- Clack and Vitest snapshots for injected prompts and generated-file assertions.