kitup exposes the same installer concepts in TypeScript, Go, and Rust.
The core flow is:
- resolve a local directory tree, embedded files, or public GitHub bundle
- resolve safe target agent selection for CLI workflows
- validate
SKILL.md - copy, update, skip, or report conflicts
- write
.kitup.jsonownership metadata - return a structured report
Package: @kitup/sdk
import {
detectHosts,
directoryBundle,
filesBundle,
githubBundle,
installBundledSkill,
installFlagError,
installWorkflowError,
installUxText,
planBundledSkill,
parseInstallFlags,
classifyInstallWorkflowExit,
resolveInstallSelection,
runBundledSkillInstall,
uninstallBundledSkill,
updateBundledSkill,
validateSkillBundle,
} from "@kitup/sdk";Primitive install call:
const report = await installBundledSkill({
appId: "mycli",
skillBundle: directoryBundle("./skills/mycli"),
scope: "user",
});Public GitHub bundle call:
const report = await installBundledSkill({
appId: "mycli",
skillBundle: githubBundle({
owner: "acme",
repo: "mycli-skills",
path: "skills/mycli",
ref: "v1.2.3",
}),
scope: "user",
});Implemented functions:
loadHostSpec(hostsFile?)resolveHosts({ agents, hostsFile?, hosts? })detectHosts({ home?, cwd?, hostsFile?, scope? })resolveInstallSelection({ home?, cwd?, hostsFile?, scope, agents?, yes?, stdinTTY?, currentAgent? })resolveInstallTargets({ home?, cwd?, hostsFile?, agents?, scope, skillName })validateSkillBundle(bundle, cwd?)computeBundleContentHash(bundle, cwd?)directoryBundle(path)filesBundle(files)githubBundle(options)parseInstallFlags(flags)agentSelectorFromFlags(values)parseScopeFlag(value)classifyInstallWorkflowExit(workflow)installWorkflowError(workflow)installFlagError(errors)runBundledSkillInstall(options)planBundledSkill(options)installBundledSkill(options)updateBundledSkill(options)uninstallBundledSkill(options)installUxText
Module: github.com/lathe-cli/kitup/go
import kitup "github.com/lathe-cli/kitup/go"Primitive install call:
report, err := kitup.InstallBundledSkill(kitup.InstallOptions{
AppID: "mycli",
SkillBundle: kitup.DirectoryBundle("./skills/mycli"),
Scope: kitup.UserScope,
})Public GitHub bundle call:
report, err := kitup.InstallBundledSkill(kitup.InstallOptions{
AppID: "mycli",
SkillBundle: kitup.GitHubBundle(kitup.GitHubBundleOptions{
Owner: "acme",
Repo: "mycli-skills",
Path: "skills/mycli",
Ref: "v1.2.3",
}),
Scope: kitup.UserScope,
})Implemented functions:
LoadHostSpec(hostsFile string)ResolveHosts(agents, hosts)DetectHosts(opts, scope)ResolveInstallSelection(opts)ResolveInstallTargets(opts, agents, scope, skillName)ValidateSkillBundle(bundle)ComputeBundleContentHash(bundle)DirectoryBundle(path)FSBundle(fsys, root)FilesBundle(files)GitHubBundle(opts)ParseInstallFlags(flags)AgentSelectorFromFlags(values)ParseScopeFlag(value)ClassifyInstallWorkflowExit(report)InstallWorkflowError(report)InstallFlagError(errors)RunBundledSkillInstall(opts)PlanBundledSkill(opts)InstallBundledSkill(opts)UpdateBundledSkill(opts)UninstallBundledSkill(opts)InstallUX
Optional Cobra adapter module: github.com/lathe-cli/kitup/go-cobra
NewSkillCommand(opts)NewInstallCommand(opts)
Crate: kitup
Primitive install call:
let report = kitup::install_bundled_skill(&kitup::InstallOptions {
base: kitup::BaseOptions::default(),
app_id: "mycli".to_string(),
skill_bundle: kitup::directory_bundle("./skills/mycli"),
scope: kitup::Scope::User,
agents: kitup::AgentSelector::Auto,
force: false,
})?;Public GitHub bundle call:
let report = kitup::install_bundled_skill(&kitup::InstallOptions {
base: kitup::BaseOptions::default(),
app_id: "mycli".to_string(),
skill_bundle: kitup::github_bundle(kitup::GitHubBundleOptions {
owner: "acme".to_string(),
repo: "mycli-skills".to_string(),
path: "skills/mycli".to_string(),
ref_name: "v1.2.3".to_string(),
}),
scope: kitup::Scope::User,
agents: kitup::AgentSelector::Auto,
force: false,
})?;Implemented functions:
load_host_spec(hosts_file)resolve_hosts(agents, hosts)detect_hosts(options, scope)resolve_install_selection(options)resolve_install_targets(options, agents, scope, skill_name)validate_skill_bundle(bundle)compute_bundle_content_hash(bundle)directory_bundle(path)files_bundle(files)github_bundle(options)parse_install_flags(flags)agent_selector_from_flags(values, errors)parse_scope_flag(value, errors)classify_install_workflow_exit(report)install_workflow_error(report)install_flag_error(errors)run_bundled_skill_install(options)run_bundled_skill_install_with_io(options, input, output)plan_bundled_skill(options)install_bundled_skill(options)update_bundled_skill(options)uninstall_bundled_skill(options)INSTALL_UX
Install options use the same concepts across languages:
appId/AppID/app_id: owner id written to.kitup.jsonskillBundle/SkillBundle/skill_bundle: local directory, embedded files, or public GitHub bundlescope:userorprojectagents:"auto","*", or explicit host idsforce: overwrite unmanaged or different-owner target directories instead of reporting conflictshome,cwd,hostsFile: optional test and embedding overrides
Bundle file paths must use root-relative POSIX paths. SDKs reject empty paths, absolute paths, .., duplicate files, and backslash paths. SDKs exclude .kitup.json, .git, .DS_Store, swap files, and editor backups before validation, hashing, and copy.
The first non-local bundle constructor is GitHub only:
- TypeScript:
githubBundle({ owner, repo, path, ref }) - Go:
GitHubBundle(GitHubBundleOptions{Owner, Repo, Path, Ref}) - Rust:
github_bundle(GitHubBundleOptions { owner, repo, path, ref_name })
GitHub bundle resolution downloads only files under the configured directory path, requires SKILL.md at that bundle root, records the requested ref and resolved commit, and writes GitHub provenance into .kitup.json. It does not search GitHub, install dependencies, execute scripts, handle private auth, or install whole repositories by default.
The embedding CLI owns command names and framework attachment. kitup owns standard install flag semantics, selector mapping, user-facing workflow text, summary rendering, confirmation, dry-run planning, workflow exit classification, and execution. For user-facing commands, call runBundledSkillInstall / RunBundledSkillInstall / run_bundled_skill_install with values from the shared flag parsing helpers.
Workflow-only options:
yes: skip prompts and accept policy-selected targetsdryRun: render and return the plan without writingstdinTTY: whether interactive prompts are allowedcurrentAgent: host id detected by an embedding agent runtimedefaultScope: scope used by Enter at the scope prompt and byyeswhen scope was not explicitscopeSet: whether the user explicitly provided--scopepromptScope: whether missing explicit scope should trigger workflow scope selectioninput/Inandoutput/Out: prompt and rendering streams
Standard install flags:
--scope:userorproject--agent: repeatable target agent id; comma-separated values are accepted;*means all hosts and must be the only agent value--dry-run: render the plan without writing--yes/-y: skip prompts and accept policy-selected targets--force: overwrite unmanaged or different-owner target directories
Flag parsing returns a normalized scope, scopeSet, normalized agent selector, yes, dryRun, force, and structured flag errors. An empty agent list maps to auto; * maps to all hosts; explicit ids are deduplicated in input order.
For CLI workflows, pass promptScope: true. If scopeSet is false, TTY mode prompts for scope before agent selection and planning. Enter uses defaultScope or user. Non-TTY without explicit scope and without yes returns scope-selection-required. yes with no explicit scope uses defaultScope or user.
Workflow exit helpers classify reports into:
okcanceledselection-errorconflicterror
Recommended CLI behavior:
mycli skill install
mycli skill install --scope user --agent codex
mycli skill install --scope project --agent codex --agent claude-code
mycli skill install --scope user --agent codex --forceThe lower-level selection resolver remains available for custom shells. It returns one of:
install: proceed to plan and confirmation withselectedHostIdsselect-agents: ask the user to choose fromcandidateHostIdserror: do not write
Non-TTY without explicit agents and without yes returns agent-selection-required. yes with zero detected hosts returns no-detected-hosts; it does not mean all hosts.
In TTY mode, zero detected hosts prompts from all supported hosts. One detected host is auto-selected but still goes through summary confirmation. Multiple detected hosts prompt from the detected host candidates without defaulting to all; an empty selection cancels instead of writing.
Selector semantics:
scope: "user"installs into the firstuserSkillsDirspath for each host.scope: "project"installs into the firstprojectSkillsDirspath for each host.agents: "auto"uses host detection.agents: "*"selects every host adapter.- explicit agents select canonical host ids or aliases.
- hosts without a path for the selected scope return an
unsupported-scopeerror.
Install reports include:
installedupdatedskippedconflictserrors
Uninstall reports include:
removedskippedconflictserrors
TypeScript returns typed report objects. Go exposes InstallReport, UninstallReport, TargetResult, TargetStatus, and ReportError. Rust exposes InstallReport, UninstallReport, TargetResult, TargetStatus, and ReportError.
The serialized JSON report shape is the same across TypeScript, Go, and Rust. installed, updated, and removed contain target results. skipped and conflicts contain target results plus reason.
Conflict is the safe default. A target directory without matching .kitup.json ownership metadata is reported as a conflict, not overwritten unless force / --force is explicit.