> ## Documentation Index
> Fetch the complete documentation index at: https://noorle.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Project structure

> What a plugin project contains — wit/world.wit, the implementation, noorle.yaml, build.sh — and which file drives what.

Every plugin project, in any language, carries the same four things: an
interface, an implementation, a configuration file, and a build script. This
page describes them and what the platform does with each.

<Note>
  The code below is the Rust template, which
  `noorle plugin init <name> --template rust` scaffolds. The per-language layouts
  and build commands are under [Languages](/docs/build/languages/rust); everything on
  this page about `wit/world.wit`, `noorle.yaml`, and the archive applies to all
  of them.
</Note>

## Layout

```
my-plugin/
├── wit/
│   └── world.wit      the interface — defines your tools
├── src/
│   └── lib.rs         the implementation
├── noorle.yaml        metadata, permissions, credentials
├── Cargo.toml         Rust dependencies
├── build.sh           build script, called by `noorle plugin build`
└── dist/              build output — the .npack archive
```

Build with `noorle plugin build`, not with `cargo` directly — the CLI drives
`build.sh` and packages the archive.

## `wit/world.wit` — the interface

The WIT world defines your tools: their names, parameter types, and return
types. Two things follow from it.

**Exported functions become tools.** Discovery walks the compiled component's
exports, so what you export is what agents see.

**Doc comments become tool descriptions.** The `///` line above an export is
the text a model reads when deciding whether to call it. Write it for the
model, not for a compiler.

```wit theme={null}
package example:weather;

world weather-component {
    enum unit { metric, imperial }

    record weather-response {
        location: string,
        temperature: f64,
        humidity: option<u32>,
        weather-conditions: list<string>,
    }

    /// Check the current weather for a location
    export check-weather: func(location: string, unit: unit) -> result<weather-response, string>;
}
```

Return `result<T, string>` so failures reach the caller as errors rather than
as a success carrying an error string.

The `.wit` file is packaged into the archive and stored with the version, but
it is **not** what produces the tool schema — the schema is generated from the
component's own types. Shipping the file gets you descriptions; omitting it
gets you tools with no descriptions.

## `src/lib.rs` — the implementation

`wit_bindgen` generates a `Guest` trait from the world; you implement it and
export the implementing type.

```rust theme={null}
#![allow(unsafe_op_in_unsafe_fn)]
wit_bindgen::generate!({ world: "weather-component", path: "./wit" });

use waki::Client;

struct WeatherComponent;

impl Guest for WeatherComponent {
    fn check_weather(location: String, unit: Unit) -> Result<WeatherResponse, String> {
        // ...
    }
}

export!(WeatherComponent);
```

* **Outbound HTTP** goes through `waki`. `reqwest` does not compile to a WASI
  component.
* **Environment variables** are read with `std::env::var`, and only the keys
  you list under `permissions.environment` are visible.
* **Files** live under the mounts you grant — `/workspace/input`,
  `/workspace/output`, `/workspace/home`. There is no host filesystem behind
  them; see [Permissions](/docs/build/plugins/permissions).
* **Avoid `unsafe`.**

## `noorle.yaml` — configuration

Metadata, the permission policy, and credential declarations. This is the file
that decides what your plugin can reach at runtime; without it, the plugin
runs with no network, no storage, and no environment access.

```yaml theme={null}
schema_version: "1.0"
metadata:
  name: weather
  description: "Fetches current weather for a location"
permissions:
  network:
    allow:
      - host: "api.openweathermap.org"
  environment:
    allow:
      - key: OPENWEATHER_API_KEY
```

Full key-by-key reference: [noorle.yaml](/docs/build/plugins/configuration).

## `Cargo.toml`

```toml theme={null}
[package]
name = "weather"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
waki = "0.5"
wit-bindgen = "0.46.0"
```

`crate-type = ["cdylib"]` is required — without it the build produces
something that is not a component, and admission validation rejects it.

## What ends up in the archive

`noorle plugin build` writes a `.npack` to `dist/`. When the platform unpacks
it, it looks for four things by filename pattern:

| Pattern                      | Kept as                       | Required |
| ---------------------------- | ----------------------------- | -------- |
| any `*.wasm`                 | the component                 | **yes**  |
| `noorle.yaml` / `noorle.yml` | the policy and metadata       | no       |
| any `*.wit`                  | tool descriptions             | no       |
| `.env` / `env`               | environment variable defaults | no       |

Everything else in the archive is ignored. **Your source is not uploaded** —
the upload path stores the component, the manifest, the environment defaults,
and the WIT file, and nothing else. (Plugin versions produced by Plugin
Builder do keep a source archive, so a later build can iterate on them.)

## Size

There is no minimum, and no hard maximum below the upload limit. Validation
warns above **20 MB** for the component binary — a warning, not a rejection.

If you approach it, reach for your toolchain's size levers: in Rust, release
settings such as an `opt-level` tuned for size, LTO, and symbol stripping; in
Go, the `-opt=2 -no-debug` flags TinyGo already builds with; in Python and
JavaScript, trimming dependencies, since the runtime is bundled into the
component.

## Next

* [noorle.yaml reference](/docs/build/plugins/configuration)
* [Permissions](/docs/build/plugins/permissions)
* [Publishing and versions](/docs/build/plugins/publishing)
