teamai-cli 0.25.0-beta.6 → 0.25.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/README.ja.md +26 -227
- package/README.ko.md +26 -227
- package/README.md +26 -227
- package/README.th.md +26 -227
- package/README.zh-CN.md +26 -227
- package/dist/index.js +519 -424
- package/package.json +1 -1
- package/skills/teamai/SKILL.md +6 -12
- package/skills/teamai/references/join-member.md +8 -10
- package/skills/teamai/references/provider-tgit.md +75 -0
- package/skills/teamai/references/setup-admin.md +23 -73
package/dist/index.js
CHANGED
|
@@ -838,12 +838,17 @@ function getWebhookSharing(config) {
|
|
|
838
838
|
const w = config.sharing?.webhooks;
|
|
839
839
|
return {
|
|
840
840
|
enabled: w?.enabled ?? false,
|
|
841
|
+
// Preserve every schema-accepted field. Previously `secret` was dropped and
|
|
842
|
+
// `timeout`/`retries` were force-overridden, so a configured signing secret
|
|
843
|
+
// never reached the request and receivers with signature verification
|
|
844
|
+
// rejected the (unsigned) webhook (#703).
|
|
841
845
|
endpoints: (w?.endpoints ?? []).map((ep) => ({
|
|
842
846
|
url: ep.url,
|
|
843
847
|
type: ep.type,
|
|
848
|
+
secret: ep.secret,
|
|
844
849
|
events: ep.events ?? ["push", "pull", "skill-use", "session-start", "session-stop"],
|
|
845
|
-
timeout: 5e3,
|
|
846
|
-
retries: 3
|
|
850
|
+
timeout: ep.timeout ?? 5e3,
|
|
851
|
+
retries: ep.retries ?? 3
|
|
847
852
|
}))
|
|
848
853
|
};
|
|
849
854
|
}
|
|
@@ -11378,16 +11383,19 @@ function selectToolWorkspaces(config, currentPath, currentTool) {
|
|
|
11378
11383
|
async function buildReportPayload(config, context) {
|
|
11379
11384
|
const manifest = await loadManifest();
|
|
11380
11385
|
const tool = context.tool ?? "workbuddy";
|
|
11381
|
-
const
|
|
11382
|
-
const toolPath = toolPaths[tool];
|
|
11386
|
+
const teamConfig = createLocalAgentTeamConfig(config.endpoint);
|
|
11383
11387
|
const manifestSlugs = collectManifestSlugs(manifest);
|
|
11384
|
-
const scanScope = async (
|
|
11388
|
+
const scanScope = async (workspacePath) => {
|
|
11389
|
+
const scope = workspacePath ? "project" : "user";
|
|
11390
|
+
const localConfig = createResourceLocalConfig(config, scope, workspacePath ?? getUserHome(), workspacePath);
|
|
11391
|
+
const toolPath = scopedToolPaths(teamConfig, localConfig)[tool];
|
|
11385
11392
|
if (!toolPath) return { skills: [], rules: [] };
|
|
11393
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
11386
11394
|
const skills = toolPath.skills ? await scanSkillsFromDisk(path30.join(baseDir, toolPath.skills), manifestSlugs.skills) : [];
|
|
11387
11395
|
const rules = toolPath.rules ? await scanRulesFromDisk(path30.join(baseDir, toolPath.rules), manifestSlugs.rules) : [];
|
|
11388
11396
|
return { skills, rules };
|
|
11389
11397
|
};
|
|
11390
|
-
const userScope = await scanScope(
|
|
11398
|
+
const userScope = await scanScope();
|
|
11391
11399
|
const userLevel = { group_id: config.userGroupId };
|
|
11392
11400
|
if (userScope.skills.length > 0) userLevel.skills = userScope.skills;
|
|
11393
11401
|
if (userScope.rules.length > 0) userLevel.rules = userScope.rules;
|
|
@@ -16941,6 +16949,9 @@ async function remoteBranchExists2(spec, repoRoot) {
|
|
|
16941
16949
|
return false;
|
|
16942
16950
|
}
|
|
16943
16951
|
}
|
|
16952
|
+
async function fetchTrackingRef(git, branch) {
|
|
16953
|
+
await git.fetch(["origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
|
|
16954
|
+
}
|
|
16944
16955
|
async function ensureWorktree(spec, localConfig, options = {}) {
|
|
16945
16956
|
if (!usesBranchWorktree(localConfig)) {
|
|
16946
16957
|
return getWorktreeDir(localConfig, spec.worktreeDirname);
|
|
@@ -16970,14 +16981,14 @@ async function ensureWorktree(spec, localConfig, options = {}) {
|
|
|
16970
16981
|
}
|
|
16971
16982
|
if (await remoteBranchExists2(spec, repoRoot)) {
|
|
16972
16983
|
try {
|
|
16973
|
-
await git
|
|
16984
|
+
await fetchTrackingRef(git, spec.branch);
|
|
16974
16985
|
} catch {
|
|
16975
16986
|
}
|
|
16976
16987
|
const branches = await git.branchLocal();
|
|
16977
16988
|
if (branches.all.includes(spec.branch)) {
|
|
16978
16989
|
await git.raw(["worktree", "add", wt, spec.branch]);
|
|
16979
16990
|
} else {
|
|
16980
|
-
await git.raw(["worktree", "add", wt, "--track", "-b", spec.branch, `origin/${spec.branch}`]);
|
|
16991
|
+
await git.raw(["worktree", "add", wt, "--no-track", "-b", spec.branch, `origin/${spec.branch}`]);
|
|
16981
16992
|
}
|
|
16982
16993
|
} else {
|
|
16983
16994
|
const branches = await git.branchLocal();
|
|
@@ -17054,7 +17065,7 @@ async function commitAndPushAt(spec, wt, message, files, options = {}) {
|
|
|
17054
17065
|
return { status: "failed", reason: pushErr.message };
|
|
17055
17066
|
}
|
|
17056
17067
|
try {
|
|
17057
|
-
await git
|
|
17068
|
+
await fetchTrackingRef(git, spec.branch);
|
|
17058
17069
|
await git.rebase([`origin/${spec.branch}`]);
|
|
17059
17070
|
} catch (rebaseErr) {
|
|
17060
17071
|
log.debug(`[${spec.logTag}] rebase failed, retrying: ${rebaseErr.message}`);
|
|
@@ -17168,7 +17179,7 @@ async function syncWorktree(spec, wt) {
|
|
|
17168
17179
|
const git = createGit2(wt);
|
|
17169
17180
|
const upstream = `origin/${spec.branch}`;
|
|
17170
17181
|
try {
|
|
17171
|
-
await git
|
|
17182
|
+
await fetchTrackingRef(git, spec.branch);
|
|
17172
17183
|
} catch (e) {
|
|
17173
17184
|
log.debug(`[${spec.logTag}] fetch failed, using the local copy: ${e.message}`);
|
|
17174
17185
|
return;
|
|
@@ -18911,17 +18922,21 @@ async function pushGroup(args) {
|
|
|
18911
18922
|
{ reuseBranch: Boolean(reuse) }
|
|
18912
18923
|
);
|
|
18913
18924
|
workingTreeDirtied = false;
|
|
18914
|
-
|
|
18925
|
+
const needsPrRetry = Boolean(reuse) && !reuse?.prUrl;
|
|
18926
|
+
if (!hasChanges && !needsPrRetry) {
|
|
18915
18927
|
pushSpin.succeed(
|
|
18916
18928
|
reuse ? `No changes to push (PR already up to date: ${reuse.prUrl ?? branchName})` : "No changes to push (files already up to date)"
|
|
18917
18929
|
);
|
|
18918
|
-
return
|
|
18930
|
+
return "nochange";
|
|
18919
18931
|
}
|
|
18920
|
-
pushSpin.succeed(
|
|
18932
|
+
pushSpin.succeed(
|
|
18933
|
+
hasChanges ? `Pushed branch ${branchName}` : `No new changes; retrying PR creation for ${branchName}`
|
|
18934
|
+
);
|
|
18921
18935
|
let prUrl;
|
|
18922
|
-
|
|
18936
|
+
let prFailed = false;
|
|
18937
|
+
if (reuse?.prUrl) {
|
|
18923
18938
|
prUrl = reuse.prUrl;
|
|
18924
|
-
log.success(`Existing PR updated: ${prUrl
|
|
18939
|
+
log.success(`Existing PR updated: ${prUrl}`);
|
|
18925
18940
|
} else {
|
|
18926
18941
|
prUrl = await createPrWithFallback(
|
|
18927
18942
|
teamConfig,
|
|
@@ -18933,6 +18948,7 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
|
18933
18948
|
);
|
|
18934
18949
|
if (!prUrl) {
|
|
18935
18950
|
process.exitCode = 1;
|
|
18951
|
+
prFailed = true;
|
|
18936
18952
|
}
|
|
18937
18953
|
}
|
|
18938
18954
|
recordPendingPush(pushState, {
|
|
@@ -18945,7 +18961,7 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
|
18945
18961
|
for (const rel of pushedFiles) {
|
|
18946
18962
|
await pruneEmptyDirs(path50.resolve(localConfig.repo.localPath, rel));
|
|
18947
18963
|
}
|
|
18948
|
-
return
|
|
18964
|
+
return prFailed ? "pr-failed" : "pushed";
|
|
18949
18965
|
} catch (e) {
|
|
18950
18966
|
pushSpin.fail(`Push failed: ${e.message}`);
|
|
18951
18967
|
if (workingTreeDirtied) {
|
|
@@ -18960,10 +18976,10 @@ ${items.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
|
18960
18976
|
);
|
|
18961
18977
|
}
|
|
18962
18978
|
}
|
|
18963
|
-
return
|
|
18979
|
+
return "failed";
|
|
18964
18980
|
}
|
|
18965
18981
|
}
|
|
18966
|
-
async function push(options) {
|
|
18982
|
+
async function push(options, result) {
|
|
18967
18983
|
const { localConfig, teamConfig } = await autoDetectInit();
|
|
18968
18984
|
assertNotReadOnly(localConfig, "teamai push");
|
|
18969
18985
|
if (options.project) {
|
|
@@ -19036,7 +19052,7 @@ async function push(options) {
|
|
|
19036
19052
|
if (pendingTeamConfig !== null) {
|
|
19037
19053
|
await writeFile(path50.join(wtConfig.repo.localPath, "teamai.yaml"), pendingTeamConfig);
|
|
19038
19054
|
}
|
|
19039
|
-
await pushCore(wtConfig, teamConfig, options, pendingTeamConfig);
|
|
19055
|
+
await pushCore(wtConfig, teamConfig, options, pendingTeamConfig, result);
|
|
19040
19056
|
});
|
|
19041
19057
|
} catch (e) {
|
|
19042
19058
|
if (e instanceof EmptyRepoError2) {
|
|
@@ -19059,12 +19075,12 @@ async function push(options) {
|
|
|
19059
19075
|
return;
|
|
19060
19076
|
}
|
|
19061
19077
|
try {
|
|
19062
|
-
await pushCore(localConfig, teamConfig, options);
|
|
19078
|
+
await pushCore(localConfig, teamConfig, options, null, result);
|
|
19063
19079
|
} finally {
|
|
19064
19080
|
await releaseLock(syncLock);
|
|
19065
19081
|
}
|
|
19066
19082
|
}
|
|
19067
|
-
async function pushCore(localConfig, teamConfig, options, initialPendingTeamConfig = null) {
|
|
19083
|
+
async function pushCore(localConfig, teamConfig, options, initialPendingTeamConfig = null, result) {
|
|
19068
19084
|
const selfMode = localConfig.repo.kind === "self";
|
|
19069
19085
|
const scopeLabel = localConfig.scope;
|
|
19070
19086
|
let pendingTeamConfig = initialPendingTeamConfig;
|
|
@@ -19238,7 +19254,7 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
19238
19254
|
const pendingPushes = pushState.pendingPushes;
|
|
19239
19255
|
if (allItems.length === 0) {
|
|
19240
19256
|
if (pendingTeamConfig !== null) {
|
|
19241
|
-
await pushTeamConfigOnly(localConfig, teamConfig, options);
|
|
19257
|
+
await pushTeamConfigOnly(localConfig, teamConfig, options, result);
|
|
19242
19258
|
return;
|
|
19243
19259
|
}
|
|
19244
19260
|
log.info("No new or modified resources to push");
|
|
@@ -19386,19 +19402,23 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
19386
19402
|
}
|
|
19387
19403
|
}
|
|
19388
19404
|
let configRider = pendingTeamConfig !== null;
|
|
19405
|
+
let anyPushed = false;
|
|
19406
|
+
let anyPrFailed = false;
|
|
19389
19407
|
for (const group of groups) {
|
|
19390
|
-
const
|
|
19408
|
+
const outcome = await pushGroup({
|
|
19391
19409
|
group,
|
|
19392
19410
|
teamConfig,
|
|
19393
19411
|
localConfig,
|
|
19394
19412
|
pushState,
|
|
19395
19413
|
includeTeamConfig: configRider
|
|
19396
19414
|
});
|
|
19397
|
-
if (
|
|
19415
|
+
if (outcome === "failed") {
|
|
19398
19416
|
await saveStateForScope(pushState, localConfig);
|
|
19399
19417
|
process.exitCode = 1;
|
|
19400
19418
|
return;
|
|
19401
19419
|
}
|
|
19420
|
+
if (outcome === "pushed") anyPushed = true;
|
|
19421
|
+
if (outcome === "pr-failed") anyPrFailed = true;
|
|
19402
19422
|
configRider = false;
|
|
19403
19423
|
}
|
|
19404
19424
|
const state = pushState;
|
|
@@ -19415,8 +19435,9 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
19415
19435
|
}
|
|
19416
19436
|
}
|
|
19417
19437
|
await saveStateForScope(state, localConfig);
|
|
19438
|
+
if (result && anyPushed && !anyPrFailed) result.completed = true;
|
|
19418
19439
|
}
|
|
19419
|
-
async function pushTeamConfigOnly(localConfig, teamConfig, options) {
|
|
19440
|
+
async function pushTeamConfigOnly(localConfig, teamConfig, options, result) {
|
|
19420
19441
|
console.log("");
|
|
19421
19442
|
console.log("Found team config change to push:");
|
|
19422
19443
|
console.log(" - teamai.yaml");
|
|
@@ -19449,6 +19470,8 @@ async function pushTeamConfigOnly(localConfig, teamConfig, options) {
|
|
|
19449
19470
|
);
|
|
19450
19471
|
if (!prUrl) {
|
|
19451
19472
|
process.exitCode = 1;
|
|
19473
|
+
} else if (result) {
|
|
19474
|
+
result.completed = true;
|
|
19452
19475
|
}
|
|
19453
19476
|
await checkoutMaster(localConfig.repo.localPath);
|
|
19454
19477
|
} catch (e) {
|
|
@@ -21263,6 +21286,270 @@ var init_commands = __esm({
|
|
|
21263
21286
|
}
|
|
21264
21287
|
});
|
|
21265
21288
|
|
|
21289
|
+
// src/webhook-formatters.ts
|
|
21290
|
+
function formatFeishuMessage(payload) {
|
|
21291
|
+
const eventLabel = formatEventLabel(payload.event);
|
|
21292
|
+
const time = new Date(payload.timestamp).toLocaleString("zh-CN", {
|
|
21293
|
+
timeZone: "Asia/Shanghai",
|
|
21294
|
+
year: "numeric",
|
|
21295
|
+
month: "2-digit",
|
|
21296
|
+
day: "2-digit",
|
|
21297
|
+
hour: "2-digit",
|
|
21298
|
+
minute: "2-digit",
|
|
21299
|
+
second: "2-digit"
|
|
21300
|
+
});
|
|
21301
|
+
const lines = [
|
|
21302
|
+
`\u{1F916} TeamAI Notification`,
|
|
21303
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
21304
|
+
`Event: ${eventLabel}`
|
|
21305
|
+
];
|
|
21306
|
+
if (payload.username) {
|
|
21307
|
+
lines.push(`User: ${payload.username}`);
|
|
21308
|
+
}
|
|
21309
|
+
lines.push(`Time: ${time}`);
|
|
21310
|
+
if (payload.tool) {
|
|
21311
|
+
lines.push(`Tool: ${payload.tool}`);
|
|
21312
|
+
}
|
|
21313
|
+
if (payload.data) {
|
|
21314
|
+
const dataStr = formatEventData(payload.event, payload.data);
|
|
21315
|
+
if (dataStr) {
|
|
21316
|
+
lines.push(dataStr);
|
|
21317
|
+
}
|
|
21318
|
+
}
|
|
21319
|
+
lines.push(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
21320
|
+
return {
|
|
21321
|
+
msg_type: "text",
|
|
21322
|
+
content: {
|
|
21323
|
+
text: lines.join("\n")
|
|
21324
|
+
}
|
|
21325
|
+
};
|
|
21326
|
+
}
|
|
21327
|
+
function formatWecomMessage(payload) {
|
|
21328
|
+
const eventLabel = formatEventLabel(payload.event);
|
|
21329
|
+
const time = new Date(payload.timestamp).toLocaleString("zh-CN", {
|
|
21330
|
+
timeZone: "Asia/Shanghai",
|
|
21331
|
+
year: "numeric",
|
|
21332
|
+
month: "2-digit",
|
|
21333
|
+
day: "2-digit",
|
|
21334
|
+
hour: "2-digit",
|
|
21335
|
+
minute: "2-digit",
|
|
21336
|
+
second: "2-digit"
|
|
21337
|
+
});
|
|
21338
|
+
const lines = [
|
|
21339
|
+
`\u{1F916} TeamAI Notification`,
|
|
21340
|
+
`Event: ${eventLabel}`
|
|
21341
|
+
];
|
|
21342
|
+
if (payload.username) {
|
|
21343
|
+
lines.push(`User: ${payload.username}`);
|
|
21344
|
+
}
|
|
21345
|
+
lines.push(`Time: ${time}`);
|
|
21346
|
+
if (payload.tool) {
|
|
21347
|
+
lines.push(`Tool: ${payload.tool}`);
|
|
21348
|
+
}
|
|
21349
|
+
if (payload.data) {
|
|
21350
|
+
const dataStr = formatEventData(payload.event, payload.data);
|
|
21351
|
+
if (dataStr) {
|
|
21352
|
+
lines.push(dataStr);
|
|
21353
|
+
}
|
|
21354
|
+
}
|
|
21355
|
+
return {
|
|
21356
|
+
msgtype: "text",
|
|
21357
|
+
text: {
|
|
21358
|
+
content: lines.join("\n")
|
|
21359
|
+
}
|
|
21360
|
+
};
|
|
21361
|
+
}
|
|
21362
|
+
function formatGenericJson(payload) {
|
|
21363
|
+
return {
|
|
21364
|
+
event: payload.event,
|
|
21365
|
+
timestamp: payload.timestamp,
|
|
21366
|
+
tool: payload.tool,
|
|
21367
|
+
sessionId: payload.sessionId,
|
|
21368
|
+
cwd: payload.cwd,
|
|
21369
|
+
team: payload.team,
|
|
21370
|
+
username: payload.username,
|
|
21371
|
+
data: payload.data
|
|
21372
|
+
};
|
|
21373
|
+
}
|
|
21374
|
+
function formatEventLabel(event) {
|
|
21375
|
+
const labels = {
|
|
21376
|
+
"push": "Push Complete",
|
|
21377
|
+
"pull": "Pull Complete",
|
|
21378
|
+
"skill-use": "Skill Used",
|
|
21379
|
+
"session-start": "Session Started",
|
|
21380
|
+
"session-stop": "Session Ended",
|
|
21381
|
+
"webhook-test": "Webhook Test"
|
|
21382
|
+
};
|
|
21383
|
+
return labels[event] ?? event;
|
|
21384
|
+
}
|
|
21385
|
+
function formatEventData(event, data) {
|
|
21386
|
+
switch (event) {
|
|
21387
|
+
case "push":
|
|
21388
|
+
case "pull":
|
|
21389
|
+
if (data.resources && typeof data.resources === "object") {
|
|
21390
|
+
const resources = data.resources;
|
|
21391
|
+
const parts = Object.entries(resources).filter(([, count]) => count > 0).map(([type, count]) => `${type}: ${count}`);
|
|
21392
|
+
return parts.length > 0 ? `Resources: ${parts.join(", ")}` : null;
|
|
21393
|
+
}
|
|
21394
|
+
return null;
|
|
21395
|
+
case "skill-use":
|
|
21396
|
+
if (data.skillName && typeof data.skillName === "string") {
|
|
21397
|
+
return `Skill: ${data.skillName}`;
|
|
21398
|
+
}
|
|
21399
|
+
return null;
|
|
21400
|
+
case "session-start":
|
|
21401
|
+
case "session-stop":
|
|
21402
|
+
if (data.sessionId && typeof data.sessionId === "string") {
|
|
21403
|
+
return `Session: ${data.sessionId.slice(0, 8)}...`;
|
|
21404
|
+
}
|
|
21405
|
+
return null;
|
|
21406
|
+
default:
|
|
21407
|
+
return null;
|
|
21408
|
+
}
|
|
21409
|
+
}
|
|
21410
|
+
var init_webhook_formatters = __esm({
|
|
21411
|
+
"src/webhook-formatters.ts"() {
|
|
21412
|
+
"use strict";
|
|
21413
|
+
}
|
|
21414
|
+
});
|
|
21415
|
+
|
|
21416
|
+
// src/webhook.ts
|
|
21417
|
+
var webhook_exports = {};
|
|
21418
|
+
__export(webhook_exports, {
|
|
21419
|
+
listWebhooks: () => listWebhooks,
|
|
21420
|
+
loadWebhookConfig: () => loadWebhookConfig,
|
|
21421
|
+
sendWebhook: () => sendWebhook,
|
|
21422
|
+
testWebhook: () => testWebhook
|
|
21423
|
+
});
|
|
21424
|
+
import { createHmac } from "crypto";
|
|
21425
|
+
async function sendWebhook(event, payload, config) {
|
|
21426
|
+
if (!config) {
|
|
21427
|
+
const { teamConfig } = await autoDetectInit();
|
|
21428
|
+
config = getWebhookSharing(teamConfig);
|
|
21429
|
+
}
|
|
21430
|
+
if (!config.enabled || config.endpoints.length === 0) return;
|
|
21431
|
+
const fullPayload = {
|
|
21432
|
+
event,
|
|
21433
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21434
|
+
tool: payload.tool ?? "unknown",
|
|
21435
|
+
sessionId: payload.sessionId,
|
|
21436
|
+
cwd: payload.cwd,
|
|
21437
|
+
team: payload.team,
|
|
21438
|
+
username: payload.username,
|
|
21439
|
+
data: payload.data ?? {}
|
|
21440
|
+
};
|
|
21441
|
+
const matchingEndpoints = config.endpoints.filter(
|
|
21442
|
+
(ep) => ep.events.includes(event) || ep.events.includes("*")
|
|
21443
|
+
);
|
|
21444
|
+
if (matchingEndpoints.length === 0) return;
|
|
21445
|
+
await Promise.allSettled(
|
|
21446
|
+
matchingEndpoints.map((ep) => sendToEndpoint(ep, fullPayload))
|
|
21447
|
+
);
|
|
21448
|
+
}
|
|
21449
|
+
async function sendToEndpoint(endpoint, payload) {
|
|
21450
|
+
const { url, type, secret, timeout, retries } = endpoint;
|
|
21451
|
+
const body = formatMessage(type, payload);
|
|
21452
|
+
const serialized = typeof body === "string" ? body : JSON.stringify(body);
|
|
21453
|
+
const outbound = redactWithEnv(serialized);
|
|
21454
|
+
const headers = {
|
|
21455
|
+
"Content-Type": type === "json" ? "application/json" : "text/plain; charset=utf-8"
|
|
21456
|
+
};
|
|
21457
|
+
if (secret) {
|
|
21458
|
+
const signature = createHmac("sha256", secret).update(outbound).digest("hex");
|
|
21459
|
+
headers["X-TeamAI-Signature"] = `sha256=${signature}`;
|
|
21460
|
+
}
|
|
21461
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
21462
|
+
try {
|
|
21463
|
+
const controller = new AbortController();
|
|
21464
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
21465
|
+
const response = await fetch(url, {
|
|
21466
|
+
method: "POST",
|
|
21467
|
+
headers,
|
|
21468
|
+
body: outbound,
|
|
21469
|
+
signal: controller.signal
|
|
21470
|
+
});
|
|
21471
|
+
clearTimeout(timeoutId);
|
|
21472
|
+
if (response.ok) {
|
|
21473
|
+
log.debug(`Webhook sent successfully to ${url}`);
|
|
21474
|
+
return;
|
|
21475
|
+
}
|
|
21476
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
21477
|
+
log.warn(`Webhook to ${url} failed with status ${response.status} (not retrying)`);
|
|
21478
|
+
return;
|
|
21479
|
+
}
|
|
21480
|
+
if (attempt < retries) {
|
|
21481
|
+
const delay = Math.pow(2, attempt) * 1e3;
|
|
21482
|
+
log.debug(`Webhook to ${url} failed, retrying in ${delay}ms...`);
|
|
21483
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
21484
|
+
}
|
|
21485
|
+
} catch (error) {
|
|
21486
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
21487
|
+
log.warn(`Webhook to ${url} timed out after ${timeout}ms`);
|
|
21488
|
+
} else if (attempt < retries) {
|
|
21489
|
+
const delay = Math.pow(2, attempt) * 1e3;
|
|
21490
|
+
log.debug(`Webhook to ${url} failed, retrying in ${delay}ms...`);
|
|
21491
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
21492
|
+
} else {
|
|
21493
|
+
log.warn(`Webhook to ${url} failed after ${retries + 1} attempts: ${error.message}`);
|
|
21494
|
+
}
|
|
21495
|
+
}
|
|
21496
|
+
}
|
|
21497
|
+
}
|
|
21498
|
+
function formatMessage(type, payload) {
|
|
21499
|
+
switch (type) {
|
|
21500
|
+
case "feishu":
|
|
21501
|
+
return formatFeishuMessage(payload);
|
|
21502
|
+
case "wecom":
|
|
21503
|
+
return formatWecomMessage(payload);
|
|
21504
|
+
case "json":
|
|
21505
|
+
default:
|
|
21506
|
+
return formatGenericJson(payload);
|
|
21507
|
+
}
|
|
21508
|
+
}
|
|
21509
|
+
async function loadWebhookConfig() {
|
|
21510
|
+
const { teamConfig } = await autoDetectInit();
|
|
21511
|
+
return getWebhookSharing(teamConfig);
|
|
21512
|
+
}
|
|
21513
|
+
async function listWebhooks() {
|
|
21514
|
+
const config = await loadWebhookConfig();
|
|
21515
|
+
return config.endpoints;
|
|
21516
|
+
}
|
|
21517
|
+
async function testWebhook(url) {
|
|
21518
|
+
const config = await loadWebhookConfig();
|
|
21519
|
+
const endpoints = url ? config.endpoints.filter((ep) => ep.url === url) : config.endpoints;
|
|
21520
|
+
if (endpoints.length === 0) {
|
|
21521
|
+
log.warn("No webhook endpoints configured.");
|
|
21522
|
+
return;
|
|
21523
|
+
}
|
|
21524
|
+
const testPayload = {
|
|
21525
|
+
event: "webhook-test",
|
|
21526
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21527
|
+
tool: "teamai-cli",
|
|
21528
|
+
data: {
|
|
21529
|
+
message: "This is a test webhook from TeamAI CLI"
|
|
21530
|
+
}
|
|
21531
|
+
};
|
|
21532
|
+
for (const endpoint of endpoints) {
|
|
21533
|
+
log.info(`Testing webhook to ${endpoint.url}...`);
|
|
21534
|
+
try {
|
|
21535
|
+
await sendToEndpoint(endpoint, testPayload);
|
|
21536
|
+
log.success(`Webhook test successful: ${endpoint.url}`);
|
|
21537
|
+
} catch (error) {
|
|
21538
|
+
log.error(`Webhook test failed: ${endpoint.url} - ${error.message}`);
|
|
21539
|
+
}
|
|
21540
|
+
}
|
|
21541
|
+
}
|
|
21542
|
+
var init_webhook = __esm({
|
|
21543
|
+
"src/webhook.ts"() {
|
|
21544
|
+
"use strict";
|
|
21545
|
+
init_config();
|
|
21546
|
+
init_logger();
|
|
21547
|
+
init_redact();
|
|
21548
|
+
init_types();
|
|
21549
|
+
init_webhook_formatters();
|
|
21550
|
+
}
|
|
21551
|
+
});
|
|
21552
|
+
|
|
21266
21553
|
// src/migrate.ts
|
|
21267
21554
|
var migrate_exports = {};
|
|
21268
21555
|
__export(migrate_exports, {
|
|
@@ -23139,6 +23426,7 @@ __export(usage_tracker_exports, {
|
|
|
23139
23426
|
isValidSkillName: () => isValidSkillName,
|
|
23140
23427
|
readKnownSkills: () => readKnownSkills,
|
|
23141
23428
|
readUsageEvents: () => readUsageEvents,
|
|
23429
|
+
resolveSkillUse: () => resolveSkillUse,
|
|
23142
23430
|
skillExistsOnDisk: () => skillExistsOnDisk,
|
|
23143
23431
|
track: () => track,
|
|
23144
23432
|
trackFromStdin: () => trackFromStdin,
|
|
@@ -23175,6 +23463,23 @@ function extractSkillName(toolInput) {
|
|
|
23175
23463
|
function isValidSkillName(name) {
|
|
23176
23464
|
return SKILL_NAME_REGEX.test(name);
|
|
23177
23465
|
}
|
|
23466
|
+
function resolveSkillUse(toolName, toolInput) {
|
|
23467
|
+
let skillName = null;
|
|
23468
|
+
let source = null;
|
|
23469
|
+
if (toolName === "Skill") {
|
|
23470
|
+
skillName = extractSkillName(toolInput);
|
|
23471
|
+
} else if (toolName === "Read") {
|
|
23472
|
+
const filePath = (typeof toolInput.file_path === "string" ? toolInput.file_path : null) ?? (typeof toolInput.filePath === "string" ? toolInput.filePath : null) ?? (typeof toolInput.path === "string" ? toolInput.path : null);
|
|
23473
|
+
if (filePath && /\/SKILL\.md$/i.test(filePath)) {
|
|
23474
|
+
skillName = extractSkillName({ skill: filePath });
|
|
23475
|
+
source = "cursor";
|
|
23476
|
+
}
|
|
23477
|
+
} else {
|
|
23478
|
+
return null;
|
|
23479
|
+
}
|
|
23480
|
+
if (!skillName || !isValidSkillName(skillName)) return null;
|
|
23481
|
+
return { skillName, source };
|
|
23482
|
+
}
|
|
23178
23483
|
async function skillExistsOnDisk(skillName) {
|
|
23179
23484
|
const home = getUserHome();
|
|
23180
23485
|
const userSkillDirs = [
|
|
@@ -26063,7 +26368,7 @@ async function warnIfEnvYamlShapeIsWrong(freshConfig, localConfig) {
|
|
|
26063
26368
|
log.debug(`env.yaml shape check skipped: ${e.message}`);
|
|
26064
26369
|
}
|
|
26065
26370
|
}
|
|
26066
|
-
async function pullForScope(localConfig, options, reported, policy = {}) {
|
|
26371
|
+
async function pullForScope(localConfig, options, reported, policy = {}, result) {
|
|
26067
26372
|
const scopeLabel = localConfig.scope;
|
|
26068
26373
|
const revisionField = policy.revisionField ?? "lastPullRev";
|
|
26069
26374
|
const targetsField = revisionField === "lastPullRev" ? "lastPullTargets" : "lastInheritedPullTargets";
|
|
@@ -26110,6 +26415,111 @@ async function pullForScope(localConfig, options, reported, policy = {}) {
|
|
|
26110
26415
|
return;
|
|
26111
26416
|
}
|
|
26112
26417
|
const resourceTypes = policy.resourceTypes ?? ["skills", "rules", "docs", "env", "agents"];
|
|
26418
|
+
let reportsReadRoot;
|
|
26419
|
+
const resolveReportsReadRoot = () => {
|
|
26420
|
+
reportsReadRoot ??= (async () => {
|
|
26421
|
+
if (!usesBranchWorktree(localConfig)) return localConfig.repo.localPath;
|
|
26422
|
+
try {
|
|
26423
|
+
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
26424
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
26425
|
+
return await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
26426
|
+
} catch (e) {
|
|
26427
|
+
log.debug(`reports worktree unavailable: ${e.message}`);
|
|
26428
|
+
return void 0;
|
|
26429
|
+
}
|
|
26430
|
+
})();
|
|
26431
|
+
return reportsReadRoot;
|
|
26432
|
+
};
|
|
26433
|
+
const syncLearningsAndRebuildIndex = async () => {
|
|
26434
|
+
if (options.dryRun) return;
|
|
26435
|
+
try {
|
|
26436
|
+
try {
|
|
26437
|
+
const { learningsBranch: learningsBranch2 } = await Promise.resolve().then(() => (init_learnings_branch(), learnings_branch_exports));
|
|
26438
|
+
await learningsBranch2.refresh(localConfig, { pushIfCreated: false });
|
|
26439
|
+
} catch (e) {
|
|
26440
|
+
log.debug(`learnings worktree unavailable: ${e.message}`);
|
|
26441
|
+
}
|
|
26442
|
+
const roots = learningsRoots(localConfig);
|
|
26443
|
+
const publishedRoots = roots.read;
|
|
26444
|
+
const docsRepoDir = path75.join(localConfig.repo.localPath, "docs");
|
|
26445
|
+
const rulesRepoDir = path75.join(localConfig.repo.localPath, "rules");
|
|
26446
|
+
const skillsRepoDir = path75.join(localConfig.repo.localPath, "skills");
|
|
26447
|
+
const reportsRoot = await resolveReportsReadRoot();
|
|
26448
|
+
const votesDir = reportsRoot ? path75.join(reportsRoot, "votes") : void 0;
|
|
26449
|
+
const activeLearningsNamespaces = roleContext?.activeNamespaces.learnings ?? [];
|
|
26450
|
+
const countLearnings = async (baseDir) => {
|
|
26451
|
+
if (!await pathExists(baseDir)) return [];
|
|
26452
|
+
const names = (await listFiles(baseDir)).filter((f) => f.endsWith(".md"));
|
|
26453
|
+
for (const ns of activeLearningsNamespaces) {
|
|
26454
|
+
const nsDir = path75.join(baseDir, ns);
|
|
26455
|
+
if (await pathExists(nsDir)) {
|
|
26456
|
+
names.push(
|
|
26457
|
+
...(await listFilesRecursive(nsDir)).filter((f) => f.endsWith(".md")).map((f) => path75.join(ns, f))
|
|
26458
|
+
);
|
|
26459
|
+
}
|
|
26460
|
+
}
|
|
26461
|
+
return names;
|
|
26462
|
+
};
|
|
26463
|
+
let learningsCount = 0;
|
|
26464
|
+
let effectiveLearningsDir;
|
|
26465
|
+
const mirrorSources = publishedRoots.filter((dir) => dir !== getUserLearningsDir());
|
|
26466
|
+
const counted = /* @__PURE__ */ new Set();
|
|
26467
|
+
for (const dir of publishedRoots) {
|
|
26468
|
+
for (const name of await countLearnings(dir)) counted.add(name);
|
|
26469
|
+
}
|
|
26470
|
+
learningsCount = counted.size;
|
|
26471
|
+
if (localConfig.scope === "user") {
|
|
26472
|
+
await mirrorLearnings(
|
|
26473
|
+
mirrorSources,
|
|
26474
|
+
getUserLearningsDir(),
|
|
26475
|
+
activeLearningsNamespaces
|
|
26476
|
+
);
|
|
26477
|
+
effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
|
|
26478
|
+
} else {
|
|
26479
|
+
for (const dir of publishedRoots) {
|
|
26480
|
+
if (await pathExists(dir)) {
|
|
26481
|
+
effectiveLearningsDir = dir;
|
|
26482
|
+
break;
|
|
26483
|
+
}
|
|
26484
|
+
}
|
|
26485
|
+
}
|
|
26486
|
+
const hasAnySource = effectiveLearningsDir || await pathExists(docsRepoDir) || await pathExists(rulesRepoDir) || await pathExists(skillsRepoDir);
|
|
26487
|
+
const repoCodebaseDir = path75.join(localConfig.repo.localPath, "docs", "team-codebase");
|
|
26488
|
+
const effectiveCodebaseDir = await pathExists(repoCodebaseDir) ? repoCodebaseDir : void 0;
|
|
26489
|
+
if (hasAnySource || effectiveCodebaseDir) {
|
|
26490
|
+
const votesExist = votesDir ? await pathExists(votesDir) : false;
|
|
26491
|
+
const teamaiHome = getDataHome(localConfig);
|
|
26492
|
+
const indexPath = path75.join(teamaiHome, "search-index.json");
|
|
26493
|
+
const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
|
|
26494
|
+
const elapsed = await buildIndex2({
|
|
26495
|
+
// The queue comes first: a contribution that could not be published
|
|
26496
|
+
// yet stays recallable, and a queued edit wins over the published copy.
|
|
26497
|
+
// Then every published root, so nothing is indexed from one directory
|
|
26498
|
+
// that happened to be picked.
|
|
26499
|
+
learningsDirs: [
|
|
26500
|
+
pendingLearningsDir(localConfig),
|
|
26501
|
+
...effectiveLearningsDir ? [effectiveLearningsDir] : [],
|
|
26502
|
+
...publishedRoots
|
|
26503
|
+
],
|
|
26504
|
+
learningsNamespaces: activeLearningsNamespaces,
|
|
26505
|
+
docsDir: await pathExists(docsRepoDir) ? docsRepoDir : void 0,
|
|
26506
|
+
rulesDir: await pathExists(rulesRepoDir) ? rulesRepoDir : void 0,
|
|
26507
|
+
skillsDir: await pathExists(skillsRepoDir) ? skillsRepoDir : void 0,
|
|
26508
|
+
codebaseDir: void 0,
|
|
26509
|
+
// codebase now served by teamwiki/ graph engine
|
|
26510
|
+
votesDir: votesExist ? votesDir : void 0,
|
|
26511
|
+
indexPath
|
|
26512
|
+
});
|
|
26513
|
+
if (learningsCount > 0) {
|
|
26514
|
+
log.success(`Synced ${learningsCount} learnings (index: ${elapsed}ms)`);
|
|
26515
|
+
} else {
|
|
26516
|
+
log.debug(`[${scopeLabel}] Built multi-category search index in ${elapsed}ms`);
|
|
26517
|
+
}
|
|
26518
|
+
}
|
|
26519
|
+
} catch (e) {
|
|
26520
|
+
log.debug(`Learnings/index sync skipped: ${e.message}`);
|
|
26521
|
+
}
|
|
26522
|
+
};
|
|
26113
26523
|
let currentTargets = null;
|
|
26114
26524
|
if (!options.force && !options.dryRun && !submodulesChanged) {
|
|
26115
26525
|
try {
|
|
@@ -26143,6 +26553,7 @@ async function pullForScope(localConfig, options, reported, policy = {}) {
|
|
|
26143
26553
|
if (resourceTypes.includes("env")) {
|
|
26144
26554
|
await warnIfEnvYamlShapeIsWrong(freshConfig, localConfig);
|
|
26145
26555
|
}
|
|
26556
|
+
await syncLearningsAndRebuildIndex();
|
|
26146
26557
|
return;
|
|
26147
26558
|
}
|
|
26148
26559
|
log.debug(`[${scopeLabel}] Repo unchanged; resource target set changed, syncing`);
|
|
@@ -26302,110 +26713,7 @@ async function pullForScope(localConfig, options, reported, policy = {}) {
|
|
|
26302
26713
|
if (totalSynced === 0) {
|
|
26303
26714
|
log.info(`[${scopeLabel}] No resources to sync`);
|
|
26304
26715
|
}
|
|
26305
|
-
|
|
26306
|
-
const resolveReportsReadRoot = () => {
|
|
26307
|
-
reportsReadRoot ??= (async () => {
|
|
26308
|
-
if (!usesBranchWorktree(localConfig)) return localConfig.repo.localPath;
|
|
26309
|
-
try {
|
|
26310
|
-
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
26311
|
-
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
26312
|
-
return await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
26313
|
-
} catch (e) {
|
|
26314
|
-
log.debug(`reports worktree unavailable: ${e.message}`);
|
|
26315
|
-
return void 0;
|
|
26316
|
-
}
|
|
26317
|
-
})();
|
|
26318
|
-
return reportsReadRoot;
|
|
26319
|
-
};
|
|
26320
|
-
if (!options.dryRun) {
|
|
26321
|
-
try {
|
|
26322
|
-
try {
|
|
26323
|
-
const { learningsBranch: learningsBranch2 } = await Promise.resolve().then(() => (init_learnings_branch(), learnings_branch_exports));
|
|
26324
|
-
await learningsBranch2.refresh(localConfig, { pushIfCreated: false });
|
|
26325
|
-
} catch (e) {
|
|
26326
|
-
log.debug(`learnings worktree unavailable: ${e.message}`);
|
|
26327
|
-
}
|
|
26328
|
-
const roots = learningsRoots(localConfig);
|
|
26329
|
-
const publishedRoots = roots.read;
|
|
26330
|
-
const docsRepoDir = path75.join(localConfig.repo.localPath, "docs");
|
|
26331
|
-
const rulesRepoDir = path75.join(localConfig.repo.localPath, "rules");
|
|
26332
|
-
const skillsRepoDir = path75.join(localConfig.repo.localPath, "skills");
|
|
26333
|
-
const reportsRoot = await resolveReportsReadRoot();
|
|
26334
|
-
const votesDir = reportsRoot ? path75.join(reportsRoot, "votes") : void 0;
|
|
26335
|
-
const activeLearningsNamespaces = roleContext?.activeNamespaces.learnings ?? [];
|
|
26336
|
-
const countLearnings = async (baseDir) => {
|
|
26337
|
-
if (!await pathExists(baseDir)) return [];
|
|
26338
|
-
const names = (await listFiles(baseDir)).filter((f) => f.endsWith(".md"));
|
|
26339
|
-
for (const ns of activeLearningsNamespaces) {
|
|
26340
|
-
const nsDir = path75.join(baseDir, ns);
|
|
26341
|
-
if (await pathExists(nsDir)) {
|
|
26342
|
-
names.push(
|
|
26343
|
-
...(await listFilesRecursive(nsDir)).filter((f) => f.endsWith(".md")).map((f) => path75.join(ns, f))
|
|
26344
|
-
);
|
|
26345
|
-
}
|
|
26346
|
-
}
|
|
26347
|
-
return names;
|
|
26348
|
-
};
|
|
26349
|
-
let learningsCount = 0;
|
|
26350
|
-
let effectiveLearningsDir;
|
|
26351
|
-
const mirrorSources = publishedRoots.filter((dir) => dir !== getUserLearningsDir());
|
|
26352
|
-
const counted = /* @__PURE__ */ new Set();
|
|
26353
|
-
for (const dir of publishedRoots) {
|
|
26354
|
-
for (const name of await countLearnings(dir)) counted.add(name);
|
|
26355
|
-
}
|
|
26356
|
-
learningsCount = counted.size;
|
|
26357
|
-
if (localConfig.scope === "user") {
|
|
26358
|
-
await mirrorLearnings(
|
|
26359
|
-
mirrorSources,
|
|
26360
|
-
getUserLearningsDir(),
|
|
26361
|
-
activeLearningsNamespaces
|
|
26362
|
-
);
|
|
26363
|
-
effectiveLearningsDir = await pathExists(getUserLearningsDir()) ? getUserLearningsDir() : void 0;
|
|
26364
|
-
} else {
|
|
26365
|
-
for (const dir of publishedRoots) {
|
|
26366
|
-
if (await pathExists(dir)) {
|
|
26367
|
-
effectiveLearningsDir = dir;
|
|
26368
|
-
break;
|
|
26369
|
-
}
|
|
26370
|
-
}
|
|
26371
|
-
}
|
|
26372
|
-
const hasAnySource = effectiveLearningsDir || await pathExists(docsRepoDir) || await pathExists(rulesRepoDir) || await pathExists(skillsRepoDir);
|
|
26373
|
-
const repoCodebaseDir = path75.join(localConfig.repo.localPath, "docs", "team-codebase");
|
|
26374
|
-
const effectiveCodebaseDir = await pathExists(repoCodebaseDir) ? repoCodebaseDir : void 0;
|
|
26375
|
-
if (hasAnySource || effectiveCodebaseDir) {
|
|
26376
|
-
const votesExist = votesDir ? await pathExists(votesDir) : false;
|
|
26377
|
-
const teamaiHome = getDataHome(localConfig);
|
|
26378
|
-
const indexPath = path75.join(teamaiHome, "search-index.json");
|
|
26379
|
-
const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
|
|
26380
|
-
const elapsed = await buildIndex2({
|
|
26381
|
-
// The queue comes first: a contribution that could not be published
|
|
26382
|
-
// yet stays recallable, and a queued edit wins over the published copy.
|
|
26383
|
-
// Then every published root, so nothing is indexed from one directory
|
|
26384
|
-
// that happened to be picked.
|
|
26385
|
-
learningsDirs: [
|
|
26386
|
-
pendingLearningsDir(localConfig),
|
|
26387
|
-
...effectiveLearningsDir ? [effectiveLearningsDir] : [],
|
|
26388
|
-
...publishedRoots
|
|
26389
|
-
],
|
|
26390
|
-
learningsNamespaces: activeLearningsNamespaces,
|
|
26391
|
-
docsDir: await pathExists(docsRepoDir) ? docsRepoDir : void 0,
|
|
26392
|
-
rulesDir: await pathExists(rulesRepoDir) ? rulesRepoDir : void 0,
|
|
26393
|
-
skillsDir: await pathExists(skillsRepoDir) ? skillsRepoDir : void 0,
|
|
26394
|
-
codebaseDir: void 0,
|
|
26395
|
-
// codebase now served by teamwiki/ graph engine
|
|
26396
|
-
votesDir: votesExist ? votesDir : void 0,
|
|
26397
|
-
indexPath
|
|
26398
|
-
});
|
|
26399
|
-
if (learningsCount > 0) {
|
|
26400
|
-
log.success(`Synced ${learningsCount} learnings (index: ${elapsed}ms)`);
|
|
26401
|
-
} else {
|
|
26402
|
-
log.debug(`[${scopeLabel}] Built multi-category search index in ${elapsed}ms`);
|
|
26403
|
-
}
|
|
26404
|
-
}
|
|
26405
|
-
} catch (e) {
|
|
26406
|
-
log.debug(`Learnings/index sync skipped: ${e.message}`);
|
|
26407
|
-
}
|
|
26408
|
-
}
|
|
26716
|
+
await syncLearningsAndRebuildIndex();
|
|
26409
26717
|
if (!options.dryRun) {
|
|
26410
26718
|
await syncManagedInstructions(freshConfig, localConfig, roleContext, scopeLabel);
|
|
26411
26719
|
}
|
|
@@ -26493,6 +26801,7 @@ async function pullForScope(localConfig, options, reported, policy = {}) {
|
|
|
26493
26801
|
} catch {
|
|
26494
26802
|
}
|
|
26495
26803
|
}
|
|
26804
|
+
if (result && !options.dryRun) result.completed = true;
|
|
26496
26805
|
}
|
|
26497
26806
|
function compileCulture(raw) {
|
|
26498
26807
|
let parsed;
|
|
@@ -26777,7 +27086,7 @@ async function reinjectLegacyHooks(localConfig) {
|
|
|
26777
27086
|
await injectHooksToAllTools2(teamConfig.toolPaths, baseDir, hookFilter);
|
|
26778
27087
|
log.debug("Hooks migrated to dispatch format");
|
|
26779
27088
|
}
|
|
26780
|
-
async function pull(options) {
|
|
27089
|
+
async function pull(options, result) {
|
|
26781
27090
|
const reported = /* @__PURE__ */ new Set();
|
|
26782
27091
|
const needsHookMigration = await legacyHooksNeedReinject().catch(() => false);
|
|
26783
27092
|
const contended = /* @__PURE__ */ new Set();
|
|
@@ -26819,12 +27128,12 @@ async function pull(options) {
|
|
|
26819
27128
|
await pullForScope(inheritedUserConfig, options, reported, {
|
|
26820
27129
|
resourceTypes: ["skills", "rules", "docs", "agents"],
|
|
26821
27130
|
revisionField: "lastInheritedPullRev"
|
|
26822
|
-
});
|
|
27131
|
+
}, result);
|
|
26823
27132
|
}
|
|
26824
27133
|
} else {
|
|
26825
27134
|
activeUserConfig = loadedUserConfig;
|
|
26826
27135
|
if (await lockScope(activeUserConfig)) {
|
|
26827
|
-
await pullForScope(activeUserConfig, options, reported);
|
|
27136
|
+
await pullForScope(activeUserConfig, options, reported, {}, result);
|
|
26828
27137
|
}
|
|
26829
27138
|
}
|
|
26830
27139
|
} else if (inheritUserScope) {
|
|
@@ -26839,7 +27148,7 @@ async function pull(options) {
|
|
|
26839
27148
|
if (projectConfig) {
|
|
26840
27149
|
try {
|
|
26841
27150
|
if (await lockScope(projectConfig)) {
|
|
26842
|
-
await pullForScope(projectConfig, options, reported);
|
|
27151
|
+
await pullForScope(projectConfig, options, reported, {}, result);
|
|
26843
27152
|
}
|
|
26844
27153
|
} catch (e) {
|
|
26845
27154
|
log.warn(`Project-scope pull error: ${e.message}`);
|
|
@@ -29679,267 +29988,6 @@ var init_mcp_cmd = __esm({
|
|
|
29679
29988
|
}
|
|
29680
29989
|
});
|
|
29681
29990
|
|
|
29682
|
-
// src/webhook-formatters.ts
|
|
29683
|
-
function formatFeishuMessage(payload) {
|
|
29684
|
-
const eventLabel = formatEventLabel(payload.event);
|
|
29685
|
-
const time = new Date(payload.timestamp).toLocaleString("zh-CN", {
|
|
29686
|
-
timeZone: "Asia/Shanghai",
|
|
29687
|
-
year: "numeric",
|
|
29688
|
-
month: "2-digit",
|
|
29689
|
-
day: "2-digit",
|
|
29690
|
-
hour: "2-digit",
|
|
29691
|
-
minute: "2-digit",
|
|
29692
|
-
second: "2-digit"
|
|
29693
|
-
});
|
|
29694
|
-
const lines = [
|
|
29695
|
-
`\u{1F916} TeamAI Notification`,
|
|
29696
|
-
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
29697
|
-
`Event: ${eventLabel}`
|
|
29698
|
-
];
|
|
29699
|
-
if (payload.username) {
|
|
29700
|
-
lines.push(`User: ${payload.username}`);
|
|
29701
|
-
}
|
|
29702
|
-
lines.push(`Time: ${time}`);
|
|
29703
|
-
if (payload.tool) {
|
|
29704
|
-
lines.push(`Tool: ${payload.tool}`);
|
|
29705
|
-
}
|
|
29706
|
-
if (payload.data) {
|
|
29707
|
-
const dataStr = formatEventData(payload.event, payload.data);
|
|
29708
|
-
if (dataStr) {
|
|
29709
|
-
lines.push(dataStr);
|
|
29710
|
-
}
|
|
29711
|
-
}
|
|
29712
|
-
lines.push(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
29713
|
-
return {
|
|
29714
|
-
msg_type: "text",
|
|
29715
|
-
content: {
|
|
29716
|
-
text: lines.join("\n")
|
|
29717
|
-
}
|
|
29718
|
-
};
|
|
29719
|
-
}
|
|
29720
|
-
function formatWecomMessage(payload) {
|
|
29721
|
-
const eventLabel = formatEventLabel(payload.event);
|
|
29722
|
-
const time = new Date(payload.timestamp).toLocaleString("zh-CN", {
|
|
29723
|
-
timeZone: "Asia/Shanghai",
|
|
29724
|
-
year: "numeric",
|
|
29725
|
-
month: "2-digit",
|
|
29726
|
-
day: "2-digit",
|
|
29727
|
-
hour: "2-digit",
|
|
29728
|
-
minute: "2-digit",
|
|
29729
|
-
second: "2-digit"
|
|
29730
|
-
});
|
|
29731
|
-
const lines = [
|
|
29732
|
-
`\u{1F916} TeamAI Notification`,
|
|
29733
|
-
`Event: ${eventLabel}`
|
|
29734
|
-
];
|
|
29735
|
-
if (payload.username) {
|
|
29736
|
-
lines.push(`User: ${payload.username}`);
|
|
29737
|
-
}
|
|
29738
|
-
lines.push(`Time: ${time}`);
|
|
29739
|
-
if (payload.tool) {
|
|
29740
|
-
lines.push(`Tool: ${payload.tool}`);
|
|
29741
|
-
}
|
|
29742
|
-
if (payload.data) {
|
|
29743
|
-
const dataStr = formatEventData(payload.event, payload.data);
|
|
29744
|
-
if (dataStr) {
|
|
29745
|
-
lines.push(dataStr);
|
|
29746
|
-
}
|
|
29747
|
-
}
|
|
29748
|
-
return {
|
|
29749
|
-
msgtype: "text",
|
|
29750
|
-
text: {
|
|
29751
|
-
content: lines.join("\n")
|
|
29752
|
-
}
|
|
29753
|
-
};
|
|
29754
|
-
}
|
|
29755
|
-
function formatGenericJson(payload) {
|
|
29756
|
-
return {
|
|
29757
|
-
event: payload.event,
|
|
29758
|
-
timestamp: payload.timestamp,
|
|
29759
|
-
tool: payload.tool,
|
|
29760
|
-
sessionId: payload.sessionId,
|
|
29761
|
-
cwd: payload.cwd,
|
|
29762
|
-
team: payload.team,
|
|
29763
|
-
username: payload.username,
|
|
29764
|
-
data: payload.data
|
|
29765
|
-
};
|
|
29766
|
-
}
|
|
29767
|
-
function formatEventLabel(event) {
|
|
29768
|
-
const labels = {
|
|
29769
|
-
"push": "Push Complete",
|
|
29770
|
-
"pull": "Pull Complete",
|
|
29771
|
-
"skill-use": "Skill Used",
|
|
29772
|
-
"session-start": "Session Started",
|
|
29773
|
-
"session-stop": "Session Ended",
|
|
29774
|
-
"webhook-test": "Webhook Test"
|
|
29775
|
-
};
|
|
29776
|
-
return labels[event] ?? event;
|
|
29777
|
-
}
|
|
29778
|
-
function formatEventData(event, data) {
|
|
29779
|
-
switch (event) {
|
|
29780
|
-
case "push":
|
|
29781
|
-
case "pull":
|
|
29782
|
-
if (data.resources && typeof data.resources === "object") {
|
|
29783
|
-
const resources = data.resources;
|
|
29784
|
-
const parts = Object.entries(resources).filter(([, count]) => count > 0).map(([type, count]) => `${type}: ${count}`);
|
|
29785
|
-
return parts.length > 0 ? `Resources: ${parts.join(", ")}` : null;
|
|
29786
|
-
}
|
|
29787
|
-
return null;
|
|
29788
|
-
case "skill-use":
|
|
29789
|
-
if (data.skillName && typeof data.skillName === "string") {
|
|
29790
|
-
return `Skill: ${data.skillName}`;
|
|
29791
|
-
}
|
|
29792
|
-
return null;
|
|
29793
|
-
case "session-start":
|
|
29794
|
-
case "session-stop":
|
|
29795
|
-
if (data.sessionId && typeof data.sessionId === "string") {
|
|
29796
|
-
return `Session: ${data.sessionId.slice(0, 8)}...`;
|
|
29797
|
-
}
|
|
29798
|
-
return null;
|
|
29799
|
-
default:
|
|
29800
|
-
return null;
|
|
29801
|
-
}
|
|
29802
|
-
}
|
|
29803
|
-
var init_webhook_formatters = __esm({
|
|
29804
|
-
"src/webhook-formatters.ts"() {
|
|
29805
|
-
"use strict";
|
|
29806
|
-
}
|
|
29807
|
-
});
|
|
29808
|
-
|
|
29809
|
-
// src/webhook.ts
|
|
29810
|
-
var webhook_exports = {};
|
|
29811
|
-
__export(webhook_exports, {
|
|
29812
|
-
listWebhooks: () => listWebhooks,
|
|
29813
|
-
loadWebhookConfig: () => loadWebhookConfig,
|
|
29814
|
-
sendWebhook: () => sendWebhook,
|
|
29815
|
-
testWebhook: () => testWebhook
|
|
29816
|
-
});
|
|
29817
|
-
import { createHmac } from "crypto";
|
|
29818
|
-
async function sendWebhook(event, payload, config) {
|
|
29819
|
-
if (!config) {
|
|
29820
|
-
const { teamConfig } = await autoDetectInit();
|
|
29821
|
-
config = getWebhookSharing(teamConfig);
|
|
29822
|
-
}
|
|
29823
|
-
if (!config.enabled || config.endpoints.length === 0) return;
|
|
29824
|
-
const fullPayload = {
|
|
29825
|
-
event,
|
|
29826
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29827
|
-
tool: payload.tool ?? "unknown",
|
|
29828
|
-
sessionId: payload.sessionId,
|
|
29829
|
-
cwd: payload.cwd,
|
|
29830
|
-
team: payload.team,
|
|
29831
|
-
username: payload.username,
|
|
29832
|
-
data: payload.data ?? {}
|
|
29833
|
-
};
|
|
29834
|
-
const matchingEndpoints = config.endpoints.filter(
|
|
29835
|
-
(ep) => ep.events.includes(event) || ep.events.includes("*")
|
|
29836
|
-
);
|
|
29837
|
-
if (matchingEndpoints.length === 0) return;
|
|
29838
|
-
await Promise.allSettled(
|
|
29839
|
-
matchingEndpoints.map((ep) => sendToEndpoint(ep, fullPayload))
|
|
29840
|
-
);
|
|
29841
|
-
}
|
|
29842
|
-
async function sendToEndpoint(endpoint, payload) {
|
|
29843
|
-
const { url, type, secret, timeout, retries } = endpoint;
|
|
29844
|
-
const body = formatMessage(type, payload);
|
|
29845
|
-
const headers = {
|
|
29846
|
-
"Content-Type": type === "json" ? "application/json" : "text/plain; charset=utf-8"
|
|
29847
|
-
};
|
|
29848
|
-
if (secret) {
|
|
29849
|
-
const signature = createHmac("sha256", secret).update(JSON.stringify(body)).digest("hex");
|
|
29850
|
-
headers["X-TeamAI-Signature"] = `sha256=${signature}`;
|
|
29851
|
-
}
|
|
29852
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
29853
|
-
try {
|
|
29854
|
-
const controller = new AbortController();
|
|
29855
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
29856
|
-
const response = await fetch(url, {
|
|
29857
|
-
method: "POST",
|
|
29858
|
-
headers,
|
|
29859
|
-
body: typeof body === "string" ? body : JSON.stringify(body),
|
|
29860
|
-
signal: controller.signal
|
|
29861
|
-
});
|
|
29862
|
-
clearTimeout(timeoutId);
|
|
29863
|
-
if (response.ok) {
|
|
29864
|
-
log.debug(`Webhook sent successfully to ${url}`);
|
|
29865
|
-
return;
|
|
29866
|
-
}
|
|
29867
|
-
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
29868
|
-
log.warn(`Webhook to ${url} failed with status ${response.status} (not retrying)`);
|
|
29869
|
-
return;
|
|
29870
|
-
}
|
|
29871
|
-
if (attempt < retries) {
|
|
29872
|
-
const delay = Math.pow(2, attempt) * 1e3;
|
|
29873
|
-
log.debug(`Webhook to ${url} failed, retrying in ${delay}ms...`);
|
|
29874
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
29875
|
-
}
|
|
29876
|
-
} catch (error) {
|
|
29877
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
29878
|
-
log.warn(`Webhook to ${url} timed out after ${timeout}ms`);
|
|
29879
|
-
} else if (attempt < retries) {
|
|
29880
|
-
const delay = Math.pow(2, attempt) * 1e3;
|
|
29881
|
-
log.debug(`Webhook to ${url} failed, retrying in ${delay}ms...`);
|
|
29882
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
29883
|
-
} else {
|
|
29884
|
-
log.warn(`Webhook to ${url} failed after ${retries + 1} attempts: ${error.message}`);
|
|
29885
|
-
}
|
|
29886
|
-
}
|
|
29887
|
-
}
|
|
29888
|
-
}
|
|
29889
|
-
function formatMessage(type, payload) {
|
|
29890
|
-
switch (type) {
|
|
29891
|
-
case "feishu":
|
|
29892
|
-
return formatFeishuMessage(payload);
|
|
29893
|
-
case "wecom":
|
|
29894
|
-
return formatWecomMessage(payload);
|
|
29895
|
-
case "json":
|
|
29896
|
-
default:
|
|
29897
|
-
return formatGenericJson(payload);
|
|
29898
|
-
}
|
|
29899
|
-
}
|
|
29900
|
-
async function loadWebhookConfig() {
|
|
29901
|
-
const { teamConfig } = await autoDetectInit();
|
|
29902
|
-
return getWebhookSharing(teamConfig);
|
|
29903
|
-
}
|
|
29904
|
-
async function listWebhooks() {
|
|
29905
|
-
const config = await loadWebhookConfig();
|
|
29906
|
-
return config.endpoints;
|
|
29907
|
-
}
|
|
29908
|
-
async function testWebhook(url) {
|
|
29909
|
-
const config = await loadWebhookConfig();
|
|
29910
|
-
const endpoints = url ? config.endpoints.filter((ep) => ep.url === url) : config.endpoints;
|
|
29911
|
-
if (endpoints.length === 0) {
|
|
29912
|
-
log.warn("No webhook endpoints configured.");
|
|
29913
|
-
return;
|
|
29914
|
-
}
|
|
29915
|
-
const testPayload = {
|
|
29916
|
-
event: "webhook-test",
|
|
29917
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29918
|
-
tool: "teamai-cli",
|
|
29919
|
-
data: {
|
|
29920
|
-
message: "This is a test webhook from TeamAI CLI"
|
|
29921
|
-
}
|
|
29922
|
-
};
|
|
29923
|
-
for (const endpoint of endpoints) {
|
|
29924
|
-
log.info(`Testing webhook to ${endpoint.url}...`);
|
|
29925
|
-
try {
|
|
29926
|
-
await sendToEndpoint(endpoint, testPayload);
|
|
29927
|
-
log.success(`Webhook test successful: ${endpoint.url}`);
|
|
29928
|
-
} catch (error) {
|
|
29929
|
-
log.error(`Webhook test failed: ${endpoint.url} - ${error.message}`);
|
|
29930
|
-
}
|
|
29931
|
-
}
|
|
29932
|
-
}
|
|
29933
|
-
var init_webhook = __esm({
|
|
29934
|
-
"src/webhook.ts"() {
|
|
29935
|
-
"use strict";
|
|
29936
|
-
init_config();
|
|
29937
|
-
init_logger();
|
|
29938
|
-
init_types();
|
|
29939
|
-
init_webhook_formatters();
|
|
29940
|
-
}
|
|
29941
|
-
});
|
|
29942
|
-
|
|
29943
29991
|
// src/session-collector.ts
|
|
29944
29992
|
import fs28 from "fs";
|
|
29945
29993
|
import path86 from "path";
|
|
@@ -33953,6 +34001,24 @@ async function contributeHintAllowed() {
|
|
|
33953
34001
|
return isContributeHintEnabled2({}, {});
|
|
33954
34002
|
}
|
|
33955
34003
|
}
|
|
34004
|
+
async function buildWebhookData(event, stdin) {
|
|
34005
|
+
if (event === "skill-use") {
|
|
34006
|
+
const rawToolName = stdin.tool_name;
|
|
34007
|
+
const toolInput = stdin.tool_input;
|
|
34008
|
+
if (typeof rawToolName !== "string" || !toolInput || typeof toolInput !== "object") return {};
|
|
34009
|
+
const { resolveSkillUse: resolveSkillUse2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
34010
|
+
const resolved = resolveSkillUse2(
|
|
34011
|
+
normalizeToolName(rawToolName),
|
|
34012
|
+
toolInput
|
|
34013
|
+
);
|
|
34014
|
+
return resolved ? { skillName: resolved.skillName } : {};
|
|
34015
|
+
}
|
|
34016
|
+
if (event === "session-start" || event === "session-stop") {
|
|
34017
|
+
const sessionId = deriveSessionId(stdin);
|
|
34018
|
+
return sessionId ? { sessionId } : {};
|
|
34019
|
+
}
|
|
34020
|
+
return {};
|
|
34021
|
+
}
|
|
33956
34022
|
function buildHandlerRegistry() {
|
|
33957
34023
|
return [
|
|
33958
34024
|
// ─── SessionStart ─────────────────────────────────
|
|
@@ -33965,9 +34031,11 @@ function buildHandlerRegistry() {
|
|
|
33965
34031
|
{ event: "session-start", matcher: "*", handler: packageHintHandler, timeoutMs: FOREGROUND_HOOK_TIMEOUT_MS },
|
|
33966
34032
|
{ event: "session-start", matcher: "*", handler: localAgentHandler, timeoutMs: FOREGROUND_HOOK_TIMEOUT_MS },
|
|
33967
34033
|
{ event: "session-start", matcher: "*", handler: webhookHandler, timeoutMs: FOREGROUND_HOOK_TIMEOUT_MS, background: true },
|
|
33968
|
-
// Copilot emits SessionEnd after its final turn
|
|
33969
|
-
//
|
|
34034
|
+
// Copilot emits SessionEnd after its final turn (not Stop), so the webhook
|
|
34035
|
+
// handler must run here too or those sessions emit no session-stop
|
|
34036
|
+
// notification (#702). Detached, mirroring the stop registration.
|
|
33970
34037
|
{ event: "session-end", matcher: "*", handler: dashboardReportHandler, timeoutMs: FOREGROUND_HOOK_TIMEOUT_MS, background: true },
|
|
34038
|
+
{ event: "session-end", matcher: "*", handler: webhookHandler, timeoutMs: FOREGROUND_HOOK_TIMEOUT_MS, background: true },
|
|
33971
34039
|
// ─── Stop ─────────────────────────────────────────
|
|
33972
34040
|
// votes-sync and contribute-check may return a hint the host injects back
|
|
33973
34041
|
// into the session, so they run inline (capped at FOREGROUND_HOOK_TIMEOUT_MS).
|
|
@@ -34001,7 +34069,7 @@ function filterHandlersForConfig(registry, localConfig) {
|
|
|
34001
34069
|
}
|
|
34002
34070
|
return registry;
|
|
34003
34071
|
}
|
|
34004
|
-
var FOREGROUND_HOOK_TIMEOUT_MS, TODOWRITE_HINT_TIMEOUT_MS, UPDATE_TIMEOUT_MS2, LOCAL_AGENT_TIMEOUT_MS, PULL_TIMEOUT_MS, pullHandler, updateHandler, dashboardReportHandler, trackHandler, trackSlashHandler, contributeCheckHandler, pendingHintHandler, packagePendingHintHandler, votesSyncHandler, todowriteHintHandler, mrHintHandler, packageHintHandler, localAgentHandler, webhookHandler;
|
|
34072
|
+
var FOREGROUND_HOOK_TIMEOUT_MS, TODOWRITE_HINT_TIMEOUT_MS, UPDATE_TIMEOUT_MS2, LOCAL_AGENT_TIMEOUT_MS, PULL_TIMEOUT_MS, pullHandler, updateHandler, dashboardReportHandler, trackHandler, trackSlashHandler, contributeCheckHandler, pendingHintHandler, packagePendingHintHandler, votesSyncHandler, todowriteHintHandler, mrHintHandler, packageHintHandler, localAgentHandler, WEBHOOK_EVENT_BY_HOOK, webhookHandler;
|
|
34005
34073
|
var init_hook_handlers = __esm({
|
|
34006
34074
|
"src/hook-handlers.ts"() {
|
|
34007
34075
|
"use strict";
|
|
@@ -34065,29 +34133,20 @@ var init_hook_handlers = __esm({
|
|
|
34065
34133
|
trackHandler = {
|
|
34066
34134
|
name: "track",
|
|
34067
34135
|
async execute(stdin, tool) {
|
|
34068
|
-
const {
|
|
34136
|
+
const { resolveSkillUse: resolveSkillUse2, appendUsageEvent: appendUsageEvent2, updateKnownSkills: updateKnownSkills2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
34069
34137
|
const rawToolName = stdin.tool_name;
|
|
34070
34138
|
if (typeof rawToolName !== "string") return null;
|
|
34071
34139
|
const toolName = normalizeToolName(rawToolName);
|
|
34072
34140
|
const toolInput = stdin.tool_input;
|
|
34073
34141
|
if (!toolInput || typeof toolInput !== "object") return null;
|
|
34074
|
-
|
|
34075
|
-
|
|
34076
|
-
|
|
34077
|
-
skillName
|
|
34078
|
-
|
|
34079
|
-
|
|
34080
|
-
|
|
34081
|
-
|
|
34082
|
-
skillName = extractSkillName2({ skill: filePath });
|
|
34083
|
-
toolSource = "cursor";
|
|
34084
|
-
}
|
|
34085
|
-
} else {
|
|
34086
|
-
return null;
|
|
34087
|
-
}
|
|
34088
|
-
if (!skillName || !isValidSkillName2(skillName)) return null;
|
|
34089
|
-
await appendUsageEvent2({ skill: skillName, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: toolSource });
|
|
34090
|
-
await updateKnownSkills2(skillName);
|
|
34142
|
+
const resolved = resolveSkillUse2(toolName, toolInput);
|
|
34143
|
+
if (!resolved) return null;
|
|
34144
|
+
await appendUsageEvent2({
|
|
34145
|
+
skill: resolved.skillName,
|
|
34146
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34147
|
+
tool: resolved.source ?? tool
|
|
34148
|
+
});
|
|
34149
|
+
await updateKnownSkills2(resolved.skillName);
|
|
34091
34150
|
return null;
|
|
34092
34151
|
}
|
|
34093
34152
|
};
|
|
@@ -34278,6 +34337,12 @@ var init_hook_handlers = __esm({
|
|
|
34278
34337
|
return reportAndSyncFromHook2(stdin, tool);
|
|
34279
34338
|
}
|
|
34280
34339
|
};
|
|
34340
|
+
WEBHOOK_EVENT_BY_HOOK = {
|
|
34341
|
+
sessionstart: "session-start",
|
|
34342
|
+
stop: "session-stop",
|
|
34343
|
+
sessionend: "session-stop",
|
|
34344
|
+
posttooluse: "skill-use"
|
|
34345
|
+
};
|
|
34281
34346
|
webhookHandler = {
|
|
34282
34347
|
name: "webhook-dispatch",
|
|
34283
34348
|
async execute(stdin, tool) {
|
|
@@ -34285,13 +34350,15 @@ var init_hook_handlers = __esm({
|
|
|
34285
34350
|
try {
|
|
34286
34351
|
const config = await loadWebhookConfig2();
|
|
34287
34352
|
if (!config.enabled || config.endpoints.length === 0) return null;
|
|
34288
|
-
const
|
|
34353
|
+
const hookEventName = typeof stdin.hook_event_name === "string" ? stdin.hook_event_name : "";
|
|
34354
|
+
const event = WEBHOOK_EVENT_BY_HOOK[hookEventName.toLowerCase()];
|
|
34355
|
+
if (!event) return null;
|
|
34289
34356
|
const payload = {
|
|
34290
34357
|
tool,
|
|
34291
34358
|
sessionId: deriveSessionId(stdin),
|
|
34292
34359
|
cwd: resolveHookCwd(stdin),
|
|
34293
34360
|
username: typeof stdin.username === "string" ? stdin.username : void 0,
|
|
34294
|
-
data: stdin
|
|
34361
|
+
data: await buildWebhookData(event, stdin)
|
|
34295
34362
|
};
|
|
34296
34363
|
await sendWebhook2(event, payload, config);
|
|
34297
34364
|
} catch (error) {
|
|
@@ -34671,6 +34738,13 @@ async function contribute(options) {
|
|
|
34671
34738
|
log.debug(`contribute: index rebuild skipped: ${e.message}`);
|
|
34672
34739
|
}
|
|
34673
34740
|
const report = await publishQueuedLearnings(localConfig, username);
|
|
34741
|
+
if (report.published.length > 0) {
|
|
34742
|
+
try {
|
|
34743
|
+
await rebuildIndexAfterContribute(localConfig);
|
|
34744
|
+
} catch (e) {
|
|
34745
|
+
log.debug(`contribute: post-publish index rebuild skipped: ${e.message}`);
|
|
34746
|
+
}
|
|
34747
|
+
}
|
|
34674
34748
|
const sessionId = options.sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
34675
34749
|
if (sessionId) {
|
|
34676
34750
|
await markContributed(sessionId);
|
|
@@ -43206,6 +43280,7 @@ async function importFromRepo(opts) {
|
|
|
43206
43280
|
if (!dryRun) {
|
|
43207
43281
|
const cacheWiki = path127.join(cacheDir, "teamwiki");
|
|
43208
43282
|
try {
|
|
43283
|
+
let extractIncremental = incremental;
|
|
43209
43284
|
if (incremental) {
|
|
43210
43285
|
const destIndices = path127.join(teamwikiRoot, ".indices");
|
|
43211
43286
|
const cacheIndices = path127.join(cacheDir, "teamwiki", ".indices");
|
|
@@ -43220,13 +43295,18 @@ async function importFromRepo(opts) {
|
|
|
43220
43295
|
if (await fs48.pathExists(existingManifest)) {
|
|
43221
43296
|
await fs48.copy(existingManifest, path127.join(cacheDir, "teamwiki", "source-manifest.json"));
|
|
43222
43297
|
}
|
|
43298
|
+
const cacheManifest = path127.join(cacheWiki, "source-manifest.json");
|
|
43299
|
+
if (await fs48.pathExists(cacheManifest)) {
|
|
43300
|
+
const manifest = await fs48.readJson(cacheManifest).catch(() => null);
|
|
43301
|
+
if (!lastSync || manifest?.headSha !== lastSync.sha) extractIncremental = false;
|
|
43302
|
+
}
|
|
43223
43303
|
}
|
|
43224
43304
|
await extractCodebase({
|
|
43225
43305
|
path: cacheDir,
|
|
43226
43306
|
project: slug,
|
|
43227
43307
|
json: false,
|
|
43228
43308
|
skipEnrich,
|
|
43229
|
-
incremental,
|
|
43309
|
+
incremental: extractIncremental,
|
|
43230
43310
|
repoUrl: url,
|
|
43231
43311
|
branch: cloneBranch === "HEAD" ? void 0 : cloneBranch,
|
|
43232
43312
|
sourceMrUrl
|
|
@@ -43305,7 +43385,7 @@ ${aiNarrative}`;
|
|
|
43305
43385
|
}
|
|
43306
43386
|
log.info(chalk4.green(`\u2713 teamwiki/ knowledge graph updated: ${slug}`));
|
|
43307
43387
|
} catch (err) {
|
|
43308
|
-
|
|
43388
|
+
throw new Error(`Knowledge extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
43309
43389
|
} finally {
|
|
43310
43390
|
await fs48.remove(cacheWiki).catch(() => {
|
|
43311
43391
|
});
|
|
@@ -45285,7 +45365,9 @@ async function findGitHubComment(owner, repo, prNumber, marker) {
|
|
|
45285
45365
|
`/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100`,
|
|
45286
45366
|
"GET"
|
|
45287
45367
|
);
|
|
45288
|
-
if (!resp.ok)
|
|
45368
|
+
if (!resp.ok) {
|
|
45369
|
+
throw new Error(`GitHub comment list request failed (${resp.status})`);
|
|
45370
|
+
}
|
|
45289
45371
|
const comments = await resp.json();
|
|
45290
45372
|
return comments.find((c) => c.body.includes(marker)) ?? null;
|
|
45291
45373
|
}
|
|
@@ -45341,7 +45423,9 @@ async function findTGitComment(projectId, mrGlobalId, marker) {
|
|
|
45341
45423
|
`/projects/${projectId}/merge_requests/${mrGlobalId}/notes?per_page=100`,
|
|
45342
45424
|
"GET"
|
|
45343
45425
|
);
|
|
45344
|
-
if (!resp.ok)
|
|
45426
|
+
if (!resp.ok) {
|
|
45427
|
+
throw new Error(`TGit note list request failed (${resp.status})`);
|
|
45428
|
+
}
|
|
45345
45429
|
const notes = await resp.json();
|
|
45346
45430
|
return notes.find((n) => n.body.includes(marker)) ?? null;
|
|
45347
45431
|
}
|
|
@@ -46073,6 +46157,13 @@ function registerPackagesCommand(program2, run = async (target, opts) => {
|
|
|
46073
46157
|
var MIGRATION_TRIGGER_COMMANDS = /* @__PURE__ */ new Set(["init", "pull", "push"]);
|
|
46074
46158
|
var require4 = createRequire4(import.meta.url);
|
|
46075
46159
|
var { version } = require4("../package.json");
|
|
46160
|
+
async function notifyWebhook(event) {
|
|
46161
|
+
try {
|
|
46162
|
+
const { sendWebhook: sendWebhook2 } = await Promise.resolve().then(() => (init_webhook(), webhook_exports));
|
|
46163
|
+
await sendWebhook2(event, { tool: "teamai-cli", data: {} });
|
|
46164
|
+
} catch {
|
|
46165
|
+
}
|
|
46166
|
+
}
|
|
46076
46167
|
var program = new Command();
|
|
46077
46168
|
program.name("teamai").description("TeamAI \u2014 Make Every Team AI Native").version(version).option("--dry-run", "Preview mode, no changes made").option("-v, --verbose", "Verbose output").hook("preAction", async (thisCommand, actionCommand) => {
|
|
46078
46169
|
const opts = thisCommand.opts();
|
|
@@ -46097,13 +46188,17 @@ program.command("init").description("Initialize teamai (configure Git provider,
|
|
|
46097
46188
|
program.command("push").description("Push local resources to team repo").option("--all", "Push all without confirmation").option("--skill <path>", "Push a specific skill by path (e.g., ~/.claude/skills/hai/my-skill or skills/hai_dev/my-skill)").option("--role <id>", "Target role namespace for pushed project skills").option("--project <id>", "Target a project: push skills into the project's skills namespace (from manifest/projects.yaml)").action(async (cmdOpts) => {
|
|
46098
46189
|
const globalOpts = program.opts();
|
|
46099
46190
|
const { push: push2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
46100
|
-
|
|
46191
|
+
const outcome = { completed: false };
|
|
46192
|
+
await push2({ ...globalOpts, ...cmdOpts }, outcome);
|
|
46193
|
+
if (outcome.completed) await notifyWebhook("push");
|
|
46101
46194
|
});
|
|
46102
46195
|
program.command("pull").description("Pull team resources and inject into local AI tools").option("--silent", "Silent mode (for hooks)").option("--force", "Force full sync even if repo is unchanged").action(async (cmdOpts) => {
|
|
46103
46196
|
const globalOpts = program.opts();
|
|
46104
46197
|
if (cmdOpts.silent) setSilent(true);
|
|
46105
46198
|
const { pull: pull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
|
|
46106
|
-
|
|
46199
|
+
const outcome = { completed: false };
|
|
46200
|
+
await pull2({ ...globalOpts, ...cmdOpts, interactive: !cmdOpts.silent }, outcome);
|
|
46201
|
+
if (!cmdOpts.silent && outcome.completed) await notifyWebhook("pull");
|
|
46107
46202
|
});
|
|
46108
46203
|
program.command("status").description("Show local vs team repo diff").option("--all", "List every project data partition under ~/.teamai/projects (flags stale/orphan ones)").action(async (cmdOpts) => {
|
|
46109
46204
|
const globalOpts = program.opts();
|