@team-agent/installer 0.3.0 → 0.3.1
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/emit.rs +178 -51
- package/crates/team-agent/src/cli/mod.rs +83 -17
- package/crates/team-agent/src/coordinator/health.rs +121 -0
- package/crates/team-agent/src/leader/lease.rs +23 -2
- package/crates/team-agent/src/leader/rediscover/tests.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +2 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +9 -6
- package/crates/team-agent/src/leader/tests/idle.rs +1 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +157 -0
- package/crates/team-agent/src/leader/types.rs +2 -0
- package/crates/team-agent/src/lifecycle/launch.rs +300 -24
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +52 -0
- package/crates/team-agent/src/lifecycle/types.rs +25 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +28 -0
- package/crates/team-agent/src/mcp_server/wire.rs +81 -1
- package/crates/team-agent/src/messaging/delivery.rs +204 -3
- package/crates/team-agent/src/messaging/leader_receiver.rs +26 -37
- package/crates/team-agent/src/messaging/results.rs +18 -2
- package/crates/team-agent/src/messaging/send.rs +15 -19
- package/crates/team-agent/src/state/identity.rs +3 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +179 -0
- package/crates/team-agent/src/tmux_backend.rs +58 -6
- package/npm/install.mjs +29 -7
- package/package.json +4 -4
|
@@ -332,6 +332,9 @@ pub fn apply_first_time_leader_binding(
|
|
|
332
332
|
r.insert("leader_session_uuid".to_string(), id_uuid.clone());
|
|
333
333
|
r.insert("machine_fingerprint".to_string(), id_fp.clone());
|
|
334
334
|
r.insert("owner_epoch".to_string(), json!(0));
|
|
335
|
+
if let Some(socket) = crate::tmux_backend::socket_name_from_tmux_env() {
|
|
336
|
+
r.insert("tmux_socket".to_string(), json!(socket));
|
|
337
|
+
}
|
|
335
338
|
}
|
|
336
339
|
let owner = json!({
|
|
337
340
|
"pane_id": receiver.get("pane_id").cloned().unwrap_or(Value::Null),
|
|
@@ -98,6 +98,185 @@
|
|
|
98
98
|
items.iter().map(|s| (*s).to_string()).collect()
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
struct EnvGuard {
|
|
102
|
+
saved: Vec<(String, Option<String>)>,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl EnvGuard {
|
|
106
|
+
fn apply(vars: &[(&str, Option<&str>)]) -> Self {
|
|
107
|
+
let saved = vars.iter().map(|(k, _)| ((*k).to_string(), std::env::var(k).ok())).collect();
|
|
108
|
+
for (k, v) in vars {
|
|
109
|
+
match v {
|
|
110
|
+
Some(val) => std::env::set_var(k, val),
|
|
111
|
+
None => std::env::remove_var(k),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
Self { saved }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
impl Drop for EnvGuard {
|
|
119
|
+
fn drop(&mut self) {
|
|
120
|
+
for (k, v) in &self.saved {
|
|
121
|
+
match v {
|
|
122
|
+
Some(val) => std::env::set_var(k, val),
|
|
123
|
+
None => std::env::remove_var(k),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
#[serial_test::serial(env)]
|
|
131
|
+
fn leader_receiver_endpoint_from_tmux_env_preserves_full_socket_path() {
|
|
132
|
+
let leader_socket = "/tmp/ta-leader-root/tmux-501/dl2f";
|
|
133
|
+
let _env = EnvGuard::apply(&[
|
|
134
|
+
("TMUX", Some("/tmp/ta-leader-root/tmux-501/dl2f,12345,0")),
|
|
135
|
+
("TMUX_TMPDIR", Some("/tmp/ta-coordinator-root")),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
assert_eq!(
|
|
139
|
+
super::socket_name_from_tmux_env().as_deref(),
|
|
140
|
+
Some(leader_socket),
|
|
141
|
+
"leader receivers must persist the exact tmux endpoint from $TMUX; a short -L socket \
|
|
142
|
+
name is re-rooted under the coordinator's TMUX_TMPDIR and cannot reach an external \
|
|
143
|
+
leader pane"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
#[serial_test::serial(env)]
|
|
149
|
+
fn leader_receiver_endpoint_from_tmux_env_rejects_short_socket_name() {
|
|
150
|
+
let _env = EnvGuard::apply(&[
|
|
151
|
+
("TMUX", Some("dl9aa40c88,12345,0")),
|
|
152
|
+
("TMUX_TMPDIR", Some("/tmp/ta-coordinator-root")),
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
assert_eq!(
|
|
156
|
+
super::socket_name_from_tmux_env(),
|
|
157
|
+
None,
|
|
158
|
+
"leader_receiver.tmux_socket is a durable physical endpoint: a short socket name from \
|
|
159
|
+
$TMUX must not be persisted because tmux -L <short> is re-rooted under the coordinator"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn leader_receiver_delivery_uses_full_socket_endpoint_not_short_l_reconstruction() {
|
|
165
|
+
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
166
|
+
let delivery = std::fs::read_to_string(manifest.join("src/messaging/delivery.rs")).unwrap();
|
|
167
|
+
let leader_receiver =
|
|
168
|
+
std::fs::read_to_string(manifest.join("src/messaging/leader_receiver.rs")).unwrap();
|
|
169
|
+
let tmux_backend = std::fs::read_to_string(manifest.join("src/tmux_backend.rs")).unwrap();
|
|
170
|
+
|
|
171
|
+
assert!(
|
|
172
|
+
tmux_backend.contains("\"-S\""),
|
|
173
|
+
"tmux backend must support `tmux -S <full-socket-path>` for persisted external leader \
|
|
174
|
+
endpoints; `-L <short-name>` is not enough when leader and coordinator TMUX_TMPDIR differ"
|
|
175
|
+
);
|
|
176
|
+
assert!(
|
|
177
|
+
!delivery.contains("TmuxBackend::for_socket_name(socket)"),
|
|
178
|
+
"worker->leader delivery must not reconstruct an external leader endpoint with \
|
|
179
|
+
`tmux -L <short-name>`; it must use the persisted full socket path endpoint"
|
|
180
|
+
);
|
|
181
|
+
assert!(
|
|
182
|
+
!leader_receiver.contains("TmuxBackend::for_socket_name(socket)"),
|
|
183
|
+
"leader_receiver live checks must verify the same full socket endpoint used by delivery, \
|
|
184
|
+
not a short socket name resolved under the coordinator's socket root"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#[test]
|
|
189
|
+
fn leader_receiver_full_endpoint_liveness_list_and_inject_use_s_path_command_shape() {
|
|
190
|
+
let endpoint = "/private/tmp/tmux-501/default";
|
|
191
|
+
let stdout = "%7\tteam-x\t0\tleader\t0\t/dev/ttys003\tbash\t1\t/Users/me/work\t1\t0\n";
|
|
192
|
+
let (be, rec, _stdin) = {
|
|
193
|
+
let recorded = Arc::new(Mutex::new(Vec::new()));
|
|
194
|
+
let stdin_recorded = Arc::new(Mutex::new(Vec::new()));
|
|
195
|
+
let runner = MockCommandRunner {
|
|
196
|
+
recorded: Arc::clone(&recorded),
|
|
197
|
+
stdin_recorded: Arc::clone(&stdin_recorded),
|
|
198
|
+
queue: Mutex::new(
|
|
199
|
+
vec![
|
|
200
|
+
MockResp::Out(ok(stdout)),
|
|
201
|
+
MockResp::Out(ok("%7\n")),
|
|
202
|
+
MockResp::Out(ok("")),
|
|
203
|
+
MockResp::Out(ok("")),
|
|
204
|
+
MockResp::Out(ok("")),
|
|
205
|
+
]
|
|
206
|
+
.into_iter()
|
|
207
|
+
.collect(),
|
|
208
|
+
),
|
|
209
|
+
default: MockResp::Out(ok("")),
|
|
210
|
+
};
|
|
211
|
+
(
|
|
212
|
+
TmuxBackend::with_runner_for_tmux_endpoint(Box::new(runner), endpoint),
|
|
213
|
+
recorded,
|
|
214
|
+
stdin_recorded,
|
|
215
|
+
)
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let _ = be.list_targets().expect("list_targets via endpoint");
|
|
219
|
+
let _ = be.liveness(&PaneId::new("%7")).expect("liveness via endpoint");
|
|
220
|
+
let _ = be
|
|
221
|
+
.inject(
|
|
222
|
+
&Target::Pane(PaneId::new("%7")),
|
|
223
|
+
&InjectPayload::Text("hello leader".to_string()),
|
|
224
|
+
Key::Enter,
|
|
225
|
+
true,
|
|
226
|
+
)
|
|
227
|
+
.expect("inject via endpoint");
|
|
228
|
+
|
|
229
|
+
let calls = rec.lock().unwrap().clone();
|
|
230
|
+
assert!(
|
|
231
|
+
calls.len() >= 5,
|
|
232
|
+
"fixture must exercise list-panes, display-message, buffer/paste, and send-keys; got {calls:?}"
|
|
233
|
+
);
|
|
234
|
+
for call in &calls {
|
|
235
|
+
assert!(
|
|
236
|
+
call.starts_with(&["tmux".to_string(), "-S".to_string(), endpoint.to_string()]),
|
|
237
|
+
"leader receiver list/liveness/inject must use tmux -S <full socket path>; got {call:?}"
|
|
238
|
+
);
|
|
239
|
+
assert!(
|
|
240
|
+
!call.windows(2).any(|w| w == ["-L".to_string(), endpoint.to_string()]),
|
|
241
|
+
"leader receiver full endpoint must never be reconstructed with -L; got {call:?}"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
assert!(
|
|
245
|
+
calls.iter().any(|call| call.iter().any(|arg| arg == "list-panes"))
|
|
246
|
+
&& calls.iter().any(|call| call.iter().any(|arg| arg == "display-message"))
|
|
247
|
+
&& calls.iter().any(|call| call.iter().any(|arg| arg == "paste-buffer"))
|
|
248
|
+
&& calls.iter().any(|call| call.iter().any(|arg| arg == "send-keys")),
|
|
249
|
+
"contract must cover liveness/list/inject command shapes; got {calls:?}"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn leader_receiver_short_endpoint_must_not_reconstruct_tmux_l_socket() {
|
|
255
|
+
let endpoint = "dl9aa40c88";
|
|
256
|
+
let (be, rec) = {
|
|
257
|
+
let recorded = Arc::new(Mutex::new(Vec::new()));
|
|
258
|
+
let runner = MockCommandRunner {
|
|
259
|
+
recorded: Arc::clone(&recorded),
|
|
260
|
+
stdin_recorded: Arc::new(Mutex::new(Vec::new())),
|
|
261
|
+
queue: Mutex::new(vec![MockResp::Out(ok(""))].into_iter().collect()),
|
|
262
|
+
default: MockResp::Out(ok("")),
|
|
263
|
+
};
|
|
264
|
+
(
|
|
265
|
+
TmuxBackend::with_runner_for_tmux_endpoint(Box::new(runner), endpoint),
|
|
266
|
+
recorded,
|
|
267
|
+
)
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
let _ = be.list_targets().expect("short endpoint should not become -L");
|
|
271
|
+
|
|
272
|
+
let calls = rec.lock().unwrap().clone();
|
|
273
|
+
assert!(
|
|
274
|
+
calls.iter().all(|call| !call.windows(2).any(|w| w == ["-L".to_string(), endpoint.to_string()])),
|
|
275
|
+
"non-canonical leader endpoints must be rejected or left unbound, never reconstructed as \
|
|
276
|
+
tmux -L <short> under the coordinator socket root; calls={calls:?}"
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
101
280
|
// ── 1. has_session: exit 0 -> true, exit 1 -> false; argv = `tmux has-session -t <s>` ──────────
|
|
102
281
|
#[test]
|
|
103
282
|
fn has_session_argv_and_exit_code_maps_to_bool() {
|
|
@@ -161,7 +161,12 @@ pub struct TmuxBackend {
|
|
|
161
161
|
runner: Box<dyn CommandRunner>,
|
|
162
162
|
/// `Some(name)` for a per-team socket -> every `tmux` argv gets `-L <name>` injected after the
|
|
163
163
|
/// leading "tmux" token; `None` (default) -> bare `tmux` on the shared default socket.
|
|
164
|
-
socket: Option<
|
|
164
|
+
socket: Option<TmuxSocketEndpoint>,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
enum TmuxSocketEndpoint {
|
|
168
|
+
Name(String),
|
|
169
|
+
Path(String),
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
impl TmuxBackend {
|
|
@@ -177,7 +182,25 @@ impl TmuxBackend {
|
|
|
177
182
|
pub fn for_workspace(workspace: &Path) -> Self {
|
|
178
183
|
Self {
|
|
179
184
|
runner: Box::new(RealCommandRunner),
|
|
180
|
-
socket: Some(socket_name_for_workspace(workspace)),
|
|
185
|
+
socket: Some(TmuxSocketEndpoint::Name(socket_name_for_workspace(workspace))),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
pub(crate) fn for_socket_name(socket: &str) -> Self {
|
|
190
|
+
if socket.is_empty() || socket == "default" {
|
|
191
|
+
Self::new()
|
|
192
|
+
} else {
|
|
193
|
+
Self { runner: Box::new(RealCommandRunner), socket: Some(TmuxSocketEndpoint::Name(socket.to_string())) }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pub(crate) fn for_tmux_endpoint(endpoint: &str) -> Self {
|
|
198
|
+
if endpoint.is_empty() || endpoint == "default" {
|
|
199
|
+
Self::new()
|
|
200
|
+
} else if Path::new(endpoint).is_absolute() {
|
|
201
|
+
Self { runner: Box::new(RealCommandRunner), socket: Some(TmuxSocketEndpoint::Path(endpoint.to_string())) }
|
|
202
|
+
} else {
|
|
203
|
+
Self::new()
|
|
181
204
|
}
|
|
182
205
|
}
|
|
183
206
|
|
|
@@ -189,7 +212,17 @@ impl TmuxBackend {
|
|
|
189
212
|
/// Backend with an injected runner bound to a per-workspace socket (tests: assert the `-L` is in
|
|
190
213
|
/// the recorded argv for a workspace-bound backend).
|
|
191
214
|
pub fn with_runner_for_workspace(runner: Box<dyn CommandRunner>, workspace: &Path) -> Self {
|
|
192
|
-
Self { runner, socket: Some(socket_name_for_workspace(workspace)) }
|
|
215
|
+
Self { runner, socket: Some(TmuxSocketEndpoint::Name(socket_name_for_workspace(workspace))) }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub(crate) fn with_runner_for_tmux_endpoint(runner: Box<dyn CommandRunner>, endpoint: &str) -> Self {
|
|
219
|
+
if Path::new(endpoint).is_absolute() {
|
|
220
|
+
Self { runner, socket: Some(TmuxSocketEndpoint::Path(endpoint.to_string())) }
|
|
221
|
+
} else if endpoint.is_empty() || endpoint == "default" {
|
|
222
|
+
Self { runner, socket: None }
|
|
223
|
+
} else {
|
|
224
|
+
Self { runner, socket: None }
|
|
225
|
+
}
|
|
193
226
|
}
|
|
194
227
|
|
|
195
228
|
/// THE RUN CHOKEPOINT: every executed `tmux` argv is funneled through here. When a per-team
|
|
@@ -197,11 +230,19 @@ impl TmuxBackend {
|
|
|
197
230
|
/// through unchanged. Non-`tmux` argv (e.g. the spawned provider command) is never rewritten.
|
|
198
231
|
fn tmux_argv(&self, argv: &[String]) -> Vec<String> {
|
|
199
232
|
match &self.socket {
|
|
200
|
-
Some(
|
|
233
|
+
Some(endpoint) if argv.first().map(String::as_str) == Some("tmux") => {
|
|
201
234
|
let mut out = Vec::with_capacity(argv.len() + 2);
|
|
202
235
|
out.push("tmux".to_string());
|
|
203
|
-
|
|
204
|
-
|
|
236
|
+
match endpoint {
|
|
237
|
+
TmuxSocketEndpoint::Name(socket) => {
|
|
238
|
+
out.push("-L".to_string());
|
|
239
|
+
out.push(socket.clone());
|
|
240
|
+
}
|
|
241
|
+
TmuxSocketEndpoint::Path(socket) => {
|
|
242
|
+
out.push("-S".to_string());
|
|
243
|
+
out.push(socket.clone());
|
|
244
|
+
}
|
|
245
|
+
}
|
|
205
246
|
out.extend(argv.iter().skip(1).cloned());
|
|
206
247
|
out
|
|
207
248
|
}
|
|
@@ -237,6 +278,17 @@ pub(crate) fn socket_name_for_workspace(workspace: &Path) -> String {
|
|
|
237
278
|
format!("ta-{:012x}", hasher.finish() & 0xffff_ffff_ffff)
|
|
238
279
|
}
|
|
239
280
|
|
|
281
|
+
pub(crate) fn socket_name_from_tmux_env() -> Option<String> {
|
|
282
|
+
let tmux = std::env::var("TMUX")
|
|
283
|
+
.ok()
|
|
284
|
+
.filter(|value| !value.is_empty())?;
|
|
285
|
+
let socket_path = tmux.split(',').next().unwrap_or("").trim();
|
|
286
|
+
if socket_path.is_empty() || !Path::new(socket_path).is_absolute() {
|
|
287
|
+
return None;
|
|
288
|
+
}
|
|
289
|
+
Some(socket_path.to_string())
|
|
290
|
+
}
|
|
291
|
+
|
|
240
292
|
/// Deterministic FNV-1a (64-bit) — std `DefaultHasher` is NOT stable across releases, so a fixed
|
|
241
293
|
/// FNV keeps the socket identical for the CLI, the daemon, and every later op on the same workspace.
|
|
242
294
|
struct Fnv1a(u64);
|
package/npm/install.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const packageRoot = path.resolve(__dirname, "..");
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
13
|
+
const DOCTOR_TIMEOUT_MS = 5000;
|
|
13
14
|
|
|
14
15
|
const command = process.argv[2] || "install";
|
|
15
16
|
const args = process.argv.slice(3);
|
|
@@ -87,11 +88,20 @@ function install(argv) {
|
|
|
87
88
|
console.log("skill: installed for Codex and Claude");
|
|
88
89
|
console.log(`PATH: ensure ${binDir} is on PATH`);
|
|
89
90
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
const doctorWorkspace = makeDoctorWorkspace();
|
|
92
|
+
try {
|
|
93
|
+
const doctor = spawnSync(teamAgent, ["doctor", "--json", "--workspace", doctorWorkspace], {
|
|
94
|
+
text: true,
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
timeout: DOCTOR_TIMEOUT_MS,
|
|
97
|
+
});
|
|
98
|
+
if (doctor.status === 0) {
|
|
99
|
+
console.log("doctor: ok");
|
|
100
|
+
} else {
|
|
101
|
+
console.log("doctor: has blockers; run `team-agent doctor` after updating PATH");
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
fs.rmSync(doctorWorkspace, { recursive: true, force: true });
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -103,8 +113,16 @@ function runDoctor(argv) {
|
|
|
103
113
|
console.error(`team-agent wrapper not found: ${teamAgent}`);
|
|
104
114
|
process.exit(1);
|
|
105
115
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
116
|
+
const doctorWorkspace = makeDoctorWorkspace();
|
|
117
|
+
try {
|
|
118
|
+
const proc = spawnSync(teamAgent, ["doctor", "--workspace", doctorWorkspace], {
|
|
119
|
+
stdio: "inherit",
|
|
120
|
+
timeout: DOCTOR_TIMEOUT_MS,
|
|
121
|
+
});
|
|
122
|
+
process.exit(proc.status ?? 1);
|
|
123
|
+
} finally {
|
|
124
|
+
fs.rmSync(doctorWorkspace, { recursive: true, force: true });
|
|
125
|
+
}
|
|
108
126
|
}
|
|
109
127
|
|
|
110
128
|
function uninstall(argv) {
|
|
@@ -217,6 +235,10 @@ function skillDestinations() {
|
|
|
217
235
|
];
|
|
218
236
|
}
|
|
219
237
|
|
|
238
|
+
function makeDoctorWorkspace() {
|
|
239
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "team-agent-doctor-"));
|
|
240
|
+
}
|
|
241
|
+
|
|
220
242
|
function copyTree(src, dest) {
|
|
221
243
|
const stat = fs.lstatSync(src);
|
|
222
244
|
if (stat.isDirectory()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.3.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.3.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.3.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.3.1",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.3.1",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.3.1"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|