agency-cli 1.0.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/.gitignore +2 -0
- package/CHANGELOG.md +86 -0
- package/LICENSE.md +21 -0
- package/README.md +118 -0
- package/binary-install.js +212 -0
- package/binary.js +126 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +899 -0
- package/package.json +69 -0
- package/run-agency.js +4 -0
package/.gitignore
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/tobias-walle/agency/compare/v0.1.1...v0.2.0) (2025-11-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add option to select agent in TUI ([bde32b7](https://github.com/tobias-walle/agency/commit/bde32b74c4f8ed49eb81fea6eab9e22d1bc4fe1d))
|
|
9
|
+
* add override config for socket path ([9f35a10](https://github.com/tobias-walle/agency/commit/9f35a10e8085637995b70ee0c5153c38a661f1b1))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* avoid flickering in TUI then opening interactive program ([cc0b13d](https://github.com/tobias-walle/agency/commit/cc0b13d2921868b03006e707d68ae49217ad7e55))
|
|
15
|
+
|
|
16
|
+
## [0.1.1](https://github.com/tobias-walle/agency/compare/v0.1.0...v0.1.1) (2025-11-10)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* always attach to prevent missing cursor issues ([44d8217](https://github.com/tobias-walle/agency/commit/44d8217df9f55f715f20bc0eb112645f9989cee8))
|
|
22
|
+
* don't copy folders if not included ([f6fc4d4](https://github.com/tobias-walle/agency/commit/f6fc4d4c172439e66f3a61a5c2392a430b9faa3d))
|
|
23
|
+
|
|
24
|
+
## 0.1.0 (2025-11-10)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* add .gitignore entries on `init` ([1538a98](https://github.com/tobias-walle/agency/commit/1538a983e394034a75b5d7e689211a3bcacff37a))
|
|
30
|
+
* add agency merge & agency open ([b17acfd](https://github.com/tobias-walle/agency/commit/b17acfd096e220f7e62b346589526c6d48c30e33))
|
|
31
|
+
* add bootstrap cmd to prepare worktrees and drop attach prepare-only ([de9812d](https://github.com/tobias-walle/agency/commit/de9812d8d064c47f3571ee3ec1703409ecd2f44d))
|
|
32
|
+
* add config loading ([947df9e](https://github.com/tobias-walle/agency/commit/947df9e191ed9ca1e54da0ea5cd3a1bad93faff4))
|
|
33
|
+
* add event-driven tui with subscribe and input overlay ([28ddcfb](https://github.com/tobias-walle/agency/commit/28ddcfbe0b55edb6d298695c462b563f6ce53e0b))
|
|
34
|
+
* add idle detection ([d3b598f](https://github.com/tobias-walle/agency/commit/d3b598f8afeac29c00e1b2f856c3cec036af8d36))
|
|
35
|
+
* add missing commands to cli ([d7ba45f](https://github.com/tobias-walle/agency/commit/d7ba45f5e679bfbefbfd87d2d8748beec16705d3))
|
|
36
|
+
* add multi session pty management ([9d3439f](https://github.com/tobias-walle/agency/commit/9d3439fb2758ee9e18b5f9bfe0be58e1212f95e2))
|
|
37
|
+
* add setup cmd support ([b86cca6](https://github.com/tobias-walle/agency/commit/b86cca6ce82c434c040696c90cf484f0bd4fad72))
|
|
38
|
+
* add TUI (phase 1) ([30da84d](https://github.com/tobias-walle/agency/commit/30da84d24e1e9100b7fe93d6b2a3e35ec3c6e3c5))
|
|
39
|
+
* add worktrees, branches and new CLI commands per PLN-3 with centralized task resolution and tests ([0564b0c](https://github.com/tobias-walle/agency/commit/0564b0cf6f024c47d5fdd138c6ea4ccb5ec3d668))
|
|
40
|
+
* copy ignored files & some folders on worktree creation ([df6ace1](https://github.com/tobias-walle/agency/commit/df6ace115495a214c19c03bd3b1f037dfaba8c3d))
|
|
41
|
+
* copy pty-demo and add attach/daemon to enable PTY sessions ([0f8145a](https://github.com/tobias-walle/agency/commit/0f8145a33ec12b33c2f031c7fc8cebc04f97b125))
|
|
42
|
+
* create setup, defaults and init command ([96a6c71](https://github.com/tobias-walle/agency/commit/96a6c71c87e51a1639d9c3dc24d7643d6ef27ec5))
|
|
43
|
+
* default to attaching new tasks with --no-attach opt-out ([f503367](https://github.com/tobias-walle/agency/commit/f503367d027fcd50dd2a1dd9785f31a5ea2833f1))
|
|
44
|
+
* enable task creation via new command with tty-aware output ([66b6b7a](https://github.com/tobias-walle/agency/commit/66b6b7af83a59492219107282580c6d60ee7965b))
|
|
45
|
+
* extend agency default toml ([2f4df14](https://github.com/tobias-walle/agency/commit/2f4df14737acb99f4e348ee3767bd04867ff051f))
|
|
46
|
+
* improve logging ([b056f52](https://github.com/tobias-walle/agency/commit/b056f520ed30a6ee6e200f389b23fa9199678798))
|
|
47
|
+
* improve tui UX and add Stopped state ([3953ab5](https://github.com/tobias-walle/agency/commit/3953ab5c297a69ed72e9fab589a4db272f5ce89c))
|
|
48
|
+
* improve tui ux by adding command logs ([71b67fc](https://github.com/tobias-walle/agency/commit/71b67fc298640f172986dee6d30a73074da12b42))
|
|
49
|
+
* migrate from git2 to gix & git process ([ac6d417](https://github.com/tobias-walle/agency/commit/ac6d41794904fcf699df57d4a6733d70e7b7e9c0))
|
|
50
|
+
* open editor on new tasks and inherit env and add --no-edit ([692f277](https://github.com/tobias-walle/agency/commit/692f2770b392fb18fc05f9a3f5f18473b00ecf5b))
|
|
51
|
+
* parse keybindings from config and use for detach in client ([e6dfdaa](https://github.com/tobias-walle/agency/commit/e6dfdaa10c400f44ad842fb7807833a6877739bb))
|
|
52
|
+
* **ps:** list tasks with reusable table helper and readable tests ([c765f42](https://github.com/tobias-walle/agency/commit/c765f42f0298f69525b8947aa6146c49b3bfe2a3))
|
|
53
|
+
* record base_branch and use slug title, strip header in attach ([b1e37ef](https://github.com/tobias-walle/agency/commit/b1e37efc0449fba5a3e91f1139fb5f0d923f1fa7))
|
|
54
|
+
* resolve duplicate slugs by auto-incrementing and attach by id ([a7fb6e0](https://github.com/tobias-walle/agency/commit/a7fb6e01c6359c512f1413708048bf6ca41dfa3f))
|
|
55
|
+
* route child I/O to TUI and switch terminal just in time ([5c21a85](https://github.com/tobias-walle/agency/commit/5c21a8525580acfe426dbfbc46432286571a3da9))
|
|
56
|
+
* show only task descriptions in editor ([2a5fcb3](https://github.com/tobias-walle/agency/commit/2a5fcb3f4b7ebcdd570a7ff3db2dd3850e4dc033))
|
|
57
|
+
* soft-reset view after detach and TUI to avoid leftover modes ([0526d82](https://github.com/tobias-walle/agency/commit/0526d826a970eb301d56489f274952ef309bae2f))
|
|
58
|
+
* support per-task agent selection with YAML front matter and reduce duplication ([d953138](https://github.com/tobias-walle/agency/commit/d95313848e1a1334d0639b621485f7e8bd7e81d4))
|
|
59
|
+
* switch detach shortcut to ctrl-q to avoid SIGINT conflicts ([8decfb8](https://github.com/tobias-walle/agency/commit/8decfb888331e4de76be954fa7b7a5d165193541))
|
|
60
|
+
* unify command placeholder expansion for agents and bootstrap ([fda9f16](https://github.com/tobias-walle/agency/commit/fda9f16a9a42ce7c707d872afa6e974f405c2a57))
|
|
61
|
+
* wip ([dcca483](https://github.com/tobias-walle/agency/commit/dcca48384f25dcf7091d031274d4ad34793280fc))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Bug Fixes
|
|
65
|
+
|
|
66
|
+
* allow merge with unstaged changes ([45e8551](https://github.com/tobias-walle/agency/commit/45e855163d0deaed3979587235ba5e449965c226))
|
|
67
|
+
* allow stop at handshake and add ack with clearer logging to fix stop errors ([4431c97](https://github.com/tobias-walle/agency/commit/4431c97ee664757216ab87156ea6047f38937c81))
|
|
68
|
+
* centralize notification handling and fix bug that tui wasn't updated after merge ([0626194](https://github.com/tobias-walle/agency/commit/0626194ce97e926c610572819315de319db601ba))
|
|
69
|
+
* fix agency ps table formatting ([4500fa2](https://github.com/tobias-walle/agency/commit/4500fa24f46b223026a8782dcea191472bb7bdb8))
|
|
70
|
+
* fix cursor request handling with codex ([d110150](https://github.com/tobias-walle/agency/commit/d110150ddec25de87ce09cdbb1b2aacc1be2e593))
|
|
71
|
+
* fix idle detection and add logs ([a9776f7](https://github.com/tobias-walle/agency/commit/a9776f733eb6172ea6725c844401cbea52726916))
|
|
72
|
+
* fix scroll issue in pty ([61dafdc](https://github.com/tobias-walle/agency/commit/61dafdc45fddd543711bb44ff87dcc938c4e546b))
|
|
73
|
+
* fix tests ([8fa6fe0](https://github.com/tobias-walle/agency/commit/8fa6fe07a0ac3da3bce6e2073795b10ad57dc8fd))
|
|
74
|
+
* fix warnings ([127a2f7](https://github.com/tobias-walle/agency/commit/127a2f7615eac6e76a862dd9fab42b55bc5d40aa))
|
|
75
|
+
* improve error handling if daemon is stopped ([8baf449](https://github.com/tobias-walle/agency/commit/8baf449ae3ec8c43e840dd60fe478d1080b6d5de))
|
|
76
|
+
* make ctrl-q detach work with extended keyboard via termwiz ([15e93d9](https://github.com/tobias-walle/agency/commit/15e93d9034c134aa0c0f560de0ede4db9e375266))
|
|
77
|
+
* make enter reliably restart session and re-arm after each exit ([8c2b035](https://github.com/tobias-walle/agency/commit/8c2b03500ffdb8a39c722aaed95c143520d39d26))
|
|
78
|
+
* reattach to session on same task ([2732a57](https://github.com/tobias-walle/agency/commit/2732a57967d469424b6119e0ef4d4f2fcdf3997b))
|
|
79
|
+
* remove newline from frontmatter ([ee740de](https://github.com/tobias-walle/agency/commit/ee740de0e6543e3a932ee883954b4ea806e98e00))
|
|
80
|
+
* remove redudant tests ([8c2f039](https://github.com/tobias-walle/agency/commit/8c2f0392e3193aea34b1414c167625ac5b32f9fa))
|
|
81
|
+
* resolve clippy pedantic warnings and improve task markdown utils ([84dabc4](https://github.com/tobias-walle/agency/commit/84dabc458277f90eea6ac756a371389e0b1de78e))
|
|
82
|
+
* resolve pedantic clippy lints to make just fix green ([8aa2924](https://github.com/tobias-walle/agency/commit/8aa292473d425c6b4a61deaf1ca5b4455b394f4c))
|
|
83
|
+
* restore terminal state to avoid leftover extended keyboard modes ([63932a1](https://github.com/tobias-walle/agency/commit/63932a1bf94b3acd9c4ff69caf0ef054d200af46))
|
|
84
|
+
* revert workaround ([015c07f](https://github.com/tobias-walle/agency/commit/015c07f9d252d09df5adb07809027a7e52d890aa))
|
|
85
|
+
* track session status and broadcast after restart to update TUI ([e632a7b](https://github.com/tobias-walle/agency/commit/e632a7b8f52af887742a84074a13a1a96f198c61))
|
|
86
|
+
* update fs after merge ([c1e9ed5](https://github.com/tobias-walle/agency/commit/c1e9ed58cfab32d1b0c83251e013a908af8f5851))
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Agency
|
|
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
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Agency
|
|
4
|
+
|
|
5
|
+
Agency is an AI agent orchestrator running purely in the command line.
|
|
6
|
+
|
|
7
|
+
- User-friendly TUI heavily inspired by [Lazygit](https://github.com/jesseduffield/lazygit)
|
|
8
|
+
- CLI commands for easy automation
|
|
9
|
+
- Supports any CLI coding agent: [Claude Code](https://github.com/anthropics/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [OpenCode](https://github.com/sst/opencode) and [you can add more](#defining-a-custom-agent)
|
|
10
|
+
- Isolated environments for each task using Git worktrees
|
|
11
|
+
|
|
12
|
+
## Getting Started
|
|
13
|
+
|
|
14
|
+
1. Install Agency with your preferred method (macOS and Linux supported)
|
|
15
|
+
- Build from source: `cargo install --git <url>`
|
|
16
|
+
- NPM (_Coming soon_): `npm install -g @tobias-walle/agency`
|
|
17
|
+
- Homebrew (_Coming soon_): `brew install tobias-walle/tap/agency`
|
|
18
|
+
2. Set up your preferences: `agency setup`
|
|
19
|
+
3. Set up Agency in your project: `agency init`
|
|
20
|
+
4. Start the TUI: `agency`
|
|
21
|
+
|
|
22
|
+
## TUI or CLI: your choice
|
|
23
|
+
|
|
24
|
+
The easiest option is to use the TUI; run `agency tui` or just `agency`.
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
Everything available in the TUI is also available via the CLI:
|
|
29
|
+
|
|
30
|
+
- `agency --help` - See all available commands
|
|
31
|
+
- `agency new my-task` - Start a new task with slug `my-task`; opens your `$EDITOR` to describe what the agent should do.
|
|
32
|
+
- `agency new --draft my-task` - Create a new task as a draft (doesn't start it yet).
|
|
33
|
+
- `agency edit my-task` - Edit a draft task.
|
|
34
|
+
- `agency start my-task` - Start a task that is a draft or stopped.
|
|
35
|
+
- `agency attach my-task` or `agency attach 1` - Open the agent TUI by slug or ID.
|
|
36
|
+
- `agency stop my-task` - Stop a running task (keep its worktree and branch).
|
|
37
|
+
- `agency merge my-task` - Merge the task back into the base branch.
|
|
38
|
+
- `agency path my-task` - Get the worktree path for a task.
|
|
39
|
+
- `agency shell my-task` - Open a shell in the task's worktree.
|
|
40
|
+
- `agency ps` - List all tasks and their status.
|
|
41
|
+
- `agency daemon start|stop|restart` - Manage the background daemon that runs the agents.
|
|
42
|
+
- ... and many more (see `agency --help`).
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Configuration is layered in three tiers:
|
|
47
|
+
|
|
48
|
+
1. Defaults (see [crates/agency/defaults/agency.toml](./crates/agency/defaults/agency.toml) or `agency defaults`)
|
|
49
|
+
2. Global file `~/.config/agency/agency.toml` (created by `agency setup`)
|
|
50
|
+
3. Project overrides at `./.agency/agency.toml`
|
|
51
|
+
|
|
52
|
+
### Defining a custom agent
|
|
53
|
+
|
|
54
|
+
You can define custom agents using any CLI command.
|
|
55
|
+
|
|
56
|
+
```toml
|
|
57
|
+
[agents.my-agent]
|
|
58
|
+
cmd = ["my-agent", "-p", "$AGENCY_TASK"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The following environment variables are injected into the command:
|
|
62
|
+
|
|
63
|
+
- `$AGENCY_TASK` - The full prompt for the current task.
|
|
64
|
+
- `$AGENCY_ROOT` - The path to the folder of the main repo (not the worktree).
|
|
65
|
+
|
|
66
|
+
You can also use the `<root>` placeholder for relative paths (works in any config in which you define a path).
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
[agents.my-local-agent]
|
|
70
|
+
cmd = ["<root>./my-local-agent", "-p", "$AGENCY_TASK"]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Check out the [default config](./crates/agency/defaults/agency.toml) for a few examples.
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
Agency uses a daemon + client architecture. The daemon manages all PTY sessions that run the agents. Clients (CLI or TUI) communicate with the daemon via a Unix socket.
|
|
78
|
+
|
|
79
|
+
The socket is stored in one of the following locations:
|
|
80
|
+
|
|
81
|
+
- `$AGENCY_SOCKET_PATH` env override
|
|
82
|
+
- `daemon.socket_path` in config
|
|
83
|
+
- `$XDG_RUNTIME_DIR/agency.sock`
|
|
84
|
+
- `~/.local/run/agency.sock` (Default)
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
flowchart LR
|
|
88
|
+
U[User] --> C[TUI/CLI]
|
|
89
|
+
C <--> S[Unix Socket]
|
|
90
|
+
S <--> D[Agency Daemon]
|
|
91
|
+
D --> P[PTY Sessions]
|
|
92
|
+
P --> A[CLI Agents]
|
|
93
|
+
|
|
94
|
+
subgraph Project
|
|
95
|
+
A
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For example, when creating a new task the message flow between daemon and client looks like this:
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
sequenceDiagram
|
|
103
|
+
participant U as User
|
|
104
|
+
participant C as Agency CLI
|
|
105
|
+
participant D as Daemon
|
|
106
|
+
participant P as PTY Manager
|
|
107
|
+
participant A as Agent
|
|
108
|
+
|
|
109
|
+
U->>C: agency new my-task
|
|
110
|
+
C->>C: Create .agency/tasks/my-task.md
|
|
111
|
+
C->>D: OpenSession(project, task, cmd, worktree) via socket
|
|
112
|
+
D->>P: Spawn PTY session
|
|
113
|
+
P->>A: Exec agent cmd in worktree
|
|
114
|
+
C->>P: Attach to session (interactive)
|
|
115
|
+
U<<->>P: Terminal I/O
|
|
116
|
+
P->>D: Status updates
|
|
117
|
+
D->>C: Notifications
|
|
118
|
+
```
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
|
|
2
|
+
const { join, sep } = require("path");
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const { tmpdir } = require("os");
|
|
5
|
+
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const rimraf = require("rimraf");
|
|
8
|
+
const tmpDir = tmpdir();
|
|
9
|
+
|
|
10
|
+
const error = (msg) => {
|
|
11
|
+
console.error(msg);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
class Package {
|
|
16
|
+
constructor(platform, name, url, filename, zipExt, binaries) {
|
|
17
|
+
let errors = [];
|
|
18
|
+
if (typeof url !== "string") {
|
|
19
|
+
errors.push("url must be a string");
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
new URL(url);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
errors.push(e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (name && typeof name !== "string") {
|
|
28
|
+
errors.push("package name must be a string");
|
|
29
|
+
}
|
|
30
|
+
if (!name) {
|
|
31
|
+
errors.push("You must specify the name of your package");
|
|
32
|
+
}
|
|
33
|
+
if (binaries && typeof binaries !== "object") {
|
|
34
|
+
errors.push("binaries must be a string => string map");
|
|
35
|
+
}
|
|
36
|
+
if (!binaries) {
|
|
37
|
+
errors.push("You must specify the binaries in the package");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
let errorMsg =
|
|
42
|
+
"One or more of the parameters you passed to the Binary constructor are invalid:\n";
|
|
43
|
+
errors.forEach((error) => {
|
|
44
|
+
errorMsg += error;
|
|
45
|
+
});
|
|
46
|
+
errorMsg +=
|
|
47
|
+
'\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
|
|
48
|
+
error(errorMsg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.platform = platform;
|
|
52
|
+
this.url = url;
|
|
53
|
+
this.name = name;
|
|
54
|
+
this.filename = filename;
|
|
55
|
+
this.zipExt = zipExt;
|
|
56
|
+
this.installDirectory = join(__dirname, "node_modules", ".bin_real");
|
|
57
|
+
this.binaries = binaries;
|
|
58
|
+
|
|
59
|
+
if (!existsSync(this.installDirectory)) {
|
|
60
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exists() {
|
|
65
|
+
for (const binaryName in this.binaries) {
|
|
66
|
+
const binRelPath = this.binaries[binaryName];
|
|
67
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
68
|
+
if (!existsSync(binPath)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
install(fetchOptions, suppressLogs = false) {
|
|
76
|
+
if (this.exists()) {
|
|
77
|
+
if (!suppressLogs) {
|
|
78
|
+
console.error(
|
|
79
|
+
`${this.name} is already installed, skipping installation.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (existsSync(this.installDirectory)) {
|
|
86
|
+
rimraf.sync(this.installDirectory);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
90
|
+
|
|
91
|
+
if (!suppressLogs) {
|
|
92
|
+
console.error(`Downloading release from ${this.url}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
|
|
96
|
+
.then((res) => {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
|
|
99
|
+
let tempFile = join(directory, this.filename);
|
|
100
|
+
const sink = res.data.pipe(createWriteStream(tempFile));
|
|
101
|
+
sink.on("error", (err) => reject(err));
|
|
102
|
+
sink.on("close", () => {
|
|
103
|
+
if (/\.tar\.*/.test(this.zipExt)) {
|
|
104
|
+
const result = spawnSync("tar", [
|
|
105
|
+
"xf",
|
|
106
|
+
tempFile,
|
|
107
|
+
// The tarballs are stored with a leading directory
|
|
108
|
+
// component; we strip one component in the
|
|
109
|
+
// shell installers too.
|
|
110
|
+
"--strip-components",
|
|
111
|
+
"1",
|
|
112
|
+
"-C",
|
|
113
|
+
this.installDirectory,
|
|
114
|
+
]);
|
|
115
|
+
if (result.status == 0) {
|
|
116
|
+
resolve();
|
|
117
|
+
} else if (result.error) {
|
|
118
|
+
reject(result.error);
|
|
119
|
+
} else {
|
|
120
|
+
reject(
|
|
121
|
+
new Error(
|
|
122
|
+
`An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} else if (this.zipExt == ".zip") {
|
|
127
|
+
let result;
|
|
128
|
+
if (this.platform.artifactName.includes("windows")) {
|
|
129
|
+
// Windows does not have "unzip" by default on many installations, instead
|
|
130
|
+
// we use Expand-Archive from powershell
|
|
131
|
+
result = spawnSync("powershell.exe", [
|
|
132
|
+
"-NoProfile",
|
|
133
|
+
"-NonInteractive",
|
|
134
|
+
"-Command",
|
|
135
|
+
`& {
|
|
136
|
+
param([string]$LiteralPath, [string]$DestinationPath)
|
|
137
|
+
Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
|
|
138
|
+
}`,
|
|
139
|
+
tempFile,
|
|
140
|
+
this.installDirectory,
|
|
141
|
+
]);
|
|
142
|
+
} else {
|
|
143
|
+
result = spawnSync("unzip", [
|
|
144
|
+
"-q",
|
|
145
|
+
tempFile,
|
|
146
|
+
"-d",
|
|
147
|
+
this.installDirectory,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.status == 0) {
|
|
152
|
+
resolve();
|
|
153
|
+
} else if (result.error) {
|
|
154
|
+
reject(result.error);
|
|
155
|
+
} else {
|
|
156
|
+
reject(
|
|
157
|
+
new Error(
|
|
158
|
+
`An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
reject(
|
|
164
|
+
new Error(`Unrecognized file extension: ${this.zipExt}`),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
})
|
|
171
|
+
.then(() => {
|
|
172
|
+
if (!suppressLogs) {
|
|
173
|
+
console.error(`${this.name} has been installed!`);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.catch((e) => {
|
|
177
|
+
error(`Error fetching release: ${e.message}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
run(binaryName, fetchOptions) {
|
|
182
|
+
const promise = !this.exists()
|
|
183
|
+
? this.install(fetchOptions, true)
|
|
184
|
+
: Promise.resolve();
|
|
185
|
+
|
|
186
|
+
promise
|
|
187
|
+
.then(() => {
|
|
188
|
+
const [, , ...args] = process.argv;
|
|
189
|
+
|
|
190
|
+
const options = { cwd: process.cwd(), stdio: "inherit" };
|
|
191
|
+
|
|
192
|
+
const binRelPath = this.binaries[binaryName];
|
|
193
|
+
if (!binRelPath) {
|
|
194
|
+
error(`${binaryName} is not a known binary in ${this.name}`);
|
|
195
|
+
}
|
|
196
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
197
|
+
const result = spawnSync(binPath, args, options);
|
|
198
|
+
|
|
199
|
+
if (result.error) {
|
|
200
|
+
error(result.error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
process.exit(result.status);
|
|
204
|
+
})
|
|
205
|
+
.catch((e) => {
|
|
206
|
+
error(e.message);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports.Package = Package;
|
package/binary.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const { Package } = require("./binary-install");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const cTable = require("console.table");
|
|
4
|
+
const libc = require("detect-libc");
|
|
5
|
+
const { configureProxy } = require("axios-proxy-builder");
|
|
6
|
+
|
|
7
|
+
const error = (msg) => {
|
|
8
|
+
console.error(msg);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
name,
|
|
14
|
+
artifactDownloadUrl,
|
|
15
|
+
supportedPlatforms,
|
|
16
|
+
glibcMinimum,
|
|
17
|
+
} = require("./package.json");
|
|
18
|
+
|
|
19
|
+
const builderGlibcMajorVersion = glibcMinimum.major;
|
|
20
|
+
const builderGlibcMInorVersion = glibcMinimum.series;
|
|
21
|
+
|
|
22
|
+
const getPlatform = () => {
|
|
23
|
+
const rawOsType = os.type();
|
|
24
|
+
const rawArchitecture = os.arch();
|
|
25
|
+
|
|
26
|
+
// We want to use rust-style target triples as the canonical key
|
|
27
|
+
// for a platform, so translate the "os" library's concepts into rust ones
|
|
28
|
+
let osType = "";
|
|
29
|
+
switch (rawOsType) {
|
|
30
|
+
case "Windows_NT":
|
|
31
|
+
osType = "pc-windows-msvc";
|
|
32
|
+
break;
|
|
33
|
+
case "Darwin":
|
|
34
|
+
osType = "apple-darwin";
|
|
35
|
+
break;
|
|
36
|
+
case "Linux":
|
|
37
|
+
osType = "unknown-linux-gnu";
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let arch = "";
|
|
42
|
+
switch (rawArchitecture) {
|
|
43
|
+
case "x64":
|
|
44
|
+
arch = "x86_64";
|
|
45
|
+
break;
|
|
46
|
+
case "arm64":
|
|
47
|
+
arch = "aarch64";
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (rawOsType === "Linux") {
|
|
52
|
+
if (libc.familySync() == "musl") {
|
|
53
|
+
osType = "unknown-linux-musl-dynamic";
|
|
54
|
+
} else if (libc.isNonGlibcLinuxSync()) {
|
|
55
|
+
console.warn(
|
|
56
|
+
"Your libc is neither glibc nor musl; trying static musl binary instead",
|
|
57
|
+
);
|
|
58
|
+
osType = "unknown-linux-musl-static";
|
|
59
|
+
} else {
|
|
60
|
+
let libcVersion = libc.versionSync();
|
|
61
|
+
let splitLibcVersion = libcVersion.split(".");
|
|
62
|
+
let libcMajorVersion = splitLibcVersion[0];
|
|
63
|
+
let libcMinorVersion = splitLibcVersion[1];
|
|
64
|
+
if (
|
|
65
|
+
libcMajorVersion != builderGlibcMajorVersion ||
|
|
66
|
+
libcMinorVersion < builderGlibcMInorVersion
|
|
67
|
+
) {
|
|
68
|
+
// We can't run the glibc binaries, but we can run the static musl ones
|
|
69
|
+
// if they exist
|
|
70
|
+
console.warn(
|
|
71
|
+
"Your glibc isn't compatible; trying static musl binary instead",
|
|
72
|
+
);
|
|
73
|
+
osType = "unknown-linux-musl-static";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Assume the above succeeded and build a target triple to look things up with.
|
|
79
|
+
// If any of it failed, this lookup will fail and we'll handle it like normal.
|
|
80
|
+
let targetTriple = `${arch}-${osType}`;
|
|
81
|
+
let platform = supportedPlatforms[targetTriple];
|
|
82
|
+
|
|
83
|
+
if (!platform) {
|
|
84
|
+
error(
|
|
85
|
+
`Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys(
|
|
86
|
+
supportedPlatforms,
|
|
87
|
+
).join(",")}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return platform;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getPackage = () => {
|
|
95
|
+
const platform = getPlatform();
|
|
96
|
+
const url = `${artifactDownloadUrl}/${platform.artifactName}`;
|
|
97
|
+
let filename = platform.artifactName;
|
|
98
|
+
let ext = platform.zipExt;
|
|
99
|
+
let binary = new Package(platform, name, url, filename, ext, platform.bins);
|
|
100
|
+
|
|
101
|
+
return binary;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const install = (suppressLogs) => {
|
|
105
|
+
if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) {
|
|
106
|
+
console.warn("in demo mode, not installing binaries");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const package = getPackage();
|
|
110
|
+
const proxy = configureProxy(package.url);
|
|
111
|
+
|
|
112
|
+
return package.install(proxy, suppressLogs);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const run = (binaryName) => {
|
|
116
|
+
const package = getPackage();
|
|
117
|
+
const proxy = configureProxy(package.url);
|
|
118
|
+
|
|
119
|
+
package.run(binaryName, proxy);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
install,
|
|
124
|
+
run,
|
|
125
|
+
getPackage,
|
|
126
|
+
};
|
package/install.js
ADDED