teamai-cli 0.24.0-beta.7 → 0.24.0-beta.9
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/CHANGELOG.md +2 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/index.js +438 -163
- package/package.json +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
11
11
|
|
|
12
12
|
### 🐛 Bug Fixes
|
|
13
13
|
|
|
14
|
+
- The GitHub and CNB providers resolve their CLI to a launchable absolute path and start it through cross-spawn, so on Windows they no longer answer "installed" while every call fails silently ([#520](https://github.com/Tencent/teamai-cli/pull/520)).
|
|
15
|
+
- `enabledAgents` now also gates CLI builtin deploy, CLAUDE.md-class injects, and last-pull skip-sync targets, so an already-installed tool outside the whitelist is not written to ([#510](https://github.com/Tencent/teamai-cli/issues/510)).
|
|
14
16
|
- `teamai status` counts rule files in subdirectories recursively ([#437](https://github.com/Tencent/teamai-cli/pull/437)).
|
|
15
17
|
- Codex Stop-phase contribution hints are deferred to the next prompt, so the host no longer rejects `additionalContext` ([#441](https://github.com/Tencent/teamai-cli/pull/441)).
|
|
16
18
|
|
package/README.md
CHANGED
|
@@ -199,6 +199,8 @@ teamai codebase --reconcile --output /path/to/repo # map product docs to code pa
|
|
|
199
199
|
teamai codebase --lint --output /path/to/repo # check the locally extracted graph
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
Extract writes `teamwiki/evidence/code/<project>/_manifest.json` even when AI enrichment is skipped or produces nothing, so `--deep-enrich` can start.
|
|
203
|
+
|
|
202
204
|
The graph stores components, interfaces, configs, and cross-repo import edges. `teamai recall` uses it for graph-boosted re-ranking.
|
|
203
205
|
When a recall hit comes from a codebase page, the result includes a `Sources:` line listing the relevant source file paths — giving agents a direct starting point for code changes instead of re-exploring the repo.
|
|
204
206
|
|
package/README.zh-CN.md
CHANGED
|
@@ -199,6 +199,8 @@ teamai codebase --reconcile --output /path/to/repo # 将产品文档映射到代
|
|
|
199
199
|
teamai codebase --lint --output /path/to/repo # 检查本地提取的图谱
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
只要 extract 发现了组件,就会写入 `teamwiki/evidence/code/<project>/_manifest.json`(包括跳过 AI 增强或增强没有产出的情况),因此 `--deep-enrich` 可以接着跑。
|
|
203
|
+
|
|
202
204
|
图谱存储组件、接口、配置和跨仓库依赖边。`teamai recall` 利用图谱进行增强排名。
|
|
203
205
|
当召回命中 codebase 页面时,结果会附带一行 `Sources:`,列出相关源文件路径,供 agent 直接作为代码改动的入口,无需重新探索代码库。
|
|
204
206
|
|
package/dist/index.js
CHANGED
|
@@ -171,6 +171,7 @@ __export(fs_exports, {
|
|
|
171
171
|
listFiles: () => listFiles,
|
|
172
172
|
listFilesRecursive: () => listFilesRecursive,
|
|
173
173
|
pathExists: () => pathExists,
|
|
174
|
+
pruneEmptyDirs: () => pruneEmptyDirs,
|
|
174
175
|
readFileSafe: () => readFileSafe,
|
|
175
176
|
readJson: () => readJson,
|
|
176
177
|
remove: () => remove,
|
|
@@ -255,6 +256,32 @@ async function copyDir(src, dest) {
|
|
|
255
256
|
filter: (srcPath) => !isIgnored(path2.basename(srcPath))
|
|
256
257
|
});
|
|
257
258
|
}
|
|
259
|
+
async function pruneEmptyDirs(target) {
|
|
260
|
+
const expanded = expandHome(target);
|
|
261
|
+
let entries;
|
|
262
|
+
try {
|
|
263
|
+
const stat8 = await fse.lstat(expanded);
|
|
264
|
+
if (!stat8.isDirectory() || stat8.isSymbolicLink()) return false;
|
|
265
|
+
entries = await fse.readdir(expanded, { withFileTypes: true });
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
let empty = true;
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (entry.isDirectory()) {
|
|
272
|
+
if (!await pruneEmptyDirs(path2.join(expanded, entry.name))) empty = false;
|
|
273
|
+
} else {
|
|
274
|
+
empty = false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!empty) return false;
|
|
278
|
+
try {
|
|
279
|
+
await fse.rmdir(expanded);
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
258
285
|
async function copyFile(src, dest) {
|
|
259
286
|
const destExpanded = expandHome(dest);
|
|
260
287
|
await fse.ensureDir(path2.dirname(destExpanded));
|
|
@@ -868,6 +895,9 @@ var init_types = __esm({
|
|
|
868
895
|
* team repo never receives stat commits (e.g. read-only pull setups).
|
|
869
896
|
* Default: on. */
|
|
870
897
|
usageReport: z.boolean().optional(),
|
|
898
|
+
/** Run `git submodule update --init` on pull so skills distributed as git
|
|
899
|
+
* submodules are populated and kept current. Off by default. */
|
|
900
|
+
submodules: z.boolean().optional(),
|
|
871
901
|
// MCP paths are only set for tools whose config location has been verified.
|
|
872
902
|
// Tools left without `mcp` are skipped by MCP sync rather than guessed at, so a
|
|
873
903
|
// wrong guess can never create a junk config file on a user's machine.
|
|
@@ -3212,7 +3242,7 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
|
3212
3242
|
log.debug(`Skipping built-in rules for ${tool}: tool not installed`);
|
|
3213
3243
|
continue;
|
|
3214
3244
|
}
|
|
3215
|
-
if (localConfig &&
|
|
3245
|
+
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
3216
3246
|
const rulesDir = path14.join(baseDir, toolPath.rules);
|
|
3217
3247
|
if (!await pathExists(rulesDir)) continue;
|
|
3218
3248
|
try {
|
|
@@ -3451,7 +3481,7 @@ var init_pre_push_sync = __esm({
|
|
|
3451
3481
|
});
|
|
3452
3482
|
|
|
3453
3483
|
// src/providers/types.ts
|
|
3454
|
-
var RepoNotFoundError;
|
|
3484
|
+
var RepoNotFoundError, OrganizationNotFoundError, RepoCreatePermissionError;
|
|
3455
3485
|
var init_types2 = __esm({
|
|
3456
3486
|
"src/providers/types.ts"() {
|
|
3457
3487
|
"use strict";
|
|
@@ -3461,6 +3491,26 @@ var init_types2 = __esm({
|
|
|
3461
3491
|
this.name = "RepoNotFoundError";
|
|
3462
3492
|
}
|
|
3463
3493
|
};
|
|
3494
|
+
OrganizationNotFoundError = class extends Error {
|
|
3495
|
+
org;
|
|
3496
|
+
createUrl;
|
|
3497
|
+
constructor(org, createUrl) {
|
|
3498
|
+
super(`Organization "${org}" not found.`);
|
|
3499
|
+
this.name = "OrganizationNotFoundError";
|
|
3500
|
+
this.org = org;
|
|
3501
|
+
this.createUrl = createUrl;
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
RepoCreatePermissionError = class extends Error {
|
|
3505
|
+
repo;
|
|
3506
|
+
createUrl;
|
|
3507
|
+
constructor(repo, createUrl) {
|
|
3508
|
+
super(`No permission to create repo "${repo}".`);
|
|
3509
|
+
this.name = "RepoCreatePermissionError";
|
|
3510
|
+
this.repo = repo;
|
|
3511
|
+
this.createUrl = createUrl;
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3464
3514
|
}
|
|
3465
3515
|
});
|
|
3466
3516
|
|
|
@@ -4023,20 +4073,82 @@ var init_tgit = __esm({
|
|
|
4023
4073
|
}
|
|
4024
4074
|
});
|
|
4025
4075
|
|
|
4026
|
-
// src/
|
|
4027
|
-
import {
|
|
4028
|
-
|
|
4076
|
+
// src/utils/cli-path.ts
|
|
4077
|
+
import { execFileSync } from "child_process";
|
|
4078
|
+
import { existsSync } from "fs";
|
|
4079
|
+
function pickWindowsCommand(whereOutput) {
|
|
4080
|
+
const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
4081
|
+
for (const ext of WIN_EXEC_EXTENSIONS) {
|
|
4082
|
+
const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
|
|
4083
|
+
if (hit !== void 0) return hit;
|
|
4084
|
+
}
|
|
4085
|
+
return null;
|
|
4086
|
+
}
|
|
4087
|
+
function whereOnWindows(cmd) {
|
|
4029
4088
|
try {
|
|
4030
|
-
const
|
|
4031
|
-
encoding: "
|
|
4032
|
-
stdio: ["
|
|
4089
|
+
const out = execFileSync("where", [cmd], {
|
|
4090
|
+
encoding: "utf8",
|
|
4091
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4092
|
+
shell: false,
|
|
4093
|
+
timeout: CLI_DETECT_TIMEOUT_MS
|
|
4033
4094
|
});
|
|
4034
|
-
const
|
|
4035
|
-
return
|
|
4095
|
+
const p = pickWindowsCommand(out);
|
|
4096
|
+
return p !== null && existsSync(p) ? p : null;
|
|
4036
4097
|
} catch {
|
|
4037
4098
|
return null;
|
|
4038
4099
|
}
|
|
4039
4100
|
}
|
|
4101
|
+
function whichOnPosix(cmd) {
|
|
4102
|
+
try {
|
|
4103
|
+
const p = execFileSync("bash", ["-lc", `command -v ${cmd}`], {
|
|
4104
|
+
encoding: "utf8",
|
|
4105
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4106
|
+
shell: false,
|
|
4107
|
+
timeout: CLI_DETECT_TIMEOUT_MS
|
|
4108
|
+
}).trim();
|
|
4109
|
+
if (p && existsSync(p)) return p;
|
|
4110
|
+
} catch {
|
|
4111
|
+
}
|
|
4112
|
+
try {
|
|
4113
|
+
const p = execFileSync("zsh", ["-lc", `command -v ${cmd}`], {
|
|
4114
|
+
encoding: "utf8",
|
|
4115
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4116
|
+
shell: false,
|
|
4117
|
+
timeout: CLI_DETECT_TIMEOUT_MS
|
|
4118
|
+
}).trim();
|
|
4119
|
+
if (p && existsSync(p)) return p;
|
|
4120
|
+
} catch {
|
|
4121
|
+
}
|
|
4122
|
+
try {
|
|
4123
|
+
const p = execFileSync("which", [cmd], {
|
|
4124
|
+
encoding: "utf8",
|
|
4125
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4126
|
+
shell: false,
|
|
4127
|
+
timeout: CLI_DETECT_TIMEOUT_MS
|
|
4128
|
+
}).trim();
|
|
4129
|
+
if (p && existsSync(p)) return p;
|
|
4130
|
+
} catch {
|
|
4131
|
+
}
|
|
4132
|
+
return null;
|
|
4133
|
+
}
|
|
4134
|
+
function resolveCliPath(cmd, platform = process.platform) {
|
|
4135
|
+
return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
|
|
4136
|
+
}
|
|
4137
|
+
var CLI_DETECT_TIMEOUT_MS, WIN_EXEC_EXTENSIONS;
|
|
4138
|
+
var init_cli_path = __esm({
|
|
4139
|
+
"src/utils/cli-path.ts"() {
|
|
4140
|
+
"use strict";
|
|
4141
|
+
CLI_DETECT_TIMEOUT_MS = 5e3;
|
|
4142
|
+
WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
|
|
4143
|
+
}
|
|
4144
|
+
});
|
|
4145
|
+
|
|
4146
|
+
// src/providers/github/gh-cli.ts
|
|
4147
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
4148
|
+
import crossSpawn from "cross-spawn";
|
|
4149
|
+
function getGhPath() {
|
|
4150
|
+
return resolveCliPath("gh");
|
|
4151
|
+
}
|
|
4040
4152
|
function isGhInstalled() {
|
|
4041
4153
|
return getGhPath() !== null;
|
|
4042
4154
|
}
|
|
@@ -4049,14 +4161,14 @@ function ghExec(args, options) {
|
|
|
4049
4161
|
}
|
|
4050
4162
|
log.debug(`gh exec: ${ghPath} ${args.join(" ")}`);
|
|
4051
4163
|
if (options?.inheritStdio) {
|
|
4052
|
-
const result2 =
|
|
4164
|
+
const result2 = crossSpawn.sync(ghPath, args, {
|
|
4053
4165
|
stdio: "inherit",
|
|
4054
4166
|
env: { ...process.env, ...options.env ?? {} },
|
|
4055
4167
|
cwd: options.cwd
|
|
4056
4168
|
});
|
|
4057
4169
|
return { stdout: "", stderr: "", status: result2.status ?? 1 };
|
|
4058
4170
|
}
|
|
4059
|
-
const result =
|
|
4171
|
+
const result = crossSpawn.sync(ghPath, args, {
|
|
4060
4172
|
env: { ...process.env, ...options?.env ?? {} },
|
|
4061
4173
|
encoding: "utf-8",
|
|
4062
4174
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -4304,6 +4416,7 @@ var init_gh_cli = __esm({
|
|
|
4304
4416
|
"src/providers/github/gh-cli.ts"() {
|
|
4305
4417
|
"use strict";
|
|
4306
4418
|
init_logger();
|
|
4419
|
+
init_cli_path();
|
|
4307
4420
|
GITHUB_API = "https://api.github.com";
|
|
4308
4421
|
RepoNotFoundError3 = class extends Error {
|
|
4309
4422
|
constructor(repo) {
|
|
@@ -4566,14 +4679,20 @@ var init_github = __esm({
|
|
|
4566
4679
|
});
|
|
4567
4680
|
|
|
4568
4681
|
// src/providers/cnb/cnb-cli.ts
|
|
4569
|
-
import { execSync as
|
|
4682
|
+
import { execSync as execSync2, spawnSync as spawnSync3 } from "child_process";
|
|
4683
|
+
import crossSpawn2 from "cross-spawn";
|
|
4570
4684
|
function cnbExec(args, options) {
|
|
4571
|
-
|
|
4685
|
+
const cnbPath = resolveCliPath("cnb");
|
|
4686
|
+
if (!cnbPath) {
|
|
4687
|
+
log.debug("cnb CLI not found on PATH");
|
|
4688
|
+
return { stdout: "", stderr: "cnb CLI not found on PATH", status: 127 };
|
|
4689
|
+
}
|
|
4690
|
+
log.debug(`cnb exec: ${cnbPath} ${args.join(" ")}`);
|
|
4572
4691
|
if (options?.inheritStdio) {
|
|
4573
|
-
const r2 =
|
|
4692
|
+
const r2 = crossSpawn2.sync(cnbPath, args, { stdio: "inherit", env: { ...process.env }, cwd: options.cwd });
|
|
4574
4693
|
return { stdout: "", stderr: "", status: r2.status ?? 1 };
|
|
4575
4694
|
}
|
|
4576
|
-
const r =
|
|
4695
|
+
const r = crossSpawn2.sync(cnbPath, args, {
|
|
4577
4696
|
env: { ...process.env },
|
|
4578
4697
|
encoding: "utf-8",
|
|
4579
4698
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -4595,12 +4714,7 @@ function assertCnbApiOk(out, action) {
|
|
|
4595
4714
|
}
|
|
4596
4715
|
}
|
|
4597
4716
|
function isCnbInstalled() {
|
|
4598
|
-
|
|
4599
|
-
execSync3("which cnb", { stdio: ["pipe", "pipe", "pipe"] });
|
|
4600
|
-
return true;
|
|
4601
|
-
} catch {
|
|
4602
|
-
return false;
|
|
4603
|
-
}
|
|
4717
|
+
return resolveCliPath("cnb") !== null;
|
|
4604
4718
|
}
|
|
4605
4719
|
async function ensureCnbInstalled() {
|
|
4606
4720
|
if (isCnbInstalled()) {
|
|
@@ -4609,7 +4723,7 @@ async function ensureCnbInstalled() {
|
|
|
4609
4723
|
}
|
|
4610
4724
|
const spin = spinner("Installing cnb CLI (@cnbcool/cnb-cli)...").start();
|
|
4611
4725
|
try {
|
|
4612
|
-
|
|
4726
|
+
execSync2("npm install -g @cnbcool/cnb-cli", { stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
|
|
4613
4727
|
if (!isCnbInstalled()) throw new Error("cnb not found on PATH after install");
|
|
4614
4728
|
spin.succeed("cnb CLI installed");
|
|
4615
4729
|
} catch (e) {
|
|
@@ -4643,7 +4757,7 @@ function cnbWhoami() {
|
|
|
4643
4757
|
}
|
|
4644
4758
|
function cnbLogin() {
|
|
4645
4759
|
log.info("Starting cnb authentication (OAuth2 device flow)...");
|
|
4646
|
-
const r = cnbExec(["login"], { inheritStdio: true });
|
|
4760
|
+
const r = cnbExec(["login", "--host", CNB_HOST], { inheritStdio: true });
|
|
4647
4761
|
if (r.status !== 0) throw new Error("cnb login failed. Please try again.");
|
|
4648
4762
|
}
|
|
4649
4763
|
function ensureCnbAuthenticated() {
|
|
@@ -4690,13 +4804,47 @@ function cnbRepoClone(repo, localPath) {
|
|
|
4690
4804
|
const sanitized = out.replace(/cnb:[^@]+@/g, "cnb:***@").trim();
|
|
4691
4805
|
throw new Error(`git clone failed: ${sanitized}`);
|
|
4692
4806
|
}
|
|
4807
|
+
if (!token) {
|
|
4808
|
+
const cfg = spawnSync3("git", ["config", "--local", "credential.helper", "!cnb git-credential"], {
|
|
4809
|
+
encoding: "utf-8",
|
|
4810
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4811
|
+
cwd: localPath
|
|
4812
|
+
});
|
|
4813
|
+
if (cfg.status !== 0) {
|
|
4814
|
+
log.warn(`Could not persist CNB credential helper: ${(cfg.stderr ?? "").trim()}. Push/pull may prompt for credentials.`);
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
function cnbOrganizationCreateUrl() {
|
|
4819
|
+
return `https://${CNB_HOST}/new/groups`;
|
|
4820
|
+
}
|
|
4821
|
+
function cnbRepoCreateUrl() {
|
|
4822
|
+
return `https://${CNB_HOST}/new/repos`;
|
|
4823
|
+
}
|
|
4824
|
+
function cnbOrganizationExists(org) {
|
|
4825
|
+
const r = cnbExec(["organizations", "get-group", "--group", org]);
|
|
4826
|
+
const out = r.stdout || r.stderr;
|
|
4827
|
+
if (/(?:^|["\s])status["\s:]+\s*200\b/.test(out)) return true;
|
|
4828
|
+
if (/(?:^|["\s])status["\s:]+\s*404\b/.test(out) || /not found|不存在/i.test(out)) return false;
|
|
4829
|
+
throw new Error(`cnb get-group failed for "${org}": ${out || `exit ${r.status}`}`);
|
|
4693
4830
|
}
|
|
4694
4831
|
async function cnbCreateRepo(owner, repo) {
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4832
|
+
try {
|
|
4833
|
+
const r = cnbExec(["repositories", "create-repo", "--slug", owner, "--name", repo]);
|
|
4834
|
+
if (r.status !== 0) {
|
|
4835
|
+
throw new Error(`cnb create-repo failed: ${r.stderr || r.stdout}`);
|
|
4836
|
+
}
|
|
4837
|
+
assertCnbApiOk(r.stdout, "create-repo");
|
|
4838
|
+
} catch (e) {
|
|
4839
|
+
const msg = e.message;
|
|
4840
|
+
if (/HTTP 404|not found|不存在/i.test(msg)) {
|
|
4841
|
+
throw new OrganizationNotFoundError(owner, cnbOrganizationCreateUrl());
|
|
4842
|
+
}
|
|
4843
|
+
if (/HTTP 403|scope|permission|forbidden|权限/i.test(msg)) {
|
|
4844
|
+
throw new RepoCreatePermissionError(`${owner}/${repo}`, cnbRepoCreateUrl());
|
|
4845
|
+
}
|
|
4846
|
+
throw e;
|
|
4698
4847
|
}
|
|
4699
|
-
assertCnbApiOk(r.stdout, "create-repo");
|
|
4700
4848
|
}
|
|
4701
4849
|
function cnbPullCreate(opts) {
|
|
4702
4850
|
const args = [
|
|
@@ -4729,6 +4877,8 @@ var init_cnb_cli = __esm({
|
|
|
4729
4877
|
"src/providers/cnb/cnb-cli.ts"() {
|
|
4730
4878
|
"use strict";
|
|
4731
4879
|
init_logger();
|
|
4880
|
+
init_cli_path();
|
|
4881
|
+
init_types2();
|
|
4732
4882
|
CNB_HOST = process.env.TEAMAI_CNB_HOST?.trim() || "cnb.cool";
|
|
4733
4883
|
CnbRepoNotFoundError = class extends Error {
|
|
4734
4884
|
constructor(repo) {
|
|
@@ -4778,6 +4928,12 @@ var init_cnb = __esm({
|
|
|
4778
4928
|
async createRepo(owner, repo) {
|
|
4779
4929
|
await cnbCreateRepo(owner, repo);
|
|
4780
4930
|
}
|
|
4931
|
+
organizationExists(org) {
|
|
4932
|
+
return cnbOrganizationExists(org);
|
|
4933
|
+
}
|
|
4934
|
+
getOrganizationCreateUrl() {
|
|
4935
|
+
return cnbOrganizationCreateUrl();
|
|
4936
|
+
}
|
|
4781
4937
|
async createPullRequest(opts) {
|
|
4782
4938
|
return cnbPullCreate({
|
|
4783
4939
|
repo: opts.repo,
|
|
@@ -6317,6 +6473,8 @@ __export(providers_exports, {
|
|
|
6317
6473
|
GitCodeProvider: () => GitCodeProvider,
|
|
6318
6474
|
GitHubProvider: () => GitHubProvider,
|
|
6319
6475
|
GitLabProvider: () => GitLabProvider,
|
|
6476
|
+
OrganizationNotFoundError: () => OrganizationNotFoundError,
|
|
6477
|
+
RepoCreatePermissionError: () => RepoCreatePermissionError,
|
|
6320
6478
|
RepoNotFoundError: () => RepoNotFoundError,
|
|
6321
6479
|
TGitProvider: () => TGitProvider,
|
|
6322
6480
|
detectProvider: () => detectProvider,
|
|
@@ -6391,7 +6549,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
|
6391
6549
|
log.debug(`Skipping built-in skill deployment for ${tool}: tool not installed`);
|
|
6392
6550
|
continue;
|
|
6393
6551
|
}
|
|
6394
|
-
if (localConfig &&
|
|
6552
|
+
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
6395
6553
|
for (const skillName of skillNames) {
|
|
6396
6554
|
const srcDir = path18.join(builtinDir, skillName);
|
|
6397
6555
|
const destDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, skillName, srcDir);
|
|
@@ -6737,7 +6895,7 @@ var init_session_id = __esm({
|
|
|
6737
6895
|
|
|
6738
6896
|
// src/pid-monitor.ts
|
|
6739
6897
|
import fs9 from "fs";
|
|
6740
|
-
import { execSync as
|
|
6898
|
+
import { execSync as execSync3 } from "child_process";
|
|
6741
6899
|
function getParentPid(pid) {
|
|
6742
6900
|
try {
|
|
6743
6901
|
const stat8 = fs9.readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
@@ -6749,7 +6907,7 @@ function getParentPid(pid) {
|
|
|
6749
6907
|
} catch {
|
|
6750
6908
|
}
|
|
6751
6909
|
try {
|
|
6752
|
-
const out =
|
|
6910
|
+
const out = execSync3(`ps -o ppid= -p ${pid}`, {
|
|
6753
6911
|
encoding: "utf-8",
|
|
6754
6912
|
timeout: 2e3,
|
|
6755
6913
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6766,7 +6924,7 @@ function getProcessComm(pid) {
|
|
|
6766
6924
|
} catch {
|
|
6767
6925
|
}
|
|
6768
6926
|
try {
|
|
6769
|
-
const out =
|
|
6927
|
+
const out = execSync3(`ps -o comm= -p ${pid}`, {
|
|
6770
6928
|
encoding: "utf-8",
|
|
6771
6929
|
timeout: 2e3,
|
|
6772
6930
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7875,7 +8033,7 @@ var init_agent_version = __esm({
|
|
|
7875
8033
|
// src/machine-id.ts
|
|
7876
8034
|
import crypto2 from "crypto";
|
|
7877
8035
|
import fs11 from "fs";
|
|
7878
|
-
import { execFileSync } from "child_process";
|
|
8036
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
7879
8037
|
import os6 from "os";
|
|
7880
8038
|
function getMachineId() {
|
|
7881
8039
|
if (cachedMachineId !== null) return cachedMachineId;
|
|
@@ -7901,7 +8059,7 @@ function detectMachineId(platform = process.platform) {
|
|
|
7901
8059
|
return id || os6.hostname() || "";
|
|
7902
8060
|
}
|
|
7903
8061
|
function readDarwinMachineId() {
|
|
7904
|
-
const out =
|
|
8062
|
+
const out = execFileSync2("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
7905
8063
|
encoding: "utf-8",
|
|
7906
8064
|
timeout: 3e3
|
|
7907
8065
|
});
|
|
@@ -7909,7 +8067,7 @@ function readDarwinMachineId() {
|
|
|
7909
8067
|
return match ? match[1].trim() : "";
|
|
7910
8068
|
}
|
|
7911
8069
|
function readWindowsMachineId() {
|
|
7912
|
-
const out =
|
|
8070
|
+
const out = execFileSync2(
|
|
7913
8071
|
"reg",
|
|
7914
8072
|
["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
|
|
7915
8073
|
{ encoding: "utf-8", timeout: 3e3 }
|
|
@@ -11762,9 +11920,16 @@ async function scanTeamRepoNamespaces(repoPath) {
|
|
|
11762
11920
|
for (const dir of topDirs) {
|
|
11763
11921
|
const dirPath = path29.join(teamSkillsDir, dir);
|
|
11764
11922
|
const hasSkillMd = await pathExists(path29.join(dirPath, "SKILL.md"));
|
|
11765
|
-
if (
|
|
11766
|
-
|
|
11923
|
+
if (hasSkillMd) continue;
|
|
11924
|
+
const subDirs = await listDirs(dirPath);
|
|
11925
|
+
let holdsSkill = false;
|
|
11926
|
+
for (const subDir of subDirs) {
|
|
11927
|
+
if (await pathExists(path29.join(dirPath, subDir, "SKILL.md"))) {
|
|
11928
|
+
holdsSkill = true;
|
|
11929
|
+
break;
|
|
11930
|
+
}
|
|
11767
11931
|
}
|
|
11932
|
+
if (holdsSkill) namespaces.push(dir);
|
|
11768
11933
|
}
|
|
11769
11934
|
return namespaces;
|
|
11770
11935
|
}
|
|
@@ -13179,7 +13344,7 @@ async function deployBuiltinAgents(teamConfig, localConfig, options) {
|
|
|
13179
13344
|
log.debug(`Skipping built-in agent deployment for ${tool}: tool not installed`);
|
|
13180
13345
|
continue;
|
|
13181
13346
|
}
|
|
13182
|
-
if (localConfig &&
|
|
13347
|
+
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
13183
13348
|
if (!ALL_SUPPORTED_TOOLS.includes(tool)) {
|
|
13184
13349
|
log.warn(
|
|
13185
13350
|
`Skipping built-in agent deployment for ${tool}: unsupported agent format; disable this target or add a native renderer`
|
|
@@ -15697,6 +15862,15 @@ async function initSelfRepo(options) {
|
|
|
15697
15862
|
log.info(" 4. Push your business repo (e.g. `git push -u origin HEAD`) so teammates get the .teamai/ knowledge and are auto-initialized on clone.");
|
|
15698
15863
|
closePrompt();
|
|
15699
15864
|
}
|
|
15865
|
+
function guideWebCreation(kind, name, url) {
|
|
15866
|
+
log.info(`${kind} "${name}" can't be created from the CLI (insufficient token permission).`);
|
|
15867
|
+
if (url) {
|
|
15868
|
+
log.info(`Create it here, then re-run this command:`);
|
|
15869
|
+
log.info(` ${url}`);
|
|
15870
|
+
} else {
|
|
15871
|
+
log.info(`Create the ${kind} on the platform, then re-run this command.`);
|
|
15872
|
+
}
|
|
15873
|
+
}
|
|
15700
15874
|
async function init(options) {
|
|
15701
15875
|
if (options.http) {
|
|
15702
15876
|
return initHttp(options.http, options);
|
|
@@ -15848,6 +16022,23 @@ async function init(options) {
|
|
|
15848
16022
|
} catch (e) {
|
|
15849
16023
|
if (e instanceof RepoNotFoundError) {
|
|
15850
16024
|
cloneSpin.info(`Repo ${repoInfo.owner}/${repoInfo.repo} does not exist`);
|
|
16025
|
+
if (typeof provider.organizationExists === "function") {
|
|
16026
|
+
let orgMissing = false;
|
|
16027
|
+
try {
|
|
16028
|
+
orgMissing = !provider.organizationExists(repoInfo.owner);
|
|
16029
|
+
} catch (checkErr) {
|
|
16030
|
+
log.debug(`Organization check failed: ${checkErr.message}`);
|
|
16031
|
+
}
|
|
16032
|
+
if (orgMissing) {
|
|
16033
|
+
cloneSpin.info(`Organization "${repoInfo.owner}" does not exist`);
|
|
16034
|
+
guideWebCreation(
|
|
16035
|
+
"organization",
|
|
16036
|
+
repoInfo.owner,
|
|
16037
|
+
provider.getOrganizationCreateUrl?.() ?? null
|
|
16038
|
+
);
|
|
16039
|
+
process.exit(1);
|
|
16040
|
+
}
|
|
16041
|
+
}
|
|
15851
16042
|
const confirmed = await askConfirmation(
|
|
15852
16043
|
`Create repo ${repoInfo.owner}/${repoInfo.repo}? [Y/n] `,
|
|
15853
16044
|
true
|
|
@@ -15861,6 +16052,16 @@ async function init(options) {
|
|
|
15861
16052
|
await provider.createRepo(repoInfo.owner, repoInfo.repo);
|
|
15862
16053
|
createSpin.succeed(`Repo ${repoInfo.owner}/${repoInfo.repo} created`);
|
|
15863
16054
|
} catch (ce) {
|
|
16055
|
+
if (ce instanceof OrganizationNotFoundError) {
|
|
16056
|
+
createSpin.fail(`Organization "${ce.org}" does not exist`);
|
|
16057
|
+
guideWebCreation("organization", ce.org, ce.createUrl ?? null);
|
|
16058
|
+
process.exit(1);
|
|
16059
|
+
}
|
|
16060
|
+
if (ce instanceof RepoCreatePermissionError) {
|
|
16061
|
+
createSpin.fail(`No permission to create repo "${ce.repo}" from the CLI`);
|
|
16062
|
+
guideWebCreation("repo", ce.repo, ce.createUrl ?? null);
|
|
16063
|
+
process.exit(1);
|
|
16064
|
+
}
|
|
15864
16065
|
const msg = ce.message;
|
|
15865
16066
|
if (/already been taken|already exists/i.test(msg)) {
|
|
15866
16067
|
createSpin.info(`Repo ${repoInfo.owner}/${repoInfo.repo} already exists, retrying clone`);
|
|
@@ -16241,6 +16442,9 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
|
16241
16442
|
items: toPendingItems(items)
|
|
16242
16443
|
});
|
|
16243
16444
|
await checkoutMaster(localConfig.repo.localPath);
|
|
16445
|
+
for (const rel of pushedFiles) {
|
|
16446
|
+
await pruneEmptyDirs(path45.resolve(localConfig.repo.localPath, rel));
|
|
16447
|
+
}
|
|
16244
16448
|
return true;
|
|
16245
16449
|
} catch (e) {
|
|
16246
16450
|
pushSpin.fail(`Push failed: ${e.message}`);
|
|
@@ -22021,7 +22225,7 @@ async function refreshTeamRepo(localConfig) {
|
|
|
22021
22225
|
if (!apiKey) {
|
|
22022
22226
|
throw new Error("No API key configured. Re-run `teamai init --http <url> --token <key>` or set TEAMAI_API_TOKEN.");
|
|
22023
22227
|
}
|
|
22024
|
-
return { label: "HTTP (report/sync delivery)", version: null, reportingOnly: true };
|
|
22228
|
+
return { label: "HTTP (report/sync delivery)", version: null, reportingOnly: true, submodulesFailed: false, submodulesChanged: false };
|
|
22025
22229
|
}
|
|
22026
22230
|
if (localConfig.repo.kind === "self") {
|
|
22027
22231
|
try {
|
|
@@ -22035,7 +22239,7 @@ async function refreshTeamRepo(localConfig) {
|
|
|
22035
22239
|
} catch {
|
|
22036
22240
|
version3 = null;
|
|
22037
22241
|
}
|
|
22038
|
-
return { label: "single-repo (knowledge on main)", version: version3, reportingOnly: false };
|
|
22242
|
+
return { label: "single-repo (knowledge on main)", version: version3, reportingOnly: false, submodulesFailed: false, submodulesChanged: false };
|
|
22039
22243
|
}
|
|
22040
22244
|
const result = await pullRepo(localConfig.repo.localPath);
|
|
22041
22245
|
try {
|
|
@@ -22050,7 +22254,35 @@ async function refreshTeamRepo(localConfig) {
|
|
|
22050
22254
|
log.debug("Rev check failed, proceeding with full sync");
|
|
22051
22255
|
version2 = null;
|
|
22052
22256
|
}
|
|
22053
|
-
|
|
22257
|
+
let submodulesFailed = false;
|
|
22258
|
+
let submodulesChanged = false;
|
|
22259
|
+
try {
|
|
22260
|
+
const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
22261
|
+
if (teamConfig?.submodules) {
|
|
22262
|
+
const git = createGit2(localConfig.repo.localPath);
|
|
22263
|
+
let before = null;
|
|
22264
|
+
try {
|
|
22265
|
+
before = await git.subModule(["status"]);
|
|
22266
|
+
} catch {
|
|
22267
|
+
before = null;
|
|
22268
|
+
}
|
|
22269
|
+
await git.submoduleUpdate(["--init"]);
|
|
22270
|
+
let after = null;
|
|
22271
|
+
try {
|
|
22272
|
+
after = await git.subModule(["status"]);
|
|
22273
|
+
} catch {
|
|
22274
|
+
after = null;
|
|
22275
|
+
}
|
|
22276
|
+
submodulesChanged = before === null || before !== after;
|
|
22277
|
+
log.debug(
|
|
22278
|
+
submodulesChanged ? "Submodules updated (tree changed \u2014 full sync this pull)" : "Submodules updated (no change)"
|
|
22279
|
+
);
|
|
22280
|
+
}
|
|
22281
|
+
} catch (e) {
|
|
22282
|
+
submodulesFailed = true;
|
|
22283
|
+
log.warn(`Submodule update failed for ${localConfig.repo.localPath}: ${e.message}`);
|
|
22284
|
+
}
|
|
22285
|
+
return { label: result, version: version2, reportingOnly: false, submodulesFailed, submodulesChanged };
|
|
22054
22286
|
}
|
|
22055
22287
|
async function usageReportDisabled(repoPath) {
|
|
22056
22288
|
return (await loadTeamConfig(repoPath))?.usageReport === false;
|
|
@@ -22169,7 +22401,7 @@ async function skillSafeToRemove(deployedDir, source) {
|
|
|
22169
22401
|
async function cleanupInactiveNamespaceSkills(teamConfig, localConfig, retainedSkillNames, inactiveSkillNames, inactiveSkillSources) {
|
|
22170
22402
|
const baseDir = resolveBaseDir(localConfig);
|
|
22171
22403
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
22172
|
-
if (
|
|
22404
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22173
22405
|
if (!toolPath.skills) continue;
|
|
22174
22406
|
if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
|
|
22175
22407
|
if (!await pathExists(path65.join(baseDir, toolPath.skills))) continue;
|
|
@@ -22230,7 +22462,7 @@ async function getInstalledResourceTargets(teamConfig, localConfig) {
|
|
|
22230
22462
|
const baseDir = resolveBaseDir(localConfig);
|
|
22231
22463
|
const targets = [];
|
|
22232
22464
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
22233
|
-
if (
|
|
22465
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22234
22466
|
const resourcePaths = [toolPath.skills, toolPath.rules, toolPath.agents].filter((resourcePath) => !!resourcePath);
|
|
22235
22467
|
for (const resourcePath of resourcePaths) {
|
|
22236
22468
|
if (await ResourceHandler.isToolInstalled(resourcePath, baseDir)) {
|
|
@@ -22253,17 +22485,21 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22253
22485
|
const pullSpin = spinner(`[${scopeLabel}] Pulling team repo...`).start();
|
|
22254
22486
|
let currentRev = null;
|
|
22255
22487
|
let reportingOnly = false;
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22488
|
+
let submodulesFailed = false;
|
|
22489
|
+
let submodulesChanged = false;
|
|
22490
|
+
try {
|
|
22491
|
+
const refresh = await refreshTeamRepo(localConfig);
|
|
22492
|
+
currentRev = refresh.version;
|
|
22493
|
+
reportingOnly = refresh.reportingOnly;
|
|
22494
|
+
submodulesFailed = refresh.submodulesFailed;
|
|
22495
|
+
submodulesChanged = refresh.submodulesChanged;
|
|
22496
|
+
pullSpin.succeed(`[${scopeLabel}] Team repo: ${refresh.label}`);
|
|
22261
22497
|
} catch (e) {
|
|
22262
22498
|
pullSpin.fail(`[${scopeLabel}] Pull failed: ${e.message}`);
|
|
22263
22499
|
return;
|
|
22264
22500
|
}
|
|
22265
22501
|
let currentTargets = null;
|
|
22266
|
-
if (!options.force && !options.dryRun) {
|
|
22502
|
+
if (!options.force && !options.dryRun && !submodulesChanged) {
|
|
22267
22503
|
try {
|
|
22268
22504
|
const state = await loadStateForScope(localConfig);
|
|
22269
22505
|
if (currentRev && state[revisionField] && state[revisionField] === currentRev) {
|
|
@@ -22444,7 +22680,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22444
22680
|
const dir = toolPath[toolPathField];
|
|
22445
22681
|
if (!dir) continue;
|
|
22446
22682
|
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
22447
|
-
if (
|
|
22683
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22448
22684
|
const extensions = type === "rules" ? [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])] : [ext];
|
|
22449
22685
|
for (const name of tombstones) {
|
|
22450
22686
|
for (const extension of extensions) {
|
|
@@ -22474,7 +22710,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22474
22710
|
if (!options.dryRun && desiredSkillNames && knownRepoSkillNames) {
|
|
22475
22711
|
const baseDir = resolveBaseDir(localConfig);
|
|
22476
22712
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
22477
|
-
if (
|
|
22713
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22478
22714
|
if (!toolPath.skills) continue;
|
|
22479
22715
|
if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
|
|
22480
22716
|
const skillsDir = path65.join(baseDir, toolPath.skills);
|
|
@@ -22593,7 +22829,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22593
22829
|
if (compiled) {
|
|
22594
22830
|
const baseDir = resolveBaseDir(localConfig);
|
|
22595
22831
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
22596
|
-
if (
|
|
22832
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22597
22833
|
if (!toolPath.claudemd) continue;
|
|
22598
22834
|
if (toolPath.rules && !await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
22599
22835
|
const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
|
|
@@ -22623,7 +22859,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22623
22859
|
if (compiled) {
|
|
22624
22860
|
const baseDir = resolveBaseDir(localConfig);
|
|
22625
22861
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
22626
|
-
if (
|
|
22862
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22627
22863
|
if (!toolPath.claudemd) continue;
|
|
22628
22864
|
if (toolPath.rules && !await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
22629
22865
|
const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
|
|
@@ -22685,13 +22921,15 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22685
22921
|
if (revisionField === "lastPullRev") {
|
|
22686
22922
|
state.lastPull = (/* @__PURE__ */ new Date()).toISOString();
|
|
22687
22923
|
}
|
|
22688
|
-
if (
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22924
|
+
if (!submodulesFailed) {
|
|
22925
|
+
if (currentRev !== null) {
|
|
22926
|
+
state[revisionField] = currentRev;
|
|
22927
|
+
} else {
|
|
22928
|
+
try {
|
|
22929
|
+
state[revisionField] = await getHeadRev(localConfig.repo.localPath);
|
|
22930
|
+
} catch {
|
|
22931
|
+
state[revisionField] = null;
|
|
22932
|
+
}
|
|
22695
22933
|
}
|
|
22696
22934
|
}
|
|
22697
22935
|
state[targetsField] = currentTargets ?? await getInstalledResourceTargets(freshConfig, localConfig);
|
|
@@ -22800,7 +23038,7 @@ async function injectRecallBlockIntoTools(config, localConfig, scopeLabel) {
|
|
|
22800
23038
|
const recallBlock = compileRecallRulesBlock();
|
|
22801
23039
|
let injected = 0;
|
|
22802
23040
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(config, localConfig))) {
|
|
22803
|
-
if (
|
|
23041
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22804
23042
|
if (!toolPath.claudemd || !toolPath.agents) continue;
|
|
22805
23043
|
if (!await ResourceHandler.isToolInstalled(toolPath.agents, baseDir)) continue;
|
|
22806
23044
|
const claudeMdPath = path65.join(baseDir, toolPath.claudemd);
|
|
@@ -27039,67 +27277,7 @@ __export(ai_client_exports, {
|
|
|
27039
27277
|
pickWindowsCommand: () => pickWindowsCommand,
|
|
27040
27278
|
resolveCliPath: () => resolveCliPath
|
|
27041
27279
|
});
|
|
27042
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
27043
27280
|
import spawn2 from "cross-spawn";
|
|
27044
|
-
import { existsSync } from "fs";
|
|
27045
|
-
function pickWindowsCommand(whereOutput) {
|
|
27046
|
-
const lines = whereOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
27047
|
-
for (const ext of WIN_EXEC_EXTENSIONS) {
|
|
27048
|
-
const hit = lines.find((line) => line.toLowerCase().endsWith(ext));
|
|
27049
|
-
if (hit !== void 0) return hit;
|
|
27050
|
-
}
|
|
27051
|
-
return null;
|
|
27052
|
-
}
|
|
27053
|
-
function whereOnWindows(cmd) {
|
|
27054
|
-
try {
|
|
27055
|
-
const out = execFileSync2("where", [cmd], {
|
|
27056
|
-
encoding: "utf8",
|
|
27057
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
27058
|
-
shell: false,
|
|
27059
|
-
timeout: CLI_DETECT_TIMEOUT_MS
|
|
27060
|
-
});
|
|
27061
|
-
const p = pickWindowsCommand(out);
|
|
27062
|
-
return p !== null && existsSync(p) ? p : null;
|
|
27063
|
-
} catch {
|
|
27064
|
-
return null;
|
|
27065
|
-
}
|
|
27066
|
-
}
|
|
27067
|
-
function whichOnPosix(cmd) {
|
|
27068
|
-
try {
|
|
27069
|
-
const p = execFileSync2("bash", ["-lc", `command -v ${cmd}`], {
|
|
27070
|
-
encoding: "utf8",
|
|
27071
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
27072
|
-
shell: false,
|
|
27073
|
-
timeout: CLI_DETECT_TIMEOUT_MS
|
|
27074
|
-
}).trim();
|
|
27075
|
-
if (p && existsSync(p)) return p;
|
|
27076
|
-
} catch {
|
|
27077
|
-
}
|
|
27078
|
-
try {
|
|
27079
|
-
const p = execFileSync2("zsh", ["-lc", `command -v ${cmd}`], {
|
|
27080
|
-
encoding: "utf8",
|
|
27081
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
27082
|
-
shell: false,
|
|
27083
|
-
timeout: CLI_DETECT_TIMEOUT_MS
|
|
27084
|
-
}).trim();
|
|
27085
|
-
if (p && existsSync(p)) return p;
|
|
27086
|
-
} catch {
|
|
27087
|
-
}
|
|
27088
|
-
try {
|
|
27089
|
-
const p = execFileSync2("which", [cmd], {
|
|
27090
|
-
encoding: "utf8",
|
|
27091
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
27092
|
-
shell: false,
|
|
27093
|
-
timeout: CLI_DETECT_TIMEOUT_MS
|
|
27094
|
-
}).trim();
|
|
27095
|
-
if (p && existsSync(p)) return p;
|
|
27096
|
-
} catch {
|
|
27097
|
-
}
|
|
27098
|
-
return null;
|
|
27099
|
-
}
|
|
27100
|
-
function resolveCliPath(cmd, platform = process.platform) {
|
|
27101
|
-
return platform === "win32" ? whereOnWindows(cmd) : whichOnPosix(cmd);
|
|
27102
|
-
}
|
|
27103
27281
|
function detectClaudeCli() {
|
|
27104
27282
|
for (const cmd of ALLOWED_CLI_CANDIDATES) {
|
|
27105
27283
|
const absPath = resolveCliPath(cmd);
|
|
@@ -27214,11 +27392,13 @@ async function runWithConcurrency(tasks, concurrency) {
|
|
|
27214
27392
|
await Promise.all(promises);
|
|
27215
27393
|
return results;
|
|
27216
27394
|
}
|
|
27217
|
-
var ALLOWED_CLI_CANDIDATES,
|
|
27395
|
+
var ALLOWED_CLI_CANDIDATES, DEFAULT_TIMEOUT_MS, DEFAULT_CONCURRENCY, _cliInfo;
|
|
27218
27396
|
var init_ai_client = __esm({
|
|
27219
27397
|
"src/utils/ai-client.ts"() {
|
|
27220
27398
|
"use strict";
|
|
27221
27399
|
init_logger();
|
|
27400
|
+
init_cli_path();
|
|
27401
|
+
init_cli_path();
|
|
27222
27402
|
ALLOWED_CLI_CANDIDATES = [
|
|
27223
27403
|
"claude",
|
|
27224
27404
|
"claude-internal",
|
|
@@ -27230,8 +27410,6 @@ var init_ai_client = __esm({
|
|
|
27230
27410
|
"workbuddy",
|
|
27231
27411
|
"openclaw"
|
|
27232
27412
|
];
|
|
27233
|
-
CLI_DETECT_TIMEOUT_MS = 5e3;
|
|
27234
|
-
WIN_EXEC_EXTENSIONS = [".exe", ".cmd", ".bat"];
|
|
27235
27413
|
DEFAULT_TIMEOUT_MS = 6e5;
|
|
27236
27414
|
DEFAULT_CONCURRENCY = 3;
|
|
27237
27415
|
}
|
|
@@ -33164,7 +33342,7 @@ var init_import_iwiki = __esm({
|
|
|
33164
33342
|
});
|
|
33165
33343
|
|
|
33166
33344
|
// src/providers/github/mr-fetch.ts
|
|
33167
|
-
import { execSync as
|
|
33345
|
+
import { execSync as execSync4 } from "child_process";
|
|
33168
33346
|
import https2 from "https";
|
|
33169
33347
|
function parseGitHubPRUrl(url) {
|
|
33170
33348
|
const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
@@ -33251,12 +33429,12 @@ async function fetchGitHubPR(url) {
|
|
|
33251
33429
|
const repoArg = `${owner}/${repo}`;
|
|
33252
33430
|
log.debug(`fetchGitHubPR: ${repoArg}#${number}`);
|
|
33253
33431
|
try {
|
|
33254
|
-
const viewOutput =
|
|
33432
|
+
const viewOutput = execSync4(
|
|
33255
33433
|
`gh pr view ${number} --repo ${repoArg} --json title,body,author,mergedAt,commits`,
|
|
33256
33434
|
{ maxBuffer: 10 * 1024 * 1024, encoding: "utf8" }
|
|
33257
33435
|
);
|
|
33258
33436
|
const prView = JSON.parse(viewOutput);
|
|
33259
|
-
const rawDiff =
|
|
33437
|
+
const rawDiff = execSync4(
|
|
33260
33438
|
`gh pr diff ${number} --repo ${repoArg}`,
|
|
33261
33439
|
{ maxBuffer: 50 * 1024 * 1024, encoding: "utf8" }
|
|
33262
33440
|
);
|
|
@@ -33670,7 +33848,7 @@ var init_import_mr = __esm({
|
|
|
33670
33848
|
});
|
|
33671
33849
|
|
|
33672
33850
|
// src/codebase.ts
|
|
33673
|
-
import { execSync as
|
|
33851
|
+
import { execSync as execSync5 } from "child_process";
|
|
33674
33852
|
import fs36 from "fs";
|
|
33675
33853
|
import path103 from "path";
|
|
33676
33854
|
import matter12 from "gray-matter";
|
|
@@ -33686,7 +33864,7 @@ ${commitMessages}`);
|
|
|
33686
33864
|
log.debug(`gatherRepoContext: git log \u5931\u8D25 \u2014 ${String(err)}`);
|
|
33687
33865
|
}
|
|
33688
33866
|
try {
|
|
33689
|
-
const rawTree =
|
|
33867
|
+
const rawTree = execSync5(
|
|
33690
33868
|
'find . -maxdepth 4 -not -path "*/.git/*" -not -path "*/node_modules/*" -not -path "*/__pycache__/*" -not -path "*/dist/*" -not -path "*/.claude/worktrees/*" -not -name "*.js.map"',
|
|
33691
33869
|
{ cwd: repoPath, encoding: "utf-8" }
|
|
33692
33870
|
);
|
|
@@ -36835,9 +37013,12 @@ var init_templates = __esm({
|
|
|
36835
37013
|
// src/enrich-with-ai.ts
|
|
36836
37014
|
var enrich_with_ai_exports = {};
|
|
36837
37015
|
__export(enrich_with_ai_exports, {
|
|
37016
|
+
buildFallbackManifest: () => buildFallbackManifest,
|
|
37017
|
+
describeEvidenceManifest: () => describeEvidenceManifest,
|
|
36838
37018
|
edgeProvenanceRank: () => edgeProvenanceRank,
|
|
36839
37019
|
edgeReason: () => edgeReason,
|
|
36840
37020
|
enrichWithAI: () => enrichWithAI,
|
|
37021
|
+
groupFactsByModule: () => groupFactsByModule,
|
|
36841
37022
|
parseEdgeProvenance: () => parseEdgeProvenance,
|
|
36842
37023
|
writeManifest: () => writeManifest
|
|
36843
37024
|
});
|
|
@@ -36907,6 +37088,62 @@ function resolveImportToModule(importerFile, importPath) {
|
|
|
36907
37088
|
if (first.startsWith("@")) return void 0;
|
|
36908
37089
|
return first;
|
|
36909
37090
|
}
|
|
37091
|
+
function groupFactsByModule(facts) {
|
|
37092
|
+
const modules = /* @__PURE__ */ new Map();
|
|
37093
|
+
for (const fact of facts) {
|
|
37094
|
+
if (fact.kind === "relation") continue;
|
|
37095
|
+
const mod = fact.file.split("/")[0] || "_root";
|
|
37096
|
+
const existing = modules.get(mod) ?? [];
|
|
37097
|
+
existing.push(fact);
|
|
37098
|
+
modules.set(mod, existing);
|
|
37099
|
+
}
|
|
37100
|
+
return modules;
|
|
37101
|
+
}
|
|
37102
|
+
function isSafeSlug(name) {
|
|
37103
|
+
try {
|
|
37104
|
+
assertSafeResourceName(name);
|
|
37105
|
+
return true;
|
|
37106
|
+
} catch {
|
|
37107
|
+
return false;
|
|
37108
|
+
}
|
|
37109
|
+
}
|
|
37110
|
+
function buildFallbackManifest(ctx) {
|
|
37111
|
+
const moduleNames = [...ctx.modules.keys()].filter(isSafeSlug);
|
|
37112
|
+
const componentNames = [...new Set(
|
|
37113
|
+
ctx.facts.filter((f) => f.kind === "component").map((f) => f.name)
|
|
37114
|
+
)].filter(isSafeSlug);
|
|
37115
|
+
const slugs = moduleNames.length > 0 ? moduleNames : componentNames;
|
|
37116
|
+
if (slugs.length === 0) return null;
|
|
37117
|
+
const components = slugs.map((name) => {
|
|
37118
|
+
const moduleFacts = ctx.modules.get(name) ?? ctx.facts.filter((f) => f.name === name);
|
|
37119
|
+
const entrypoints = moduleFacts.filter((f) => f.kind === "component").filter((f) => /handler|route|controller|endpoint|main|server|app/i.test(f.name)).slice(0, 5).map((f) => `${f.name} (${f.file}:${f.lineStart})`);
|
|
37120
|
+
return {
|
|
37121
|
+
slug: name,
|
|
37122
|
+
docPath: `evidence/code/${ctx.project}/${name}.md`,
|
|
37123
|
+
title: name,
|
|
37124
|
+
category: "component",
|
|
37125
|
+
confidence: "EXTRACTED",
|
|
37126
|
+
...entrypoints.length > 0 ? { entrypoints } : {}
|
|
37127
|
+
};
|
|
37128
|
+
});
|
|
37129
|
+
return {
|
|
37130
|
+
schemaVersion: "team-wiki.codebase-output-manifest.v2",
|
|
37131
|
+
project: ctx.project,
|
|
37132
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37133
|
+
components,
|
|
37134
|
+
edges: []
|
|
37135
|
+
};
|
|
37136
|
+
}
|
|
37137
|
+
function describeEvidenceManifest(source, componentCount) {
|
|
37138
|
+
if (source === "fallback") {
|
|
37139
|
+
const noun = componentCount === 1 ? "component" : "components";
|
|
37140
|
+
return `Wrote fallback _manifest.json (${componentCount} ${noun}, no AI enrich)`;
|
|
37141
|
+
}
|
|
37142
|
+
if (source === "none") {
|
|
37143
|
+
return "AI enrich produced no manifest; deep-enrich will have no components";
|
|
37144
|
+
}
|
|
37145
|
+
return void 0;
|
|
37146
|
+
}
|
|
36910
37147
|
async function enrichWithAI(ctx) {
|
|
36911
37148
|
const moduleEntries = [...ctx.modules.entries()].filter(([, facts]) => facts.length >= 5);
|
|
36912
37149
|
if (moduleEntries.length === 0) {
|
|
@@ -37045,6 +37282,7 @@ var init_enrich_with_ai = __esm({
|
|
|
37045
37282
|
"use strict";
|
|
37046
37283
|
init_ai_client();
|
|
37047
37284
|
init_logger();
|
|
37285
|
+
init_path_safety();
|
|
37048
37286
|
}
|
|
37049
37287
|
});
|
|
37050
37288
|
|
|
@@ -37566,39 +37804,53 @@ async function extractCodebase(opts) {
|
|
|
37566
37804
|
const repoGraph = mergeGraphs(graph, overlay);
|
|
37567
37805
|
await saveGraphIndex(wikiRoot, repoGraph);
|
|
37568
37806
|
let aiDomains = [];
|
|
37807
|
+
let manifestSource = "none";
|
|
37808
|
+
let manifestComponentCount = 0;
|
|
37809
|
+
const {
|
|
37810
|
+
enrichWithAI: enrichWithAI2,
|
|
37811
|
+
writeManifest: writeManifest2,
|
|
37812
|
+
buildFallbackManifest: buildFallbackManifest2,
|
|
37813
|
+
groupFactsByModule: groupFactsByModule2,
|
|
37814
|
+
describeEvidenceManifest: describeEvidenceManifest2
|
|
37815
|
+
} = await Promise.resolve().then(() => (init_enrich_with_ai(), enrich_with_ai_exports));
|
|
37816
|
+
const modules = groupFactsByModule2(facts);
|
|
37569
37817
|
if (opts.skipEnrich) {
|
|
37570
37818
|
if (!opts.json) console.log(chalk3.dim(" [AI enrich: skipped (--skip-enrich)]"));
|
|
37571
|
-
} else
|
|
37572
|
-
|
|
37573
|
-
|
|
37574
|
-
|
|
37575
|
-
|
|
37576
|
-
|
|
37577
|
-
|
|
37578
|
-
|
|
37579
|
-
|
|
37580
|
-
|
|
37581
|
-
|
|
37582
|
-
|
|
37583
|
-
|
|
37584
|
-
|
|
37585
|
-
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
|
|
37589
|
-
|
|
37590
|
-
}
|
|
37591
|
-
|
|
37819
|
+
} else {
|
|
37820
|
+
try {
|
|
37821
|
+
const enrichResult = await enrichWithAI2({ project, facts, interfaceInventory, modules });
|
|
37822
|
+
if (enrichResult) {
|
|
37823
|
+
await writeManifest2(enrichResult.manifest, evidenceDir);
|
|
37824
|
+
manifestSource = "ai";
|
|
37825
|
+
manifestComponentCount = enrichResult.manifest.components.length;
|
|
37826
|
+
aiDomains = enrichResult.domains;
|
|
37827
|
+
const domainMeta = {
|
|
37828
|
+
domain: enrichResult.repoDomain || (enrichResult.domains[0]?.name ?? ""),
|
|
37829
|
+
description: enrichResult.repoDescription || "",
|
|
37830
|
+
keywords: enrichResult.repoKeywords || [],
|
|
37831
|
+
components: enrichResult.domains[0]?.components ?? []
|
|
37832
|
+
};
|
|
37833
|
+
await writeFile10(path112.join(evidenceDir, "_domains.json"), JSON.stringify(domainMeta, null, 2), "utf-8");
|
|
37834
|
+
if (!opts.json) {
|
|
37835
|
+
const domainLabel = domainMeta.domain || "uncategorized";
|
|
37836
|
+
console.log(` AI enrich: ${enrichResult.manifest.components.length} modules, domain=${domainLabel}`);
|
|
37837
|
+
}
|
|
37838
|
+
}
|
|
37839
|
+
} catch (e) {
|
|
37592
37840
|
if (!opts.json) {
|
|
37593
|
-
|
|
37594
|
-
console.log(` AI enrich: ${enrichResult.manifest.components.length} modules, domain=${domainLabel}`);
|
|
37841
|
+
console.log(chalk3.dim(` [AI enrich skipped: ${e.message}]`));
|
|
37595
37842
|
}
|
|
37596
37843
|
}
|
|
37597
|
-
}
|
|
37598
|
-
|
|
37599
|
-
|
|
37844
|
+
}
|
|
37845
|
+
if (manifestSource === "none") {
|
|
37846
|
+
const fallback = buildFallbackManifest2({ project, facts, modules });
|
|
37847
|
+
if (fallback && fallback.components.length > 0) {
|
|
37848
|
+
await writeManifest2(fallback, evidenceDir);
|
|
37849
|
+
manifestSource = "fallback";
|
|
37850
|
+
manifestComponentCount = fallback.components.length;
|
|
37600
37851
|
}
|
|
37601
37852
|
}
|
|
37853
|
+
const manifestNote = describeEvidenceManifest2(manifestSource, manifestComponentCount);
|
|
37602
37854
|
const moduleSummaries = buildModuleSummaries(facts, graph, project);
|
|
37603
37855
|
if (moduleSummaries.size > 0) {
|
|
37604
37856
|
const modulesDir = path112.join(evidenceDir, "modules");
|
|
@@ -37709,7 +37961,13 @@ async function extractCodebase(opts) {
|
|
|
37709
37961
|
facts: { total: facts.length, byKind },
|
|
37710
37962
|
graph: { nodes: repoGraph.nodes.length, edges: repoGraph.edges.length },
|
|
37711
37963
|
incremental: !!opts.incremental && !!changedFiles,
|
|
37712
|
-
outputDir: wikiRoot
|
|
37964
|
+
outputDir: wikiRoot,
|
|
37965
|
+
manifest: {
|
|
37966
|
+
written: manifestSource !== "none",
|
|
37967
|
+
source: manifestSource,
|
|
37968
|
+
components: manifestComponentCount,
|
|
37969
|
+
...manifestNote ? { note: manifestNote } : {}
|
|
37970
|
+
}
|
|
37713
37971
|
};
|
|
37714
37972
|
if (opts.json) {
|
|
37715
37973
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -37727,6 +37985,9 @@ async function extractCodebase(opts) {
|
|
|
37727
37985
|
console.log(` Call chains: ${callChains.length} chains (max depth ${Math.max(...callChains.map((c) => c.depth))})`);
|
|
37728
37986
|
}
|
|
37729
37987
|
console.log(` Output: ${wikiRoot}`);
|
|
37988
|
+
if (manifestNote) {
|
|
37989
|
+
console.log(` ${manifestNote}`);
|
|
37990
|
+
}
|
|
37730
37991
|
}
|
|
37731
37992
|
}
|
|
37732
37993
|
var init_codebase_extract = __esm({
|
|
@@ -37995,7 +38256,8 @@ var init_repo_cache = __esm({
|
|
|
37995
38256
|
// src/deep-enrich.ts
|
|
37996
38257
|
var deep_enrich_exports = {};
|
|
37997
38258
|
__export(deep_enrich_exports, {
|
|
37998
|
-
deepEnrich: () => deepEnrich
|
|
38259
|
+
deepEnrich: () => deepEnrich,
|
|
38260
|
+
runHiddenDeepEnrich: () => runHiddenDeepEnrich
|
|
37999
38261
|
});
|
|
38000
38262
|
import { readFile as readFile11, writeFile as writeFile11, readdir as readdir4, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
|
|
38001
38263
|
import path114 from "path";
|
|
@@ -38577,6 +38839,18 @@ async function invalidateStaleAiDocs(docsDir, slugs) {
|
|
|
38577
38839
|
await removeIfExists(architectureDocPath(docsDir));
|
|
38578
38840
|
await removeIfExists(path114.join(docsDir, "graph-g5-scenarios.md"));
|
|
38579
38841
|
}
|
|
38842
|
+
async function runHiddenDeepEnrich(opts) {
|
|
38843
|
+
const wikiRoot = opts.wikiRoot ?? path114.join(process.cwd(), ".teamai", "team-repo", "teamwiki");
|
|
38844
|
+
const evidenceDir = path114.join(wikiRoot, "evidence", "code", opts.project);
|
|
38845
|
+
const result = await deepEnrich({
|
|
38846
|
+
project: opts.project,
|
|
38847
|
+
evidenceDir,
|
|
38848
|
+
wikiRoot,
|
|
38849
|
+
maxModules: opts.maxModules
|
|
38850
|
+
});
|
|
38851
|
+
if (!result.complete) process.exitCode = 1;
|
|
38852
|
+
return result;
|
|
38853
|
+
}
|
|
38580
38854
|
async function deepEnrich(opts) {
|
|
38581
38855
|
const { project, evidenceDir } = opts;
|
|
38582
38856
|
const docsDir = path114.join(evidenceDir, "docs");
|
|
@@ -40789,7 +41063,7 @@ async function codebaseCmd(opts) {
|
|
|
40789
41063
|
componentCount = 0;
|
|
40790
41064
|
}
|
|
40791
41065
|
if (componentCount === 0) {
|
|
40792
|
-
console.log(`No components in
|
|
41066
|
+
console.log(`No components in _manifest.json for project "${project}".`);
|
|
40793
41067
|
process.exitCode = 1;
|
|
40794
41068
|
return;
|
|
40795
41069
|
}
|
|
@@ -42433,11 +42707,12 @@ ciCmd.command("extract-mr").description("Extract knowledge from MR/PR and post a
|
|
|
42433
42707
|
await ciExtractMr2({ ...globalOpts, ...cmdOpts });
|
|
42434
42708
|
});
|
|
42435
42709
|
program.command("deep-enrich", { hidden: true }).description("Run deep AI knowledge generation for an imported repo").requiredOption("--project <slug>", "Project slug (directory name in evidence/code/)").option("--wiki-root <path>", "Teamwiki root path").option("--max-modules <n>", "Max modules to process (cost control)", parseInt).action(async (cmdOpts) => {
|
|
42436
|
-
const
|
|
42437
|
-
|
|
42438
|
-
|
|
42439
|
-
|
|
42440
|
-
|
|
42710
|
+
const { runHiddenDeepEnrich: runHiddenDeepEnrich2 } = await Promise.resolve().then(() => (init_deep_enrich(), deep_enrich_exports));
|
|
42711
|
+
await runHiddenDeepEnrich2({
|
|
42712
|
+
project: cmdOpts.project,
|
|
42713
|
+
wikiRoot: cmdOpts.wikiRoot,
|
|
42714
|
+
maxModules: cmdOpts.maxModules
|
|
42715
|
+
});
|
|
42441
42716
|
});
|
|
42442
42717
|
recallCmd.command("feedback").description("Record manual feedback for a recalled document").option("--positive <docId>", "Upvote a document (marks as actually useful)").option("--negative <docId>", "Record negative signal for a document").action(async (cmdOpts) => {
|
|
42443
42718
|
const { recallFeedback: recallFeedback2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamai-cli",
|
|
3
|
-
"version": "0.24.0-beta.
|
|
3
|
+
"version": "0.24.0-beta.9",
|
|
4
4
|
"description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,26 +56,32 @@
|
|
|
56
56
|
"ora": "^8.1.0",
|
|
57
57
|
"semver": "^7.8.5",
|
|
58
58
|
"simple-git": "^3.36.0",
|
|
59
|
-
"smol-toml": "^1.
|
|
59
|
+
"smol-toml": "^1.7.1",
|
|
60
60
|
"tree-sitter-wasms": "0.1.13",
|
|
61
61
|
"web-tree-sitter": "0.25.10",
|
|
62
62
|
"yaml": "^2.6.0",
|
|
63
63
|
"zod": "^3.24.0"
|
|
64
64
|
},
|
|
65
65
|
"overrides": {
|
|
66
|
-
"js-yaml": "^3.15.
|
|
66
|
+
"js-yaml": "^3.15.2",
|
|
67
|
+
"brace-expansion@1": "1.1.18",
|
|
68
|
+
"brace-expansion@2": "2.1.4",
|
|
69
|
+
"brace-expansion@5": "5.0.9",
|
|
70
|
+
"nanoid": "3.3.19",
|
|
71
|
+
"postcss": "8.5.28",
|
|
72
|
+
"picomatch": "4.0.7"
|
|
67
73
|
},
|
|
68
74
|
"devDependencies": {
|
|
69
75
|
"@types/cross-spawn": "^6.0.6",
|
|
70
76
|
"@types/fs-extra": "^11.0.4",
|
|
71
77
|
"@types/node": "^20.17.0",
|
|
72
78
|
"@types/semver": "^7.8.0",
|
|
73
|
-
"@vitest/coverage-v8": "^2.
|
|
79
|
+
"@vitest/coverage-v8": "^3.2.7",
|
|
74
80
|
"opencode-ai": "1.18.23",
|
|
75
81
|
"standard-version": "^9.5.0",
|
|
76
82
|
"tsup": "^8.3.0",
|
|
77
83
|
"typescript": "^5.7.0",
|
|
78
|
-
"vitest": "^2.
|
|
84
|
+
"vitest": "^3.2.7"
|
|
79
85
|
},
|
|
80
86
|
"main": "index.js",
|
|
81
87
|
"directories": {
|