@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 +21 -0
- package/README.md +134 -2
- package/bin/accordo.cjs +50 -0
- package/package.json +34 -6
- package/index.js +0 -1
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
|
-
#
|
|
1
|
+
# Accordo
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
package/bin/accordo.cjs
ADDED
|
@@ -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.
|
|
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 = {};
|