@matfire/accordo 0.0.0 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matteo Gassend
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,135 @@
1
- # @matfire/accordo
1
+ # Accordo
2
2
 
3
- This is a stub package published by setup-trusted-publishing to enable OIDC trusted publishing configuration.
3
+ Accordo runs development services together on **macOS and Linux**, with a Ratatui tabbed interface or a merged stream of numbered logs.
4
+
5
+ ## Install
6
+
7
+ Once the first release is published, install a prebuilt binary with Homebrew:
8
+
9
+ ```sh
10
+ brew install mattsverse/tap/accordo
11
+ ```
12
+
13
+ Or install through mise:
14
+
15
+ ```sh
16
+ mise use -g github:mattsverse/accordo@latest
17
+ ```
18
+
19
+ With a current stable Rust toolchain, install from crates.io:
20
+
21
+ ```sh
22
+ cargo install accordo --locked
23
+ ```
24
+
25
+ With Node.js 22 or newer, install through npm or run directly with npx:
26
+
27
+ ```sh
28
+ npm install -g @matfire/accordo
29
+ npx @matfire/accordo --help
30
+ ```
31
+
32
+ The npm package automatically selects the matching binary from `@getaccordo`.
33
+ Keep optional dependencies enabled; no Rust compiler or postinstall download is needed.
34
+
35
+ All methods install the `accordo` command. Prebuilt binaries support Apple Silicon
36
+ and Intel macOS, and ARM64 and x86_64 Linux (glibc 2.39 or newer, such as Ubuntu 24.04).
37
+ Archives and checksums are also available on the [GitHub releases page](https://github.com/mattsverse/accordo/releases).
38
+
39
+ To install directly from this checkout:
40
+
41
+ ```sh
42
+ cargo install --path .
43
+ ```
44
+
45
+ For development, use `cargo run -- -f path/to/accordo.yaml`.
46
+
47
+ Maintainers: see [the release procedure](docs/releasing.md) for publishing and recovery.
48
+
49
+ ## Configure and run
50
+
51
+ Create `accordo.yaml`:
52
+
53
+ ```yaml
54
+ services:
55
+ - name: app1
56
+ cwd: ./apps/app1
57
+ cmd: pnpm dev
58
+ hide_stderr: true
59
+ - name: app2
60
+ cmd: cargo run app2
61
+ autostart: false
62
+ layout: tabbed
63
+ ```
64
+
65
+ Run `accordo`, or `accordo -f your_config.yaml` (`--file` also works). Use `accordo --help` for CLI help.
66
+
67
+ - `layout` defaults to `tabbed`; the other choice is `linear`.
68
+ - `autostart` defaults to `true`. Enabled services start concurrently.
69
+ - `hide_stderr` defaults to `false`. Set it to `true` on a service to hide its stderr in both views. Tabbed mode redirects stderr to `/dev/null` before launching the command; linear mode drains and filters it. Stdout and failure exit codes are unaffected.
70
+ - `cwd` defaults to the configuration file's directory. Relative directories resolve against that directory, even when Accordo is launched elsewhere. For a symlinked configuration, paths resolve beside the target file.
71
+ - Each `cmd` runs through `/bin/sh -c` with the inherited environment. Shell quoting, pipes, and redirects work; interactive shell aliases and profiles are not loaded.
72
+ - Configuration is validated before any service starts. Names must be unique and nonempty, commands must be nonempty, and working directories must exist. Unknown configuration fields are rejected.
73
+
74
+ ## Tabbed view
75
+
76
+ Services appear in a numbered sidebar on the left, with their current status. Each service runs in its own pseudo-terminal (PTY), and the selected service's terminal screen appears on the right. The `vt100` engine and `tui-term` widget handle colors, cursor movement, progress-bar redraws, line wrapping, and alternate screens. Exited services remain available to inspect and restart. A restart retains available history and adds a run separator.
77
+
78
+ | Key | Action |
79
+ | --- | --- |
80
+ | `0`–`9` | Select service 0–9 (number row or numpad with Num Lock) |
81
+ | `↑` / `↓` | Cycle through all services, including those beyond 9 |
82
+ | Click a service | Select it in the sidebar |
83
+ | Mouse wheel / trackpad over logs | Scroll the selected service's log history |
84
+ | `PageUp` / `PageDown` | Scroll logs one page |
85
+ | `Home` / `End` | Jump to oldest retained logs / resume live output |
86
+ | `s` | Start or stop the selected service |
87
+ | `r` | Restart the selected service, or start it if inactive |
88
+ | `q` | Quit and stop every service |
89
+ | `Ctrl+C` | Quit and stop every service |
90
+
91
+ Tabbed layout requires interactive stdin and stdout. Terminal output follows the latest screen until you scroll up. Your place is preserved as output arrives and when switching services; scrolling back to the bottom or pressing `End` resumes live output. Wheel scrolling never changes the selected service. Each emulator retains up to 10,000 scrollback rows; alternate-screen applications control their own screen and do not contribute normal scrollback.
92
+
93
+ Accordo resizes every service's PTY to match the output pane and reports `TERM=xterm-256color`. Programs can redraw for the new size; existing wrapped output is not reflowed by the current engine. Stdout and stderr share the terminal, so stderr has no added prefix or forced color in this view. Lifecycle status stays in the sidebar and pane title rather than being injected into program output.
94
+
95
+ This mode displays terminal output while reserving the keyboard for Accordo's controls. Child stdin is a PTY and basic terminal queries receive replies, but typing and mouse input are not forwarded to applications. Interactive prompts therefore still require a separate terminal. The renderer supports text and terminal styles, not Ghostty-specific graphics or every modern terminal extension.
96
+
97
+ ## Linear view
98
+
99
+ Set `layout: linear` to print ordinary terminal output:
100
+
101
+ ```text
102
+ [0 app1] --- run 1 ---
103
+ [0 app1] --- running ---
104
+ [1 app2] --- stopped ---
105
+ [0 app1] ready on port 3000
106
+ [0 app1] [stderr] an example warning
107
+ ```
108
+
109
+ The merged feed supports normal terminal scrollback, redirection, and piping. Service logs and lifecycle messages go to stdout; configuration and Accordo errors go to stderr. Stderr from a service is marked `[stderr]` in the merged feed.
110
+
111
+ There are **no keyboard bindings in linear mode**. `Ctrl+C` stops every service. Services with `autostart: false` remain stopped; linear mode exits once all enabled services finish. If none are enabled, Accordo prints an explanation and exits successfully.
112
+
113
+ ## Process and output behavior
114
+
115
+ - A failed service does not stop the others. Services never restart automatically.
116
+ - Stop, restart, quit, and SIGTERM signal the service's entire Unix process group with SIGTERM. After three seconds, remaining members receive SIGKILL. Restart waits for cleanup before starting again.
117
+ - Descendants remaining after their main command exits are also cleaned up. Services must stay in their assigned process group; daemonized processes that create a new session/group are unsupported.
118
+ - In linear mode, stdout and stderr are read concurrently, preserving each stream's ordering. The merged feed follows arrival order; ordering across different streams is not guaranteed. Child stdin is closed.
119
+ - Linear mode strips terminal escape/control sequences, replaces invalid UTF-8, and turns carriage returns into separate records. Records are split at 16 KiB; an unterminated final record is still displayed. Tabbed mode sends raw PTY bytes directly to the terminal emulator, including partial lines and escape sequences.
120
+ - Output queues are bounded. A slow consumer applies backpressure to services while lifecycle controls remain responsive. A closed output pipe triggers process cleanup.
121
+ - Tabbed terminal state is restored before shutdown waits for processes. On user-requested shutdown, pending output is drained for cleanup but is not displayed.
122
+
123
+ Exit codes: `0` for successful completion (or a closed output pipe), `1` for configuration/service/application failures, `130` for Ctrl+C, and `143` for SIGTERM. In tabbed mode, `q` returns `1` if any service failure was observed during the session. CLI argument errors use Clap's exit code `2`.
124
+
125
+ ## Development checks
126
+
127
+ ```sh
128
+ cargo test
129
+ cargo fmt --all -- --check
130
+ cargo clippy --all-targets --all-features -- -D warnings -D clippy::pedantic -D clippy::perf -D clippy::suspicious
131
+ cargo check --all-targets --all-features
132
+ python3 tests/terminal_smoke.py
133
+ ```
134
+
135
+ The terminal smoke test uses a real pseudo-terminal to exercise keyboard controls, resizing, and terminal restoration. Run it after `cargo test` has built the binary. CI runs these checks on macOS and Linux.
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawn } = require("node:child_process");
5
+
6
+ function main() {
7
+ const { platform, arch } = process;
8
+ if (!["darwin", "linux"].includes(platform) || !["arm64", "x64"].includes(arch)) {
9
+ throw new Error(`No Accordo binary is available for ${platform}-${arch}.`);
10
+ }
11
+ if (platform === "linux" && !process.report.getReport().header.glibcVersionRuntime) {
12
+ throw new Error("Accordo's Linux npm binaries require glibc; musl/Alpine is unsupported.");
13
+ }
14
+ const name = `@getaccordo/accordo-${platform}-${arch}${platform === "linux" ? "-gnu" : ""}`;
15
+ let binary;
16
+ try {
17
+ binary = require.resolve(`${name}/bin/accordo`);
18
+ } catch {
19
+ throw new Error(`Missing ${name}. Reinstall @matfire/accordo with optional dependencies enabled.`);
20
+ }
21
+
22
+ // Keep the terminal attached and forward signals sent directly to this launcher.
23
+ const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
24
+ const handlers = new Map();
25
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP", "SIGWINCH"]) {
26
+ const handler = () => child.kill(signal);
27
+ handlers.set(signal, handler);
28
+ process.on(signal, handler);
29
+ }
30
+ const cleanup = () => {
31
+ for (const [signal, handler] of handlers) process.removeListener(signal, handler);
32
+ };
33
+ child.on("error", (error) => {
34
+ cleanup();
35
+ console.error(`accordo: Could not start the native binary: ${error.message}`);
36
+ process.exitCode = 1;
37
+ });
38
+ child.on("exit", (code, signal) => {
39
+ cleanup();
40
+ if (signal) process.kill(process.pid, signal);
41
+ else process.exitCode = code ?? 1;
42
+ });
43
+ }
44
+
45
+ try {
46
+ main();
47
+ } catch (error) {
48
+ console.error(`accordo: ${error.message}`);
49
+ process.exitCode = 1;
50
+ }
package/package.json CHANGED
@@ -1,15 +1,43 @@
1
1
  {
2
2
  "name": "@matfire/accordo",
3
- "version": "0.0.0",
4
- "main": "index.js",
3
+ "version": "0.1.0",
5
4
  "description": "Run development services together in terminal tabs or a merged log stream",
6
- "publishConfig": {
7
- "access": "public"
8
- },
9
5
  "license": "MIT",
10
- "homepage": "https://github.com/mattsverse/accordo",
11
6
  "repository": {
12
7
  "type": "git",
13
8
  "url": "git+https://github.com/mattsverse/accordo.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/mattsverse/accordo/issues"
12
+ },
13
+ "homepage": "https://github.com/mattsverse/accordo",
14
+ "bin": {
15
+ "accordo": "bin/accordo.cjs"
16
+ },
17
+ "files": [
18
+ "bin/accordo.cjs",
19
+ "LICENSE",
20
+ "README.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "os": [
26
+ "darwin",
27
+ "linux"
28
+ ],
29
+ "cpu": [
30
+ "arm64",
31
+ "x64"
32
+ ],
33
+ "optionalDependencies": {
34
+ "@getaccordo/accordo-darwin-arm64": "0.1.0",
35
+ "@getaccordo/accordo-darwin-x64": "0.1.0",
36
+ "@getaccordo/accordo-linux-arm64-gnu": "0.1.0",
37
+ "@getaccordo/accordo-linux-x64-gnu": "0.1.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
14
42
  }
15
43
  }
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = {};