oatty 0.1.0-placeholder.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ ![Oatty Logo](assets/branding/oatty-lockup-github-v6.1.svg)
2
+
3
+ [![CI](https://github.com/oattyio/oatty/actions/workflows/ci.yml/badge.svg)](https://github.com/oattyio/oatty/actions/workflows/ci.yml)
4
+ ![Rust](https://img.shields.io/badge/rust-1.93%2B-orange)
5
+ ![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)
6
+ ![Status](https://img.shields.io/badge/status-experimental-yellow)
7
+
8
+ # Oatty - Schema-driven CLI + TUI + MCP
9
+
10
+ A vendor-agnostic operations surface for teams that are tired of juggling near-identical CLIs, partial API coverage, and
11
+ separate MCP servers with different constraints.
12
+
13
+ Oatty turns OpenAPI documents into runnable commands, lets you execute them from one consistent CLI/TUI, and extends
14
+ that surface with MCP tools and reusable workflows.
15
+
16
+ ## Why Oatty Exists
17
+
18
+ Modern developer tooling has a strange paradox:
19
+ the APIs are powerful and well-documented, but the tools built on top of them are fragmented, incomplete, and
20
+ inconsistent.
21
+
22
+ Most vendor CLIs are thin wrappers around an API with a small set of hand-crafted workflows.
23
+ When you work across vendors, the experience converges into the same problems:
24
+
25
+ - Nearly identical commands with different naming conventions
26
+ - Partial API coverage that forces you back to curl or custom scripts
27
+ - Separate MCP servers that expose even *less* functionality than the CLI
28
+ - Automation that lives in brittle scripts instead of reusable, inspectable workflows
29
+
30
+ Oatty was built to collapse this complexity into **one coherent operational surface**.
31
+
32
+ ---
33
+
34
+ ## Core UX Principles
35
+
36
+ Oatty is not just a CLI replacement — it is a **carefully designed terminal experience** built around four core
37
+ principles.
38
+
39
+ ### 1. Discoverability
40
+
41
+ You should never need to memorize commands.
42
+
43
+ - Every command, workflow, and option is searchable and browsable.
44
+ - The TUI acts as a living catalog derived directly from schemas and plugins.
45
+ - If the API supports it, you can *find it* — even if you’ve never used it before.
46
+
47
+ This makes Oatty approachable for new users and powerful for experts.
48
+
49
+ ---
50
+
51
+ ### 2. Simplicity
52
+
53
+ Each screen does **one thing**, clearly.
54
+
55
+ - Search commands
56
+ - Inspect details
57
+ - Run an action
58
+ - Monitor progress or results
59
+
60
+ There is no clutter, no overloaded views, and no hidden modes.
61
+ Familiar interaction patterns (lists, tables, forms, logs) keep cognitive load low, even when the underlying task is
62
+ complex.
63
+
64
+ ---
65
+
66
+ ### 3. Speed
67
+
68
+ Oatty is designed for real work, not demos.
69
+
70
+ - Fast startup and low-latency interactions
71
+ - Keyboard-first navigation with the mouse interactions you'd expect from a beautiful terminal UI
72
+ - Reliable execution paths for long-running workflows and streaming output
73
+
74
+ Power users can move as fast as they can type — without sacrificing safety or clarity.
75
+
76
+ ---
77
+
78
+ ### 4. Consistency
79
+
80
+ Once you learn Oatty, you’ve learned every vendor's CLI, workflow, and MCP server.
81
+
82
+ - The same command model powers the CLI, the TUI, workflows, and MCP tools
83
+ - Flags, arguments, outputs, and behaviors follow consistent patterns
84
+ - Vendor-specific quirks are normalized behind a shared execution model
85
+
86
+ This consistency dramatically reduces context switching and relearning.
87
+
88
+ ---
89
+
90
+ ## The Big Idea
91
+
92
+ Treat vendor APIs and MCP servers as **inputs**, not product boundaries.
93
+
94
+ Oatty ingests OpenAPI schemas to build a runtime command catalog, exposes that catalog through a unified CLI and TUI,
95
+ and allows vendors or teams to extend it via MCP — all while enabling first-class, filesystem-backed workflows.
96
+
97
+ Instead of maintaining:
98
+
99
+ - N vendor CLIs
100
+ - N MCP servers
101
+ - N incompatible automation approaches
102
+
103
+ You get **one interface**, one mental model, and one place to operate.
104
+
105
+ ---
106
+
107
+ ## What This Unlocks
108
+
109
+ - Full API coverage without waiting for vendors to implement it their CLI
110
+ - Workflows that span multiple vendors and MCP servers
111
+ - A single MCP surface instead of fragmented plugin ecosystems
112
+ - Shareable, reviewable workflows instead of opaque scripts
113
+ - A terminal UX that scales from quick commands to complex operations
114
+
115
+ ## Quick Example
116
+
117
+ Use one interface for API-derived commands and workflow execution:
118
+
119
+ ```bash
120
+ # Start interactive mode
121
+ cargo run -p oatty
122
+
123
+ # Execute a command from a loaded catalog
124
+ cargo run -p oatty -- apps list
125
+
126
+ # Run a workflow from runtime storage
127
+ cargo run -p oatty -- workflow list
128
+ ```
129
+
130
+ ## Usage
131
+
132
+ - Build: `cargo build --workspace`
133
+ - Run TUI (no args): `cargo run -p oatty` (or the installed binary `oatty`)
134
+ - CLI examples:
135
+ - `cargo run -p oatty -- apps list`
136
+ - `cargo run -p oatty -- apps info my-app`
137
+ - `cargo run -p oatty -- workflow list`
138
+ - `cargo run -p oatty -- workflow preview --file workflows/create_app_and_db.yaml`
139
+
140
+ Note: available commands depend on which registry catalogs and MCP plugins are configured/enabled.
141
+
142
+ ## NPM Distribution
143
+
144
+ Oatty can be installed via npm and will download the matching prebuilt binary for your platform during `postinstall`.
145
+
146
+ ```bash
147
+ npm i -g oatty
148
+ oatty --help
149
+ ```
150
+
151
+ How it works:
152
+
153
+ - npm installs a small Node launcher package (`oatty`).
154
+ - The installer detects your OS/arch and fetches the matching GitHub release asset for the npm package version.
155
+ - The download is verified against the release `SHA256SUMS` before extraction.
156
+
157
+ Currently mapped platforms:
158
+
159
+ - macOS `x64` -> `x86_64-apple-darwin`
160
+ - macOS `arm64` -> `aarch64-apple-darwin`
161
+ - Linux `x64` -> `x86_64-unknown-linux-gnu`
162
+ - Windows `x64` -> `x86_64-pc-windows-msvc`
163
+
164
+ If your platform is not currently mapped, install from source (`cargo build --release`) or use a directly downloaded binary.
165
+
166
+ Maintainers:
167
+
168
+ - GitHub release assets are built/published via `.github/workflows/release.yml`.
169
+ - npm publication is handled by `.github/workflows/npm-publish.yml` when a non-prerelease GitHub Release is published and npm Trusted Publishing is configured.
170
+
171
+ ### First-time: import a registry catalog
172
+
173
+ If you have no catalogs configured yet, the fastest way to get started is via the TUI:
174
+
175
+ 1. Run `cargo run -p oatty`.
176
+ 2. In the Library view, import a local OpenAPI document (e.g., `schemas/samples/render-public-api.json`) or a URL.
177
+ 3. Accept the default command prefix (or enter your own).
178
+ 4. The registry configuration is saved to `~/.config/oatty/registry.json` and manifests are stored under
179
+ `~/.config/oatty/catalogs/`.
180
+
181
+ ## Architecture (high level)
182
+
183
+ - **Registry** (`crates/registry`): loads registry catalogs from `~/.config/oatty/registry.json` and reads per-catalog
184
+ manifest files (typically `~/.config/oatty/catalogs/*.bin`).
185
+ - **MCP** (`crates/mcp`): loads `~/.config/oatty/mcp.json`, manages plugin lifecycles, and can inject MCP tool commands
186
+ into the registry at runtime.
187
+ - **CLI** (`crates/cli`): builds a Clap tree from the registry, routes `workflow` commands, and executes HTTP/MCP
188
+ commands.
189
+ - **TUI** (`crates/tui`): interactive UI built on Ratatui/Crossterm; includes in-app help and log panes.
190
+ - **Engine** (`crates/engine`): workflow parsing and execution utilities used by `oatty workflow ...`.
191
+
192
+ ## Environment & Config
193
+
194
+ - `OATTY_LOG`: tracing level for stderr logs in CLI mode (`error`, `warn`, `info` [default], `debug`, `trace`).
195
+ - `TUI_THEME`: theme override for the TUI (`dracula`, `dracula_hc`, `nord`, `nord_hc`, `cyberpunk`, `cyberpunk_hc`,
196
+ `ansi256`, `ansi256_hc`).
197
+ - `MCP_CONFIG_PATH`: overrides MCP config path (default: `~/.config/oatty/mcp.json`).
198
+ - `REGISTRY_CONFIG_PATH`: overrides registry config path (default: `~/.config/oatty/registry.json`).
199
+ - `REGISTRY_CATALOGS_PATH`: overrides the directory where catalog manifest files are stored (default:
200
+ `~/.config/oatty/catalogs`).
201
+ - `REGISTRY_WORKFLOWS_PATH`: overrides workflow manifest storage (default: `~/.config/oatty/workflows`).
202
+
203
+ Authentication headers/tokens are configured per catalog in the Library view (or persisted catalog configuration), not
204
+ via a global API token environment variable.
205
+
206
+ ### Logging behavior
207
+
208
+ - TUI mode silences tracing output to stderr while the UI is active to prevent overlaying the terminal UI; logs are
209
+ routed into in-app panes where applicable.
210
+ - CLI mode writes logs to stderr as usual.
211
+
212
+ ## Development
213
+
214
+ - Toolchain: `rust-toolchain.toml` pins the project to Rust `stable`.
215
+ - Workspace crates: `cli`, `tui`, `registry`, `registry-gen`, `engine`, `api`, `util`, `types`, `mcp`.
216
+ - Common commands:
217
+ - Build: `cargo build --workspace`
218
+ - Test: `cargo test --workspace`
219
+ - Lint: `cargo clippy --workspace -- -D warnings`
220
+ - Format: `cargo fmt --all`
221
+
222
+ ### Registry catalogs (manifests)
223
+
224
+ - Generator crate: `crates/registry-gen` (`oatty-registry-gen`) can derive a manifest from OpenAPI documents.
225
+ - At runtime, the registry reads catalog manifests from paths referenced in `~/.config/oatty/registry.json`.
226
+
227
+ ## Registry Generator (Library)
228
+
229
+ The registry is derived from OpenAPI documents using the `registry-gen` crate.
230
+
231
+ ### Library Usage
232
+
233
+ Add a dependency on the generator crate from within the workspace:
234
+
235
+ ```toml
236
+ [dependencies]
237
+ oatty-registry-gen = { path = "crates/registry-gen" }
238
+ ```
239
+
240
+ Generate a manifest file (postcard):
241
+
242
+ ```rust
243
+ use std::path::PathBuf;
244
+ use oatty_registry_gen::{io::ManifestInput, write_manifest};
245
+
246
+ fn main() -> anyhow::Result<()> {
247
+ let schema = PathBuf::from("schemas/samples/render-public-api.json");
248
+ let output = PathBuf::from("target/manifest.bin");
249
+ write_manifest(ManifestInput::new(Some(schema), None, None), output)?;
250
+ Ok(())
251
+ }
252
+ ```
253
+
254
+ Generate a manifest file (JSON):
255
+
256
+ ```rust
257
+ use std::path::PathBuf;
258
+ use oatty_registry_gen::{io::ManifestInput, write_manifest_json};
259
+
260
+ fn main() -> anyhow::Result<()> {
261
+ let schema = PathBuf::from("schemas/samples/render-public-api.json");
262
+ let output = PathBuf::from("target/manifest.json");
263
+ write_manifest_json(ManifestInput::new(Some(schema), None, None), output)?;
264
+ Ok(())
265
+ }
266
+ ```
267
+
268
+ Derive commands in-memory from an OpenAPI document:
269
+
270
+ ```rust
271
+ use oatty_registry_gen::openapi::{derive_commands_from_openapi, derive_vendor_from_document};
272
+
273
+ fn load_commands(openapi_json: &str) -> anyhow::Result<Vec<oatty_types::CommandSpec>> {
274
+ let document: serde_json::Value = serde_json::from_str(openapi_json)?;
275
+ let vendor = derive_vendor_from_document(&document);
276
+ let cmds = derive_commands_from_openapi(&document, &vendor)?;
277
+ Ok(cmds)
278
+ }
279
+ ```
280
+
281
+ ## Flow
282
+
283
+ ```mermaid
284
+ flowchart LR
285
+ subgraph Runtime
286
+ A[oatty no args] -->|Launch| TUI[TUI]
287
+ A2[oatty group cmd] -->|Parse| CLI[CLI Router]
288
+ end
289
+
290
+ subgraph Registry
291
+ S[OpenAPI Document] --> D["Derive Manifest (registry-gen)"]
292
+ D --> M["Catalog Manifest (.bin)"]
293
+ M --> RC[Registry Catalogs]
294
+ RC --> C[Clap Tree]
295
+ end
296
+
297
+ TUI -->|Search/Select| C
298
+ TUI -->|Compose| RUN[Execute]
299
+ CLI -->|Match| C
300
+ CLI -->|Execute| RUN
301
+
302
+ RUN -->|HTTP| HTTP[Oatty API]
303
+ RUN -->|MCP| MCP[MCP Tool]
304
+ HTTP --> OUT2[Status + Body]
305
+ MCP --> OUT2
306
+
307
+ subgraph Policy
308
+ REDACT[Redact secrets in output]
309
+ end
310
+ OUT2 --> REDACT
311
+ RUN --> LOGS[Logs Panel / stdout]
312
+ ```
313
+
314
+ ## Security
315
+
316
+ - Secrets are redacted from output by default (headers/payload in output and logs).
317
+ - Network calls go through reqwest with default timeouts.
318
+ - Release artifact verification guide: `VERIFY.md`.
319
+ - Release publish secrets setup: `RELEASE_SECRETS.md`.
320
+
321
+ ## Status
322
+
323
+ - Schema-driven registry, CLI router, TUI, MCP plugin engine, and workflow support are implemented. Some HTTP client
324
+ behaviors (retries/backoff) are minimal.
325
+
326
+ ## Theme Architecture
327
+
328
+ - Location: `crates/tui/src/ui/theme` (roles, helpers, Dracula/Nord themes)
329
+ - Docs: `specs/THEME.md` (theme mapping, usage, and guidelines)
330
+ - Select theme via env var `TUI_THEME`:
331
+ - `dracula` (default), `dracula_hc`
332
+ - `nord`, `nord_hc`
333
+ - `cyberpunk`, `cyberpunk_hc`
334
+ - `ansi256`, `ansi256_hc`
335
+ - Example: `TUI_THEME=dracula cargo run -p oatty`
336
+
337
+ ## Release Trust
338
+
339
+ - Artifact/user verification steps: `VERIFY.md`
340
+ - CI publish setup (npm Trusted Publishing): `RELEASE_SECRETS.md`
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("node:path");
5
+ const fs = require("node:fs");
6
+ const { spawn } = require("node:child_process");
7
+
8
+ const binaryPath = path.join(__dirname, process.platform === "win32" ? "oatty.exe" : "oatty");
9
+
10
+ if (!fs.existsSync(binaryPath)) {
11
+ process.stderr.write(
12
+ "[oatty] Binary not found. Reinstall with `npm rebuild oatty` or verify release assets exist for your platform.\n"
13
+ );
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(binaryPath, process.argv.slice(2), {
18
+ stdio: "inherit",
19
+ env: process.env,
20
+ cwd: process.cwd()
21
+ });
22
+
23
+ child.on("exit", (code, signal) => {
24
+ if (signal) {
25
+ process.kill(process.pid, signal);
26
+ return;
27
+ }
28
+ process.exit(code ?? 1);
29
+ });
30
+
31
+ child.on("error", (error) => {
32
+ process.stderr.write(`[oatty] Failed to launch binary at ${binaryPath}: ${error.message}\n`);
33
+ process.exit(1);
34
+ });
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ const PLATFORM_TARGETS = {
4
+ darwin: {
5
+ x64: { target: "x86_64-apple-darwin", archiveExtension: ".zip", binaryName: "oatty" },
6
+ arm64: { target: "aarch64-apple-darwin", archiveExtension: ".zip", binaryName: "oatty" }
7
+ },
8
+ linux: {
9
+ x64: { target: "x86_64-unknown-linux-gnu", archiveExtension: ".tar.gz", binaryName: "oatty" }
10
+ },
11
+ win32: {
12
+ x64: { target: "x86_64-pc-windows-msvc", archiveExtension: ".zip", binaryName: "oatty.exe" }
13
+ }
14
+ };
15
+
16
+ function resolvePlatformTarget(platform, architecture) {
17
+ const platformEntry = PLATFORM_TARGETS[platform];
18
+ if (!platformEntry) {
19
+ return null;
20
+ }
21
+
22
+ return platformEntry[architecture] || null;
23
+ }
24
+
25
+ module.exports = {
26
+ resolvePlatformTarget
27
+ };
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const fsp = require("node:fs/promises");
5
+ const https = require("node:https");
6
+ const path = require("node:path");
7
+ const crypto = require("node:crypto");
8
+ const os = require("node:os");
9
+ const tar = require("tar");
10
+ const AdmZip = require("adm-zip");
11
+ const { resolvePlatformTarget } = require("./platform");
12
+
13
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
14
+ const BIN_DIRECTORY = path.join(PACKAGE_ROOT, "npm", "bin");
15
+ const OUTPUT_EXECUTABLE_PATH = path.join(BIN_DIRECTORY, process.platform === "win32" ? "oatty.exe" : "oatty");
16
+ const OUTPUT_MARKER_PATH = path.join(BIN_DIRECTORY, ".oatty-installed-version");
17
+ const RELEASE_REPOSITORY = process.env.OATTY_NPM_RELEASE_REPOSITORY || "oattyio/oatty";
18
+ const RELEASE_TAG_PREFIX = process.env.OATTY_NPM_RELEASE_TAG_PREFIX || "v";
19
+ const RELEASE_BASE_URL = process.env.OATTY_NPM_RELEASE_BASE_URL || `https://github.com/${RELEASE_REPOSITORY}/releases/download`;
20
+
21
+ async function main() {
22
+ const packageJsonPath = path.join(PACKAGE_ROOT, "package.json");
23
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
24
+ const packageVersion = packageJson.version;
25
+ const releaseTag = `${RELEASE_TAG_PREFIX}${packageVersion}`;
26
+
27
+ const platformTarget = resolvePlatformTarget(process.platform, process.arch);
28
+ if (!platformTarget) {
29
+ emitWarning(
30
+ `No prebuilt oatty binary is published for platform=${process.platform} arch=${process.arch}.` +
31
+ " Skipping binary download."
32
+ );
33
+ return;
34
+ }
35
+
36
+ const assetFileName = `oatty-${releaseTag}-${platformTarget.target}${platformTarget.archiveExtension}`;
37
+ const checksumFileName = "SHA256SUMS";
38
+ const assetUrl = `${RELEASE_BASE_URL}/${releaseTag}/${assetFileName}`;
39
+ const checksumUrl = `${RELEASE_BASE_URL}/${releaseTag}/${checksumFileName}`;
40
+
41
+ await fsp.mkdir(BIN_DIRECTORY, { recursive: true });
42
+ if (await isAlreadyInstalled(packageVersion)) {
43
+ return;
44
+ }
45
+
46
+ const temporaryDirectory = await fsp.mkdtemp(path.join(os.tmpdir(), "oatty-npm-"));
47
+ const archivePath = path.join(temporaryDirectory, assetFileName);
48
+ const checksumPath = path.join(temporaryDirectory, checksumFileName);
49
+ try {
50
+ await downloadToPath(assetUrl, archivePath);
51
+ await downloadToPath(checksumUrl, checksumPath);
52
+ await verifyArchiveChecksum(archivePath, checksumPath, assetFileName);
53
+
54
+ if (platformTarget.archiveExtension === ".tar.gz") {
55
+ await extractTarArchive(archivePath, BIN_DIRECTORY);
56
+ } else {
57
+ extractZipArchive(archivePath, BIN_DIRECTORY);
58
+ }
59
+
60
+ await ensureExecutablePermissions(OUTPUT_EXECUTABLE_PATH);
61
+ await fsp.writeFile(OUTPUT_MARKER_PATH, packageVersion, "utf8");
62
+ } catch (error) {
63
+ emitWarning(`Failed to install oatty binary from ${assetUrl}. ${error.message}`);
64
+ emitWarning("You can retry later with: npm rebuild oatty");
65
+ } finally {
66
+ await fsp.rm(temporaryDirectory, { recursive: true, force: true });
67
+ }
68
+ }
69
+
70
+ async function isAlreadyInstalled(packageVersion) {
71
+ try {
72
+ const version = (await fsp.readFile(OUTPUT_MARKER_PATH, "utf8")).trim();
73
+ const binaryExists = await fileExists(OUTPUT_EXECUTABLE_PATH);
74
+ return version === packageVersion && binaryExists;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ async function verifyArchiveChecksum(archivePath, checksumPath, assetFileName) {
81
+ const expectedChecksum = await readExpectedChecksum(checksumPath, assetFileName);
82
+ const actualChecksum = await hashFileSha256(archivePath);
83
+ if (actualChecksum !== expectedChecksum) {
84
+ throw new Error(`Checksum mismatch for ${assetFileName}. expected=${expectedChecksum} actual=${actualChecksum}`);
85
+ }
86
+ }
87
+
88
+ async function readExpectedChecksum(checksumPath, assetFileName) {
89
+ const checksumContent = await fsp.readFile(checksumPath, "utf8");
90
+ const lines = checksumContent.split(/\r?\n/);
91
+ for (const line of lines) {
92
+ if (!line.trim()) {
93
+ continue;
94
+ }
95
+ const match = line.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
96
+ if (!match) {
97
+ continue;
98
+ }
99
+ const [, checksum, fileName] = match;
100
+ if (fileName.trim() === assetFileName) {
101
+ return checksum.toLowerCase();
102
+ }
103
+ }
104
+ throw new Error(`Checksum for ${assetFileName} not found in SHA256SUMS`);
105
+ }
106
+
107
+ async function hashFileSha256(filePath) {
108
+ const hash = crypto.createHash("sha256");
109
+ await new Promise((resolve, reject) => {
110
+ const stream = fs.createReadStream(filePath);
111
+ stream.on("error", reject);
112
+ stream.on("data", (chunk) => hash.update(chunk));
113
+ stream.on("end", resolve);
114
+ });
115
+ return hash.digest("hex");
116
+ }
117
+
118
+ async function downloadToPath(url, destinationPath) {
119
+ await new Promise((resolve, reject) => {
120
+ const request = https.get(url, (response) => {
121
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
122
+ response.resume();
123
+ downloadToPath(response.headers.location, destinationPath).then(resolve).catch(reject);
124
+ return;
125
+ }
126
+
127
+ if (response.statusCode !== 200) {
128
+ response.resume();
129
+ reject(new Error(`Request failed for ${url}. status=${response.statusCode}`));
130
+ return;
131
+ }
132
+
133
+ const file = fs.createWriteStream(destinationPath);
134
+ response.pipe(file);
135
+ file.on("finish", () => file.close(resolve));
136
+ file.on("error", reject);
137
+ });
138
+
139
+ request.on("error", reject);
140
+ });
141
+ }
142
+
143
+ async function extractTarArchive(archivePath, destinationPath) {
144
+ await tar.x({
145
+ cwd: destinationPath,
146
+ file: archivePath
147
+ });
148
+ }
149
+
150
+ function extractZipArchive(archivePath, destinationPath) {
151
+ const zipArchive = new AdmZip(archivePath);
152
+ zipArchive.extractAllTo(destinationPath, true);
153
+ }
154
+
155
+ async function ensureExecutablePermissions(binaryPath) {
156
+ if (process.platform === "win32") {
157
+ return;
158
+ }
159
+
160
+ if (!(await fileExists(binaryPath))) {
161
+ throw new Error(`Expected binary not found after extraction: ${binaryPath}`);
162
+ }
163
+
164
+ await fsp.chmod(binaryPath, 0o755);
165
+ }
166
+
167
+ async function fileExists(filePath) {
168
+ try {
169
+ await fsp.access(filePath);
170
+ return true;
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+
176
+ function emitWarning(message) {
177
+ process.stderr.write(`[oatty npm installer] ${message}\n`);
178
+ }
179
+
180
+ main().catch((error) => {
181
+ emitWarning(error.message);
182
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "oatty",
3
+ "version": "0.1.0-placeholder.0",
4
+ "description": "Schema-driven CLI + TUI + MCP runtime",
5
+ "license": "MIT OR Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/oattyio/oatty.git"
9
+ },
10
+ "homepage": "https://github.com/oattyio/oatty",
11
+ "bugs": {
12
+ "url": "https://github.com/oattyio/oatty/issues"
13
+ },
14
+ "bin": {
15
+ "oatty": "npm/bin/oatty.js"
16
+ },
17
+ "files": [
18
+ "npm/bin",
19
+ "npm/scripts",
20
+ "README.md",
21
+ "LICENSE*"
22
+ ],
23
+ "scripts": {
24
+ "postinstall": "node npm/scripts/postinstall.js"
25
+ },
26
+ "keywords": [
27
+ "cli",
28
+ "tui",
29
+ "mcp",
30
+ "openapi",
31
+ "workflow"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "adm-zip": "^0.5.16",
38
+ "tar": "^7.4.3"
39
+ }
40
+ }