@team-agent/installer 0.3.7 → 0.3.8
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/adapters.rs +52 -7
- package/crates/team-agent/src/cli/emit.rs +112 -0
- package/crates/team-agent/src/cli/mod.rs +121 -28
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +83 -1
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +86 -21
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +76 -0
- package/crates/team-agent/src/leader/owner_bind.rs +59 -20
- package/crates/team-agent/src/lifecycle/launch.rs +203 -16
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +16 -10
- package/crates/team-agent/src/lifecycle/tests/core.rs +192 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +36 -0
- package/crates/team-agent/src/lifecycle/types.rs +2 -0
- package/crates/team-agent/src/provider/adapter.rs +177 -15
- package/crates/team-agent/src/state/identity.rs +29 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +44 -0
- package/crates/team-agent/src/tmux_backend.rs +90 -7
- package/crates/team-agent/src/transport/test_support.rs +57 -4
- package/crates/team-agent/src/transport.rs +13 -0
- package/npm/install.mjs +31 -35
- package/package.json +4 -4
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
use std::collections::BTreeMap;
|
|
19
19
|
use std::hash::{Hash, Hasher};
|
|
20
20
|
use std::io::{Read, Write};
|
|
21
|
+
use std::os::unix::fs::FileTypeExt;
|
|
21
22
|
use std::path::{Path, PathBuf};
|
|
22
23
|
use std::process::Stdio;
|
|
23
24
|
use std::time::{Duration, Instant};
|
|
@@ -331,6 +332,20 @@ pub(crate) fn socket_name_for_workspace(workspace: &Path) -> String {
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
pub(crate) fn socket_path_for_workspace(workspace: &Path) -> Option<PathBuf> {
|
|
335
|
+
if let Some(existing) = existing_socket_path_for_workspace(workspace) {
|
|
336
|
+
return Some(existing);
|
|
337
|
+
}
|
|
338
|
+
let uid = unsafe { libc::geteuid() };
|
|
339
|
+
let default_root = PathBuf::from(format!("/tmp/tmux-{uid}"));
|
|
340
|
+
let default_root = default_root.canonicalize().unwrap_or(default_root);
|
|
341
|
+
Some(default_root.join(socket_name_for_workspace(workspace)))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
pub(crate) fn socket_probe_missing_for_workspace(workspace: &Path) -> bool {
|
|
345
|
+
existing_socket_path_for_workspace(workspace).is_none()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
fn existing_socket_path_for_workspace(workspace: &Path) -> Option<PathBuf> {
|
|
334
349
|
let socket_name = socket_name_for_workspace(workspace);
|
|
335
350
|
let roots = tmux_socket_roots();
|
|
336
351
|
for root in &roots {
|
|
@@ -340,12 +355,19 @@ pub(crate) fn socket_path_for_workspace(workspace: &Path) -> Option<PathBuf> {
|
|
|
340
355
|
return Some(candidate.canonicalize().unwrap_or(candidate));
|
|
341
356
|
}
|
|
342
357
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
358
|
+
None
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pub(crate) fn socket_missing_hint_for_workspace(workspace: &Path) -> String {
|
|
362
|
+
let socket_name = socket_name_for_workspace(workspace);
|
|
363
|
+
let roots = tmux_socket_roots()
|
|
364
|
+
.into_iter()
|
|
365
|
+
.map(|root| root.display().to_string())
|
|
366
|
+
.collect::<Vec<_>>()
|
|
367
|
+
.join(", ");
|
|
368
|
+
format!(
|
|
369
|
+
"tmux socket {socket_name} not found under [{roots}]; run `team-agent attach-leader` or restart the team before attaching"
|
|
370
|
+
)
|
|
349
371
|
}
|
|
350
372
|
|
|
351
373
|
pub(crate) fn attach_command_for_workspace(
|
|
@@ -373,7 +395,7 @@ pub(crate) fn attach_commands_for_windows<'a>(
|
|
|
373
395
|
.collect()
|
|
374
396
|
}
|
|
375
397
|
|
|
376
|
-
fn tmux_socket_roots() -> Vec<PathBuf> {
|
|
398
|
+
pub(crate) fn tmux_socket_roots() -> Vec<PathBuf> {
|
|
377
399
|
let uid = unsafe { libc::geteuid() };
|
|
378
400
|
let mut roots = vec![PathBuf::from(format!("/tmp/tmux-{uid}"))];
|
|
379
401
|
if let Some(tmpdir) = std::env::var_os("TMPDIR") {
|
|
@@ -384,6 +406,29 @@ fn tmux_socket_roots() -> Vec<PathBuf> {
|
|
|
384
406
|
roots
|
|
385
407
|
}
|
|
386
408
|
|
|
409
|
+
pub(crate) fn tmux_socket_endpoints() -> Vec<String> {
|
|
410
|
+
let mut endpoints = Vec::new();
|
|
411
|
+
for root in tmux_socket_roots() {
|
|
412
|
+
let Ok(entries) = std::fs::read_dir(root) else {
|
|
413
|
+
continue;
|
|
414
|
+
};
|
|
415
|
+
for entry in entries.flatten() {
|
|
416
|
+
let Ok(file_type) = entry.file_type() else {
|
|
417
|
+
continue;
|
|
418
|
+
};
|
|
419
|
+
if !file_type.is_socket() {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
let path = entry.path();
|
|
423
|
+
let path = path.canonicalize().unwrap_or(path);
|
|
424
|
+
endpoints.push(path.to_string_lossy().to_string());
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
endpoints.sort();
|
|
428
|
+
endpoints.dedup();
|
|
429
|
+
endpoints
|
|
430
|
+
}
|
|
431
|
+
|
|
387
432
|
pub(crate) fn socket_name_from_tmux_env() -> Option<String> {
|
|
388
433
|
let tmux = std::env::var("TMUX")
|
|
389
434
|
.ok()
|
|
@@ -673,6 +718,10 @@ impl Transport for TmuxBackend {
|
|
|
673
718
|
BackendKind::Tmux
|
|
674
719
|
}
|
|
675
720
|
|
|
721
|
+
fn probes_real_tmux_socket_roots(&self) -> bool {
|
|
722
|
+
true
|
|
723
|
+
}
|
|
724
|
+
|
|
676
725
|
fn spawn_first(
|
|
677
726
|
&self,
|
|
678
727
|
session: &SessionName,
|
|
@@ -859,6 +908,40 @@ impl Transport for TmuxBackend {
|
|
|
859
908
|
}
|
|
860
909
|
}
|
|
861
910
|
|
|
911
|
+
fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
912
|
+
let argv = self.tmux_argv(&[
|
|
913
|
+
"tmux".to_string(),
|
|
914
|
+
"display-message".to_string(),
|
|
915
|
+
"-p".to_string(),
|
|
916
|
+
"-t".to_string(),
|
|
917
|
+
pane.as_str().to_string(),
|
|
918
|
+
"#{pane_id}".to_string(),
|
|
919
|
+
]);
|
|
920
|
+
let output = self.runner.run(&argv)?;
|
|
921
|
+
if output.success {
|
|
922
|
+
let pane_id = output.stdout.trim();
|
|
923
|
+
if pane_id.is_empty() {
|
|
924
|
+
return Ok(Some(false));
|
|
925
|
+
}
|
|
926
|
+
if pane_id == pane.as_str()
|
|
927
|
+
&& pane_id.starts_with('%')
|
|
928
|
+
&& pane_id[1..].chars().all(|ch| ch.is_ascii_digit())
|
|
929
|
+
{
|
|
930
|
+
return Ok(Some(true));
|
|
931
|
+
}
|
|
932
|
+
return Ok(None);
|
|
933
|
+
}
|
|
934
|
+
let stderr = output.stderr.to_ascii_lowercase();
|
|
935
|
+
if stderr.contains("can't find pane")
|
|
936
|
+
|| stderr.contains("no such pane")
|
|
937
|
+
|| (stderr.contains("can't find") && stderr.contains("pane"))
|
|
938
|
+
{
|
|
939
|
+
Ok(Some(false))
|
|
940
|
+
} else {
|
|
941
|
+
Ok(None)
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
862
945
|
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
863
946
|
// P5 (C-P5-3): `#{pane_pid}` rides the single list-panes call (field index 11),
|
|
864
947
|
// killing the per-pane display-message N+1 fallback.
|
|
@@ -18,18 +18,39 @@ pub struct SpawnRecord {
|
|
|
18
18
|
pub argv: Vec<String>,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
#[derive(Debug, Clone
|
|
21
|
+
#[derive(Debug, Clone)]
|
|
22
22
|
struct OfflineState {
|
|
23
23
|
session_present: bool,
|
|
24
24
|
session_absent_after_spawn_first: bool,
|
|
25
25
|
targets: Vec<PaneInfo>,
|
|
26
26
|
windows: Vec<WindowName>,
|
|
27
|
+
pane_presence: BTreeMap<String, bool>,
|
|
28
|
+
liveness: BTreeMap<String, PaneLiveness>,
|
|
29
|
+
default_liveness: PaneLiveness,
|
|
27
30
|
calls: Vec<&'static str>,
|
|
28
31
|
spawns: Vec<SpawnRecord>,
|
|
29
32
|
inject_targets: Vec<Target>,
|
|
30
33
|
inject_payloads: Vec<String>,
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
impl Default for OfflineState {
|
|
37
|
+
fn default() -> Self {
|
|
38
|
+
Self {
|
|
39
|
+
session_present: false,
|
|
40
|
+
session_absent_after_spawn_first: false,
|
|
41
|
+
targets: Vec::new(),
|
|
42
|
+
windows: Vec::new(),
|
|
43
|
+
pane_presence: BTreeMap::new(),
|
|
44
|
+
liveness: BTreeMap::new(),
|
|
45
|
+
default_liveness: PaneLiveness::Unknown,
|
|
46
|
+
calls: Vec::new(),
|
|
47
|
+
spawns: Vec::new(),
|
|
48
|
+
inject_targets: Vec::new(),
|
|
49
|
+
inject_payloads: Vec::new(),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
#[derive(Debug, Clone, Default)]
|
|
34
55
|
pub struct OfflineTransport {
|
|
35
56
|
inner: Arc<Mutex<OfflineState>>,
|
|
@@ -60,6 +81,25 @@ impl OfflineTransport {
|
|
|
60
81
|
self
|
|
61
82
|
}
|
|
62
83
|
|
|
84
|
+
pub fn with_default_liveness(self, liveness: PaneLiveness) -> Self {
|
|
85
|
+
self.with_state(|state| state.default_liveness = liveness);
|
|
86
|
+
self
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub fn with_liveness(self, pane: impl Into<String>, liveness: PaneLiveness) -> Self {
|
|
90
|
+
self.with_state(|state| {
|
|
91
|
+
state.liveness.insert(pane.into(), liveness);
|
|
92
|
+
});
|
|
93
|
+
self
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub fn with_pane_presence(self, pane: impl Into<String>, present: bool) -> Self {
|
|
97
|
+
self.with_state(|state| {
|
|
98
|
+
state.pane_presence.insert(pane.into(), present);
|
|
99
|
+
});
|
|
100
|
+
self
|
|
101
|
+
}
|
|
102
|
+
|
|
63
103
|
pub fn calls(&self) -> Vec<&'static str> {
|
|
64
104
|
self.with_state(|state| state.calls.clone())
|
|
65
105
|
}
|
|
@@ -198,9 +238,22 @@ impl Transport for OfflineTransport {
|
|
|
198
238
|
Ok(None)
|
|
199
239
|
}
|
|
200
240
|
|
|
201
|
-
fn liveness(&self,
|
|
202
|
-
self.
|
|
203
|
-
|
|
241
|
+
fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
242
|
+
Ok(self.with_state(|state| {
|
|
243
|
+
state.calls.push("liveness");
|
|
244
|
+
state
|
|
245
|
+
.liveness
|
|
246
|
+
.get(pane.as_str())
|
|
247
|
+
.copied()
|
|
248
|
+
.unwrap_or(state.default_liveness)
|
|
249
|
+
}))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
253
|
+
Ok(self.with_state(|state| {
|
|
254
|
+
state.calls.push("has_pane");
|
|
255
|
+
state.pane_presence.get(pane.as_str()).copied()
|
|
256
|
+
}))
|
|
204
257
|
}
|
|
205
258
|
|
|
206
259
|
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
@@ -399,6 +399,12 @@ pub trait Transport: Send + Sync {
|
|
|
399
399
|
/// 后端种类(诊断/事件用)。
|
|
400
400
|
fn kind(&self) -> BackendKind;
|
|
401
401
|
|
|
402
|
+
/// Only the concrete tmux backend should scan real tmux socket roots.
|
|
403
|
+
/// Test doubles stay hermetic and use their injected probe results.
|
|
404
|
+
fn probes_real_tmux_socket_roots(&self) -> bool {
|
|
405
|
+
false
|
|
406
|
+
}
|
|
407
|
+
|
|
402
408
|
// —— SPAWN(ST):所有后端天然满足;cwd/env 是 spawn 参数,无独立动词(§gap-setenv)——
|
|
403
409
|
|
|
404
410
|
/// tmux=`new-session -d` / wezterm=`spawn --new-window` / conpty=`openpty`+spawn。
|
|
@@ -481,6 +487,13 @@ pub trait Transport: Send + Sync {
|
|
|
481
487
|
/// pane 存活三态(`PaneLiveness`,bug-085 穷尽 match;unknown ≠ dead ≠ live)。
|
|
482
488
|
fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError>;
|
|
483
489
|
|
|
490
|
+
/// Cheap direct pane existence check when a backend can prove it. `Ok(None)`
|
|
491
|
+
/// preserves the existing Unknown boundary.
|
|
492
|
+
fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
493
|
+
let _ = pane;
|
|
494
|
+
Ok(None)
|
|
495
|
+
}
|
|
496
|
+
|
|
484
497
|
// —— ENUMERATE / IDENTITY(SL + 进程探测):身份/rebind 地基 ——
|
|
485
498
|
|
|
486
499
|
/// 全局枚举所有 pane + 每 pane 的 leader_env。tmux=`list-panes -a` + 读进程 env;
|
package/npm/install.mjs
CHANGED
|
@@ -80,13 +80,13 @@ function install(argv) {
|
|
|
80
80
|
writeExecWrapper(path.join(binDir, "team-agent"), runtimeBinary, []);
|
|
81
81
|
writeExecWrapper(path.join(binDir, "team_orchestrator"), runtimeBinary, ["mcp-server"]);
|
|
82
82
|
writeExecWrapper(path.join(binDir, "team-agent-coordinator"), runtimeBinary, ["coordinator"]);
|
|
83
|
-
installSkills();
|
|
83
|
+
installSkills(runtimeBinary);
|
|
84
84
|
|
|
85
85
|
const teamAgent = path.join(binDir, "team-agent");
|
|
86
86
|
console.log(`installed: ${teamAgent}`);
|
|
87
87
|
console.log(`runtime: ${dest}`);
|
|
88
88
|
console.log(`binary: ${platformBinary.packageName}`);
|
|
89
|
-
console.log("skill: installed for Codex and
|
|
89
|
+
console.log("skill: installed for Codex, Claude and Copilot");
|
|
90
90
|
console.log(`PATH: ensure ${binDir} is on PATH`);
|
|
91
91
|
|
|
92
92
|
// 0.3.6 hotfix · C-5 cr verdict — post-install binary smoke 门(走 `--help`
|
|
@@ -149,14 +149,24 @@ function runDoctor(argv) {
|
|
|
149
149
|
function uninstall(argv) {
|
|
150
150
|
const opts = parseOptions(argv);
|
|
151
151
|
const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
|
|
152
|
+
// 卸载 skill 走二进制单源(同一 SkillTarget 表 codex/claude/copilot),在删 wrapper 前调
|
|
153
|
+
// (删 wrapper 后 PATH 上的 team-agent 没了,但 runtime 二进制仍在;用 runtime 二进制直调)。
|
|
154
|
+
const teamAgentBin = path.join(prefix, "bin", "team-agent");
|
|
155
|
+
if (fs.existsSync(teamAgentBin)) {
|
|
156
|
+
const res = spawnSync(teamAgentBin, ["install-skill", "--target", "all", "--uninstall", "--json"], {
|
|
157
|
+
text: true,
|
|
158
|
+
encoding: "utf8",
|
|
159
|
+
timeout: VERSION_SMOKE_TIMEOUT_MS,
|
|
160
|
+
});
|
|
161
|
+
if (res.status !== 0) {
|
|
162
|
+
console.error(`WARN: skill uninstall via binary failed (status=${res.status ?? "signal"}); skill dirs may remain under ~/.codex|.claude|.copilot/skills/team-agent`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
152
165
|
for (const name of ["team-agent", "team_orchestrator", "team-agent-coordinator"]) {
|
|
153
166
|
fs.rmSync(path.join(prefix, "bin", name), { force: true });
|
|
154
167
|
}
|
|
155
|
-
for (const skillDir of skillDestinations()) {
|
|
156
|
-
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
157
|
-
}
|
|
158
168
|
console.log(`removed wrappers from ${path.join(prefix, "bin")}`);
|
|
159
|
-
console.log("removed skills from ~/.codex
|
|
169
|
+
console.log("removed skills from ~/.codex, ~/.claude and ~/.copilot skills/team-agent");
|
|
160
170
|
if (opts.purgeRuntime) {
|
|
161
171
|
const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
|
|
162
172
|
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
@@ -238,46 +248,32 @@ exec ${shellQuote(binary)} ${argPrefix}"$@"
|
|
|
238
248
|
fs.chmodSync(file, 0o755);
|
|
239
249
|
}
|
|
240
250
|
|
|
241
|
-
|
|
251
|
+
// RED-1 根治(单源):skill 安装唯一实现在二进制 `install-skill`(SkillTarget 表:
|
|
252
|
+
// codex/claude/copilot)。install.mjs 不再有自己的 JS 拷贝逻辑/目标硬编码——改调二进制,
|
|
253
|
+
// 失败显式报错(非零退出),绝不静默回退 JS。
|
|
254
|
+
function installSkills(runtimeBinary) {
|
|
242
255
|
const source = path.join(packageRoot, "skills", "team-agent");
|
|
243
256
|
if (!fs.existsSync(source)) {
|
|
244
257
|
throw new Error(`skill source not found: ${source}`);
|
|
245
258
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
const res = spawnSync(runtimeBinary, ["install-skill", "--target", "all", "--source", source, "--json"], {
|
|
260
|
+
text: true,
|
|
261
|
+
encoding: "utf8",
|
|
262
|
+
timeout: VERSION_SMOKE_TIMEOUT_MS,
|
|
263
|
+
});
|
|
264
|
+
if (res.status !== 0) {
|
|
265
|
+
const log = (res.stderr || res.stdout || "").trim() || "no stderr/stdout";
|
|
266
|
+
console.error(`ERROR: skill install failed (status=${res.status ?? "signal"})`);
|
|
267
|
+
console.error(`ACTION: reinstall, or run \`team-agent install-skill --target all --source ${source}\` manually`);
|
|
268
|
+
console.error(`LOG: ${runtimeBinary} install-skill --target all => ${log}`);
|
|
269
|
+
process.exit(1);
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
|
|
252
|
-
function skillDestinations() {
|
|
253
|
-
return [
|
|
254
|
-
path.join(os.homedir(), ".codex", "skills", "team-agent"),
|
|
255
|
-
path.join(os.homedir(), ".claude", "skills", "team-agent"),
|
|
256
|
-
];
|
|
257
|
-
}
|
|
258
|
-
|
|
259
273
|
function makeDoctorWorkspace() {
|
|
260
274
|
return fs.mkdtempSync(path.join(os.tmpdir(), "team-agent-doctor-"));
|
|
261
275
|
}
|
|
262
276
|
|
|
263
|
-
function copyTree(src, dest) {
|
|
264
|
-
const stat = fs.lstatSync(src);
|
|
265
|
-
if (stat.isDirectory()) {
|
|
266
|
-
fs.mkdirSync(dest, { recursive: true, mode: stat.mode });
|
|
267
|
-
for (const entry of fs.readdirSync(src)) {
|
|
268
|
-
if (entry === ".DS_Store") {
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
copyTree(path.join(src, entry), path.join(dest, entry));
|
|
272
|
-
}
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (stat.isFile()) {
|
|
276
|
-
fs.copyFileSync(src, dest);
|
|
277
|
-
fs.chmodSync(dest, stat.mode);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
277
|
function expandHome(value) {
|
|
282
278
|
if (value === "~") {
|
|
283
279
|
return os.homedir();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
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.8",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.3.8",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.3.8"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|