Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"name": "Native Instruments"
},
"plugins": [
{
"name": "komplete-script",
"source": "./plugins/komplete-script",
"description": "Skills for developing with Komplete Script (kscript)"
},
{
"name": "kontakt-developer",
"source": "./plugins/kontakt-developer",
Expand Down
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Code owners. See https://docs.github.com/articles/about-code-owners

/plugins/kontakt-developer/ @ni-mgotsch
/plugins/kontakt-developer/ @ni-mgotsch @ni-acasapu
/plugins/komplete-script/ @ni-mgotsch @ni-acasapu
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ user-visible change.
3. Register new plugins in `.claude-plugin/marketplace.json`.
4. Commit. That's the release.

## Plugins that depend on other plugins

`kontakt-developer` builds on `komplete-script`: the Kontakt skill covers only the Kontakt
layer and loads the language skill for kscript itself. There is no dependency field in
`plugin.json`, and — because no plugin declares a `version` — no way to pin one either. So a
cross-plugin dependency is expressed two ways:

1. Documented in both `README.md` files as a prerequisite.
2. Asserted at skill load time: the depending skill instructs loading the other skill first,
and tells the user which `/plugin install` to run if it is missing.

Keep both in sync when adding a dependency, and never duplicate reference material across
plugins — one source of truth, referenced by name.

## Documenting commands

Command args live in the command file's frontmatter (`argument-hint`) and are
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ Claude plugins provided by Native Instruments.

## Plugins

- **kontakt-developer** — Contains skills for developing Kontakt instruments and tools
- **komplete-script** — The Komplete Script (kscript) language: syntax, standard library,
`ui` package. Host-independent.
- **kontakt-developer** — Kontakt instruments and tools: KSP binding, the `kontakt` package,
Kontakt Controls, the Kontakt MCP dev loop. **Builds on `komplete-script`** — install both
for Kontakt UI work.

## Install the marketplace

Expand All @@ -23,6 +27,7 @@ Or from remote by URL instead:
## Install a plugin

```
/plugin install komplete-script@native-instruments
/plugin install kontakt-developer@native-instruments
```

Expand Down
7 changes: 7 additions & 0 deletions plugins/komplete-script/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "komplete-script",
"description": "Skills for developing with Komplete Script (kscript)",
"author": {
"name": "Native Instruments"
}
}
31 changes: 31 additions & 0 deletions plugins/komplete-script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# komplete-script

Claude Code plugin for developing with **Komplete Script** (kscript) — the language behind
Komplete UI.

## Install

```
/plugin install komplete-script@native-instruments
```

See the [marketplace README](../../README.md) for adding the marketplace first.

## Skills

Skills load automatically when relevant; no command needed.

### `language`

The kscript language itself: syntax, types, classes, components, templates, modifiers,
modules, reactivity, layout, gestures, the standard library, and the `ui`, `math`, `path`,
`uri` and `audio_components` packages. kscript is **not in the model's training data** — the
skill bundles the full reference and loads it on demand before any kscript is written.

Triggers when you work with `.kscript` files or mention kscript, Komplete Script or
Komplete UI.

The skill is **host-independent**. Building a Kontakt instrument UI additionally needs
[`kontakt-developer`](../kontakt-developer/README.md), which adds KSP binding, the `kontakt`
package, Kontakt Controls, the Kontakt MCP dev loop, and the Kontakt → kscript version
mapping.
60 changes: 60 additions & 0 deletions plugins/komplete-script/skills/language/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: language
description: Write Komplete Script (kscript) — the language itself. Covers syntax, types, classes, components, templates, modifiers, modules, reactivity, layout, gestures, the standard library and the ui/math/path/uri/audio_components packages. Use when working with .kscript files or when the user mentions kscript, Komplete Script or Komplete UI. kscript is a proprietary language NOT in AI training data — always load the bundled references before writing any kscript code. Host-independent; for Kontakt instrument UIs (KSP binding, the kontakt package, Kontakt Controls, the Kontakt MCP dev loop) also load the kontakt-developer komplete-script skill.
---

# Komplete Script (kscript)

kscript is a statically typed language with a declarative, reactive UI layer (Komplete UI).
It is **not in your training data** — never write kscript from intuition or by analogy to
Swift/SwiftUI/JavaScript. Ground every construct in the bundled references.

This skill covers the language and its bundled packages only. Anything host-specific — how
a host loads a program, where files must live on disk, how the UI binds to an audio engine,
how compile output is read back — lives in that host's own skill.

For Kontakt instruments that means `kontakt-developer:komplete-script` — KSP binding, the
`kontakt` package, Kontakt Controls, the resource-container layout and the Kontakt MCP dev
loop. Load it alongside this skill for any Kontakt work.

## Reference files — load before writing code

Read these on demand from this skill's `references/` directory:

| File | When to read |
|---|---|
| `references/language.md` | **Always**, before writing any kscript. Syntax, types, variables, functions, classes, components, templates, modifiers, modules, declarative UI, reactivity, layout, gestures. |
| `references/stdlib.md` | Using built-in types: Int, Float, String, Array, Map, Color, Range, Angle, print, Error/Warning. |
| `references/ui-package.md` | Building UI: Components (Text, Rectangle, ZStack, …), Modifiers, enums, constants from the `ui` package. |
| `references/other-packages.md` | `math`, `uri`, `path` and `audio_components` packages. |
| `references/versions.md` | Checking whether a feature exists in the target language version. |
| `references/troubleshooting.md` | Any error/warning, unexpected behavior (flickering, state resets, cycle errors), known issues. |

For a typical UI task the set is `language.md` + `ui-package.md` (+ `stdlib.md` for
collection or string work).

## Language-level project conventions

- One file = one **module**. Filenames must be lowercase (letters, digits, underscores).
- Import paths are dot-separated and **always resolved from the project root** — the host's
script directory — never relative to the importing file.
- The main module must export the entry point: `export var main: Component = <Component>`.
- Exported types must not refer to un-exported types; annotate the export where needed.

## Version discipline

Features are gated by **language version** (`references/versions.md`). A host maps its own
release to a language version — for Kontakt, `kontakt-developer`'s
`references/kontakt-versions.md` holds that mapping and `kontaktTargetVersion` sets the
ceiling. Establish the target language version before writing code, and never use a feature
newer than it.

## Hard rules

1. **Never invent API.** Every component, modifier, function, parameter label and enum case
you write must appear in a reference file. If it is not there, say so rather than guessing.
2. The main module must `export var main = <Component>` (type-annotated when required).
3. Respect the target language version — check `references/versions.md` when unsure.
4. Verify code through the host's feedback loop (compile errors, warnings, `print` output)
before declaring success. Use `print("...")` for temporary debug logging and remove it
before finishing.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

# Komplete Script (kscript) Language Reference

Komplete Script is the language for building Kontakt instrument UIs and their business logic. It is **type-safe and statically typed**. Business logic is written imperatively; the UI is **declarative and reactive** — you describe what the UI looks like based on state, and the runtime keeps it in sync automatically. It works alongside KSP (Kontakt Script Processor), which handles real-time MIDI/audio; Komplete Script sits on top.
Komplete Script is the language for building Komplete UI interfaces and their business logic. It is **type-safe and statically typed**. Business logic is written imperatively; the UI is **declarative and reactive** — you describe what the UI looks like based on state, and the runtime keeps it in sync automatically. The language itself is host-independent; a host embeds it and supplies the real-time audio/MIDI layer underneath.

Files use the `.kscript` extension. Each file is a **module**. The entry point of an instrument is an exported `main` variable of type `Component`:
Files use the `.kscript` extension. Each file is a **module**. The entry point of a Komplete Script program is the main module's exported `main` variable of type `Component`:

```kscript
import { Text } from ui

export var main: Component = Text("Hello, world!")
```

`print(...)` is always available without import (output goes to Creator Tools).
`print(...)` is always available without import (output goes to the host's log channel).

## Comments

Expand Down Expand Up @@ -411,7 +411,7 @@ class BoundingBox {
- Multiple constructors are allowed if their parameter signatures differ.
- Constructor parameters follow function parameter rules (labels, `_`, defaults).

### Method Overloading (Kontakt 8.12)
### Method Overloading (kscript 1.9)

A class can define multiple methods with the same name, as long as their parameters differ — in number, in types, or in argument labels. The compiler selects the matching overload based on the arguments at the call site:

Expand Down Expand Up @@ -439,7 +439,7 @@ Methods that differ only in their return type are not valid overloads. Overloadi

Interpolating an instance prints its stored properties: `Vec2(x: 3.0, y: 4.0)` (computed properties excluded). Define `to_string() -> (String)` to customize.

### Subscript Operator (Kontakt 8.11)
### Subscript Operator (kscript 1.8)

Define a method named `at` with exactly one parameter to enable `obj[expr]` reads; define `assign` with exactly two parameters (value first, then subscript) and no return value to enable `obj[expr] = value`. Both are also callable directly as normal methods.

Expand Down Expand Up @@ -695,7 +695,7 @@ A component body may produce multiple sibling components (directly, or via decla

### Exporting

`export component Name { … }` exports a component. The instrument entry point is `export var main: Component = SomeComponent()`.
`export component Name { … }` exports a component. The program entry point is `export var main: Component = SomeComponent()`.

## Templates

Expand Down Expand Up @@ -874,11 +874,11 @@ import { controls.knob.Knob as Knob } from my_module

### Directory Structure

Modules may live in subdirectories; import paths are dot-separated and **always resolved from the project root** (the `komplete_scripts` directory), never relative to the importing file:
Modules may live in subdirectories; import paths are dot-separated and **always resolved from the project root** (the host's script directory — `komplete_scripts/` in Kontakt), never relative to the importing file:

```kscript
import * from components.tabbar
import * from kontakt_components.base.button_base
import * from shared.base.button_base
```

### Folder Modules
Expand Down Expand Up @@ -937,7 +937,7 @@ export component Checkbox {
### Reactivity Pitfalls

1. **`var` in components does not work** — a component reading a global `var` in an expression will never update when the `var` changes. Use component state or a class property instead.
2. **UI code must be side-effect free.** The order and count of re-evaluations is NOT guaranteed and may change between Kontakt versions. Never mutate anything (including globals) inside computed state/properties.
2. **UI code must be side-effect free.** The order and count of re-evaluations is NOT guaranteed and may change between language versions. Never mutate anything (including globals) inside computed state/properties.
3. **Never create components from functions** (`(Args) -> (Component)` properties or method calls in the body) — the component is recreated whenever any argument changes. Use `Template(…)` instead.

## Layout Fundamentals
Expand Down Expand Up @@ -1058,46 +1058,6 @@ With a tap and a drag on the same component: a pointer-down triggers the tap's `

`DragGesture(minimum_distance: 0, …)` normally starts on pointer-down, **except** when a higher-priority tap gesture exists — then the drag starts on first movement so the tap can still fire. If the zero-threshold drag is listed first (higher priority), the tap never triggers.

## Kontakt Integration Pattern

Connect to KSP controls via the `kontakt` package. KSP connections are fixed at load time, so declare them **globally**, not inside a component:

```kscript
import { VStack, Text, Arc, DragGesture, Padding } from ui
import { KSPKnob } from kontakt

var reverb_knob = KSPKnob(id: "reverb") // connects to KSP ui_knob "reverb"

component ReverbSend {
accumulated_delta: Float = 0.0 // carries sub-integer drag remainder

VStack(spacing: 4) {
Arc(
color: Color(0xFF000000),
start_angle: Angle(degrees: 135),
angle: Angle(degrees: reverb_knob.normalized_value * 270),
) with {
DragGesture(fun (event) {
var dy = event.delta.y / event.frame.height
self.accumulated_delta = self.accumulated_delta - dy * (reverb_knob.max - reverb_knob.min)
var steps = self.accumulated_delta.to_int()
if steps != 0 {
reverb_knob.value = (reverb_knob.value + steps).clamped(min: reverb_knob.min, max: reverb_knob.max)
self.accumulated_delta = self.accumulated_delta - steps
}
})
}
Text(reverb_knob.label) // reactive: updates with KSP label
Text("\{reverb_knob.value}") // reactive: updates with KSP value
} with {
Padding(16)
}
}

export var main: Component = ReverbSend()
```

`KSPKnob` exposes reactive `value` (Int), `min`, `max`, `normalized_value` (Float 0.0–1.0), and `label`. The `kontakt controls` package provides ready-made `Knob`/`Slider` components wrapping this pattern. Colors are constructed as `Color(0xAARRGGBB)` (e.g. `Color(0xFFFF0000)` opaque red).

## Gotchas (differences from mainstream languages)

Expand All @@ -1123,13 +1083,12 @@ export var main: Component = ReverbSend()
20. **UI code must be side-effect free** — re-evaluation order/count is unspecified; any value read in a reactive expression becomes a tracked dependency (even reads inside `print`).
21. **Map lookups always return optionals; assigning `nil` deletes the key.** Force unwrap (`!`) on `nil` is a runtime crash.
22. **Empty collection literals need type annotations**; empty map is `[:]`, not `{}` or `[]`.
23. **Import paths resolve from the project root** (`komplete_scripts/`), never relative to the current file. Module filenames must be lowercase (letters, digits, underscores). Nested-namespace imports require `as` renames.
23. **Import paths resolve from the project root** (the host's script directory), never relative to the current file. Module filenames must be lowercase (letters, digits, underscores). Nested-namespace imports require `as` renames.
24. **Block comments nest.**
25. **`===` (identity) exists only for class instances**; `==` compares values.
26. **KSP connections (e.g. `KSPKnob`) must be declared globally** — they are fixed at load time and don't belong inside components.
27. **Higher (later) siblings block lower siblings' gestures entirely** — you cannot combine gestures across siblings.
28. Float literals need digits on both sides of the dot (`0.5`, not `.5`; `1.0`, not `1.`).
29. **Case is enforced**: symbols (variables, properties, functions, enum cases) must start lowercase; types (classes, components, modifiers, enums, aliases) must start uppercase. `var TITLE = ""` does not compile.
30. **No direct chaining onto a constructor call** — `Color(0xFFFFFFFF).opacity(0.5)` is illegal; write `(Color(0xFFFFFFFF)).opacity(0.5)` or bind to a variable first.
31. **Member order is enforced.** Classes: properties → constructors → methods. Components/modifiers: properties/bindings → constructors → state/methods → child components last.
32. **No `var` inside templates or trailing `{ … }` children blocks** — those take component expressions only. Pass values in via template parameters, properties, or state.
26. **Higher (later) siblings block lower siblings' gestures entirely** — you cannot combine gestures across siblings.
27. Float literals need digits on both sides of the dot (`0.5`, not `.5`; `1.0`, not `1.`).
28. **Case is enforced**: symbols (variables, properties, functions, enum cases) must start lowercase; types (classes, components, modifiers, enums, aliases) must start uppercase. `var TITLE = ""` does not compile.
29. **No direct chaining onto a constructor call** — `Color(0xFFFFFFFF).opacity(0.5)` is illegal; write `(Color(0xFFFFFFFF)).opacity(0.5)` or bind to a variable first.
30. **Member order is enforced.** Classes: properties → constructors → methods. Components/modifiers: properties/bindings → constructors → state/methods → child components last.
31. **No `var` inside templates or trailing `{ … }` children blocks** — those take component expressions only. Pass values in via template parameters, properties, or state.
Loading