Back to the catalog

Ix Cli Core Specification

Index of artifacts in this directory.

Open source Repository Open in the app JSON README (API)

About

# Ix Cli Core Specification

## Contents

- [Functional](./functional/index.md) - Functional artifacts.
- [Non Functional](./non-functional/index.md) - Non Functional artifacts.
- [Stakeholder](./stakeholder/index.md) - Stakeholder artifacts.
- [Usecase](./usecase/index.md) - Usecase artifacts.
- [Spec](./spec.md)

Details

Kind
OKF bundles
Topic
Developer tools
Publisher
agent-ix
Origin
okf_github
Category
dados
Version
0.1
Stars
1
Last push
2026-09-06T08:43:51Z
Repository state
ativo
Language
TypeScript
License
MIT
Added
2026-09-08 09:01:12
Updated
2026-09-08 09:01:12
Origin id
agent-ix/ix-cli-core:spec/index.md

README

# @agent-ix/ix-cli-core

> Generic framework foundation for building Agent IX CLIs — config service, secrets service, plugin contract, runtime.

`ix-cli-core` is the shared substrate every Agent IX command-line tool is built on. It takes the parts of a CLI that are tedious and easy to get wrong — typed configuration, secret storage, login, plugin loading, command wiring — and provides them as a single, batteries-included library on top of [oclif](https://oclif.io/). Your CLI declares _what_ it needs; the framework handles _how_ it is stored, validated, resolved, and secured.

```bash
pnpm add @agent-ix/ix-cli-core
```

> Requires `@oclif/core` (peer dependency) `>= 4.11.4`.

---

## Features

### 🗂 Typed configuration

Plugin-scoped configuration files backed by [Zod](https://zod.dev/) schemas. Each plugin owns its own YAML file under the user's config directory, and every read is validated and type-safe.

- **Schema-validated** — values are parsed against a strict Zod schema; unknown keys are rejected.
- **Layered resolution** — environment variables override file values, which override schema defaults.
- **Project-local overrides** — an in-repo `.ix/` directory can layer per-project settings over the user config.
- **Crash-proof reads** — a corrupt or invalid file never throws into your command; defaults are returned and the problem is recorded for diagnostics.
- **Safe writes** — every write is atomic, permission-locked (`0o600`), and serialized against concurrent writers.
- **Built-in diagnostics** — a `doctor` report surfaces parse, schema, and I/O incidents across all plugins.

### 🔐 Multi-backend secrets

Secret storage that does the right thing on every platform without the caller caring where bytes actually live.

- **OS-native keyring first** — uses the macOS Keychain, Windows Credential Manager, or Linux Secret Service when available.
- **Encrypted file fallback** — transparently falls back to an [age](https://age-encryption.org/)-encrypted on-disk store when no keyring is present.
- **Environment overrides** — a declared env var always wins, so CI and containers can inject secrets without touching disk.
- **Leak-resistant** — secret values are kept out of logs and error messages; only ids and descriptions are ever rendered.

### 🔑 Auth engine (device flow)

A drop-in OAuth 2.0 Device Authorization Grant ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)) login, fully service-agnostic.

- **Service discovery** — reads a `/.well-known/agentix-service.json` document so endpoints, audience, and scopes are never hard-coded.
- **Browser-based approval** — walks the user through verification with a best-effort, non-fatal browser open.
- **Host-isolated tokens** — tokens are keyed per host and stored through the secrets backend; logging into one service never disturbs another.
- **Automatic refresh** — access tokens are refreshed before expiry and rotated transparently.

### 🧩 Plugin contract

A lightweight convention that lets a plugin declare everything the framework needs in one object.

- A plugin exports an `ixSchema` describing its **config schema**, **secrets**, and **env bindings**.
- The host registers it once at startup, and the plugin's config and secrets immediately become visible to `ix config` and `ix secrets`.
- Plugin namespaces are derived safely from the npm package name when not given explicitly.

### ⚙️ Base command + capabilities

An oclif base command that every command in your CLI extends to inherit framework wiring for free.

- **Standard flags** — `--config-root` and `--no-project-config` are parsed natively, no argv hacks.
- **Ready-to-use services** — config and secrets are available without per-command boilerplate.
- **Declarative capability checks** — a command states the capabilities it requires (e.g. "needs auth"), and the framework short-circuits with a structured, user-friendly error when they are unavailable.

### 📦 Plugin marketplace

A thin adapter over [`@agent-ix/ts-plugin-kit`](https://github.com/agent-ix/ts-plugin-kit) for installing and reconciling plugins from typed git sources, with a cache layout under the standard cache directory.

### 🛡 Safe filesystem & XDG paths

The primitives the rest of the framework is built on: XDG-compliant config and cache locations, and atomic writes that enforce `0o600` permissions and refuse to follow symlinks.

---

## Usage Guide

This walkthrough builds up a CLI on the framework, one subsystem at a time. Everything is imported from the package root:

```ts
import {
  ConfigService,
  registerPluginSchema,
  registerSecret,
  SecretsService,
  defaultSecretsService,
  fetchServiceDiscovery,
  runDeviceFlow,
  TokenStore,
  BaseCommand,
} from "@agent-ix/ix-cli-core";
import { z } from "zod";
```

### 1. Declare your plugin schema

A plugin describes its config, secrets, and env bindings in a single `ixSchema` object and registers it at startup. Config schemas must be **strict** Zod objects.

```ts
const ixSchema = {
  id: "deploy", // config/secrets namespace; derived from the package name if omitted
  config: z
    .object({
      region: z.string().default("us-east-1"),
      replicas: z.coerce.number().default(1),
    })
    .strict(),
  secrets: [
    {
      name: "api-token",
      description: "Deploy API token",
      envVar: "DEPLOY_API_TOKEN",
    },
  ],
  env: { region: "DEPLOY_REGION" }, // config key → env var
};

const result = registerPluginSchema("@acme/ix-cli-deploy", ixSchema);
if (!result.ok) {
  // first-wins, non-throwing: inspect result.kind / result.detail
  console.warn(`plugin schema not registered: ${result.detail}`);
}
```

Registering the schema also wires its config and secrets into the global registries, so `ix config` and `ix secrets` see the plugin immediately.

### 2. Read and write typed config

Get a typed accessor scoped to one plugin id. `get()` resolves env → file → defaults; `set()` deep-merges and atomically rewrites.

```ts
const config = ConfigService.forPlugin("deploy", ixSchema.config, {
  envBindings: { region: "DEPLOY_REGION" },
});

const current = config.get(); // { region: string; replicas: number } — fully typed
config.set({ replicas: 3 }); // validated, atomic, lock-serialized write
config.replace({ region: "eu-west-1", replicas: 2 }); // overwrite (can remove keys)
config.reset(); // delete the file; get() then returns defaults
```

Diagnose problems across all registered plugins:

```ts
import { doctor } from "@agent-ix/ix-cli-core";

const report = doctor(); // parse / schema / io incidents, per plugin
```

### 3. Store and read secrets

Secrets are declared (so they appear in `ix secrets list`) and then read or written through a `SecretsService`. The framework picks the backend; in most commands you use the process-global default.

```ts
registerSecret("deploy", {
  name: "api-token",
  description: "Deploy API token",
  envVar: "DEPLOY_API_TOKEN",
});

const secrets = defaultSecretsService();

await secrets.set("deploy.api-token", "s3cr3t"); // refuses if env var is shadowing
const token = await secrets.get("deploy.api-token"); // env var > backend > null
const source = await secrets.which("deploy.api-token"); // "env" | "keyring" | "age-file" | "unset"
```

`get()` returns the env-var value first when the secret's `envVar` is set, then falls back to the active backend, then `null`.

### 4. Wire up login

Discover the service, run the device flow, and persist the resulting tokens in a host-keyed `TokenStore` (backed by the secrets service).

```ts
const discovery = await fetchServiceDiscovery("https://api.example.com");

const bundle = await runDeviceFlow(discovery, {
  clientId: "my-cli",
  prompter: {
    showVerification({ approvalUri, userCode }) {
      console.log(`Visit ${approvalUri} and enter code ${userCode}`);
    },
  },
});

const tokens = new TokenStore({ secrets: defaultSecretsService() });
await tokens.save("https://api.example.com", bundle);

// Later, in any command — refreshed automatically when near expiry:
const accessToken = await tokens.getAccessToken("https://api.example.com");
```

By default `runDeviceFlow` opens the verification URI in a browser (non-fatal) and presents the prompt through the `prompter` you supply, so your CLI owns all rendering.

### 5. Author a command

Extend `BaseCommand` to inherit the global flags and runtime context, and declare any capabilities the command requires. A missing required capability short-circuits the command with a structured error before `run()` executes.

```ts
export default class Deploy extends BaseCommand {
  static override description = "Deploy the current project";

  static override capabilities = {
    required: ["ix-api"], // resolved in prerun(); fails fast if unavailable
    optional: ["github"],
  } as const;

  async run(): Promise<void> {
    const config = ConfigService.forPlugin("deploy", ixSchema.config);
    const { region, replicas } = config.get();
    this.log(`Deploying ${replicas} replica(s) to ${region}…`);

    if (this.hasCapability("github")) {
      // optional capability is available — do the extra thing
    }
  }
}
```

### 6. Install marketplace plugins (optional)

For CLIs that load plugins from typed git sources, the marketplace adapter provides install options and a reconcile pass over a default set:

```ts
import {
  marketplaceInstallOptions,
  reconcileDefaultSet,
} from "@agent-ix/ix-cli-core";

const opts = marketplaceInstallOptions();
await reconcileDefaultSet(/* targets */);
```

---

## Related projects

- **[ix-cli](https://github.com/agent-ix/ix-cli)** — the canonical Agent IX CLI (`ix`), built on this framework. _(Currently private; will be made public soon.)_
- **[quoin](https://github.com/agent-ix/quoin)** — the spec authoring and validation toolchain for the Agent IX ecosystem.
- **[ix-flow](https://github.com/agent-ix/ix-flow)** — the agent-driven workflow lifecycle runner.

---

## Development

This project uses **pnpm** with **Corepack**.

### Prerequisites

- Node.js 20+
- Corepack enabled (`corepack enable`)

### Setup

```bash
pnpm install      # install dependencies
pnpm run build    # build the library (vite)
pnpm run test     # run the test suite (vitest)
```

### Scripts

| Command           | Description                             |
| ----------------- | --------------------------------------- |
| `pnpm run build`  | Compile the library                     |
| `pnpm test`       | Run tests                               |
| `pnpm run lint`   | Run ESLint + Prettier check             |
| `pnpm run format` | Run Prettier                            |
| `pnpm run clean`  | Remove build artifacts and node_modules |

A `Makefile` is provided for convenience and delegates to the equivalent `pnpm run` scripts (`make build`, `make test`, `make lint`, …).

---

## License

[MIT](./LICENSE) © Agent IX

More