@team-ai/teamai-cli 0.30.1 → 0.30.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +140 -25
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -61820,6 +61820,39 @@ function mergeWorkspaceBindings(existing2, incoming, canonicalKey) {
61820
61820
  if (!base.ideType) base.ideType = existing2.ideType ?? incoming.ideType;
61821
61821
  return base;
61822
61822
  }
61823
+ function workspaceEnrollmentsOf(config) {
61824
+ return config.workspaceEnrollments ??= {};
61825
+ }
61826
+ async function inferWorkspaceEnrollments(config) {
61827
+ const inferred = {};
61828
+ const add = async (workspacePath, enrolledAt = config.createdAt, ideType) => {
61829
+ const canonical2 = await canonicalizeWorkspacePath(workspacePath);
61830
+ inferred[canonical2] ??= { enrolledAt, ...ideType ? { ideType } : {} };
61831
+ };
61832
+ for (const [workspacePath, binding] of Object.entries(config.workspaceBindings)) {
61833
+ if (binding.projectId > 0) await add(workspacePath, binding.boundAt, binding.ideType);
61834
+ }
61835
+ const manifest = await readJson(getManifestPath());
61836
+ for (const key of Object.keys(manifest?.scopes ?? {})) {
61837
+ const parsed = parseScopeKey(key);
61838
+ if (parsed.scope === "project" && parsed.workspacePath) await add(parsed.workspacePath);
61839
+ }
61840
+ const models = await readJson(getModelManifestPath());
61841
+ for (const workspacePath of Object.keys(models?.workspaceModels ?? {})) await add(workspacePath);
61842
+ const projectHomes = await listDirs(path76.join(getTeamaiHomePath(), "projects")).catch(() => []);
61843
+ const mcpOwnershipFiles = [
61844
+ path76.join(getTeamaiHomePath(), "managed-local-mcp.json"),
61845
+ ...projectHomes.map((dir) => path76.join(getTeamaiHomePath(), "projects", dir, "managed-local-mcp.json"))
61846
+ ];
61847
+ for (const file of mcpOwnershipFiles) {
61848
+ const records2 = await readJson(file);
61849
+ for (const [key, owned] of Object.entries(records2 ?? {})) {
61850
+ const match = /^[^:]+:local:(.+)$/.exec(key);
61851
+ if (match?.[1] && owned.length > 0) await add(match[1]);
61852
+ }
61853
+ }
61854
+ return inferred;
61855
+ }
61823
61856
  async function loadLocalAgentConfig(options2 = {}) {
61824
61857
  const fileConfig = await readJson(getConfigPath());
61825
61858
  if (fileConfig && "disabled" in fileConfig && fileConfig.disabled === true) return null;
@@ -61827,8 +61860,14 @@ async function loadLocalAgentConfig(options2 = {}) {
61827
61860
  const config = {
61828
61861
  ...fileConfig,
61829
61862
  endpoint: normalizeEndpoint(fileConfig.endpoint),
61863
+ workspaceEnrollments: fileConfig.workspaceEnrollments ?? {},
61830
61864
  workspaceBindings: fileConfig.workspaceBindings ?? {}
61831
61865
  };
61866
+ let enrollmentMigrationChanged = false;
61867
+ if (fileConfig.workspaceEnrollments === void 0) {
61868
+ config.workspaceEnrollments = await inferWorkspaceEnrollments(config);
61869
+ enrollmentMigrationChanged = true;
61870
+ }
61832
61871
  const removedLegacyPaths = [];
61833
61872
  for (const [wsPath, binding] of Object.entries(config.workspaceBindings)) {
61834
61873
  if ("groupId" in binding && !("projectId" in binding)) {
@@ -61857,7 +61896,14 @@ async function loadLocalAgentConfig(options2 = {}) {
61857
61896
  migrated[canonicalKey] = mergeWorkspaceBindings(migrated[canonicalKey], binding, canonicalKey);
61858
61897
  }
61859
61898
  config.workspaceBindings = migrated;
61860
- if (migrationChanged && !options2.dryRun) {
61899
+ const migratedEnrollments = {};
61900
+ for (const [wsPath, enrollment] of Object.entries(config.workspaceEnrollments)) {
61901
+ const canonicalKey = await canonicalizeWorkspacePath(wsPath);
61902
+ if (canonicalKey !== wsPath || migratedEnrollments[canonicalKey]) migrationChanged = true;
61903
+ migratedEnrollments[canonicalKey] ??= enrollment;
61904
+ }
61905
+ config.workspaceEnrollments = migratedEnrollments;
61906
+ if ((migrationChanged || enrollmentMigrationChanged) && !options2.dryRun) {
61861
61907
  await saveLocalAgentConfig(config);
61862
61908
  }
61863
61909
  return config;
@@ -61872,6 +61918,7 @@ async function loadLocalAgentConfig(options2 = {}) {
61872
61918
  endpoint,
61873
61919
  token,
61874
61920
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
61921
+ workspaceEnrollments: {},
61875
61922
  workspaceBindings: {}
61876
61923
  };
61877
61924
  if (options2.dryRun) return backfilled;
@@ -61889,6 +61936,7 @@ async function loadLocalAgentConfig(options2 = {}) {
61889
61936
  endpoint: normalizeEndpoint(envEndpoint),
61890
61937
  token: process.env.TEAMAI_API_TOKEN ?? process.env.TEAMAI_TOKEN,
61891
61938
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
61939
+ workspaceEnrollments: {},
61892
61940
  workspaceBindings: {}
61893
61941
  };
61894
61942
  }
@@ -61896,6 +61944,7 @@ async function saveLocalAgentConfig(config) {
61896
61944
  await writeJsonAtomic(getConfigPath(), {
61897
61945
  ...config,
61898
61946
  endpoint: normalizeEndpoint(config.endpoint),
61947
+ workspaceEnrollments: config.workspaceEnrollments ?? {},
61899
61948
  workspaceBindings: config.workspaceBindings ?? {}
61900
61949
  });
61901
61950
  }
@@ -62302,6 +62351,10 @@ async function persistWorkspaceBinding(config, cwd, resolvedPath, projectId, pro
62302
62351
  }
62303
62352
  await saveLocalAgentConfig(config);
62304
62353
  }
62354
+ async function enrollWorkspace(config, resolvedPath) {
62355
+ const enrollments = workspaceEnrollmentsOf(config);
62356
+ enrollments[resolvedPath] ??= { enrolledAt: (/* @__PURE__ */ new Date()).toISOString() };
62357
+ }
62305
62358
  async function inheritWorktreeBinding(config, cwd, resolvedPath) {
62306
62359
  const anchors = await resolveAnchors(cwd);
62307
62360
  if (!anchors || anchors.projectAnchor === anchors.workspaceRoot) return false;
@@ -62321,6 +62374,11 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
62321
62374
  if (!config) {
62322
62375
  throw new Error("HTTP local agent is not initialized. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` first.");
62323
62376
  }
62377
+ if (!workspaceEnrollmentsOf(config)[workspacePath]) {
62378
+ throw new Error(
62379
+ "Current workspace is not enrolled. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` here first."
62380
+ );
62381
+ }
62324
62382
  const projects = await fetchUserProjects(config);
62325
62383
  const project = projectId ? projects.find((item) => item.id === projectId) : await promptForProjectBinding(workspacePath, projects);
62326
62384
  if (!project) return null;
@@ -62334,6 +62392,7 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
62334
62392
  return binding;
62335
62393
  }
62336
62394
  async function ensureWorkspaceBinding(config, workspacePath, sessionId, cwd) {
62395
+ if (!workspaceEnrollmentsOf(config)[workspacePath]) return;
62337
62396
  if (config.workspaceBindings[workspacePath]) return;
62338
62397
  if (await inheritWorktreeBinding(config, cwd, workspacePath)) return;
62339
62398
  const markerKey = sessionId || `ppid-${process.ppid}`;
@@ -62382,6 +62441,7 @@ function isBindPromptTool(tool) {
62382
62441
  return kind === "codebuddy" || kind === "workbuddy";
62383
62442
  }
62384
62443
  async function emitBindingHint(config, workspacePath, sessionId, cwd) {
62444
+ if (!workspaceEnrollmentsOf(config)[workspacePath]) return;
62385
62445
  if (config.workspaceBindings[workspacePath]) return;
62386
62446
  if (await inheritWorktreeBinding(config, cwd, workspacePath)) return;
62387
62447
  const markerKey = sessionId || `ppid-${process.ppid}`;
@@ -62581,9 +62641,14 @@ async function scanModelsFromDisk(tool, workspacePath) {
62581
62641
  }
62582
62642
  async function pruneDeadWorkspaceBindings(config) {
62583
62643
  let changed = false;
62584
- for (const workspacePath of Object.keys(config.workspaceBindings)) {
62644
+ const allPaths = /* @__PURE__ */ new Set([
62645
+ ...Object.keys(config.workspaceBindings),
62646
+ ...Object.keys(workspaceEnrollmentsOf(config))
62647
+ ]);
62648
+ for (const workspacePath of allPaths) {
62585
62649
  if (await isUserHomeDir(workspacePath)) {
62586
62650
  delete config.workspaceBindings[workspacePath];
62651
+ delete workspaceEnrollmentsOf(config)[workspacePath];
62587
62652
  changed = true;
62588
62653
  continue;
62589
62654
  }
@@ -62592,6 +62657,7 @@ async function pruneDeadWorkspaceBindings(config) {
62592
62657
  } catch (error2) {
62593
62658
  if (error2.code === "ENOENT") {
62594
62659
  delete config.workspaceBindings[workspacePath];
62660
+ delete workspaceEnrollmentsOf(config)[workspacePath];
62595
62661
  changed = true;
62596
62662
  } else {
62597
62663
  log.debug(
@@ -62604,19 +62670,26 @@ async function pruneDeadWorkspaceBindings(config) {
62604
62670
  }
62605
62671
  function stampWorkspaceTool(config, currentPath, tool) {
62606
62672
  if (!currentPath) return false;
62673
+ const enrollment = workspaceEnrollmentsOf(config)[currentPath];
62674
+ if (!enrollment) return false;
62607
62675
  const binding = config.workspaceBindings[currentPath];
62608
- if (!binding) return false;
62609
62676
  const normalized = normalizeAgentType(tool);
62610
- if (binding.ideType === normalized) return false;
62611
- binding.ideType = normalized;
62612
- return true;
62677
+ let changed = false;
62678
+ if (enrollment.ideType !== normalized) {
62679
+ enrollment.ideType = normalized;
62680
+ changed = true;
62681
+ }
62682
+ if (binding && binding.ideType !== normalized) {
62683
+ binding.ideType = normalized;
62684
+ changed = true;
62685
+ }
62686
+ return changed;
62613
62687
  }
62614
62688
  function selectToolWorkspaces(config, currentPath, currentTool) {
62615
- const paths = new Set(Object.keys(config.workspaceBindings));
62616
- if (currentPath) paths.add(currentPath);
62617
- return Array.from(paths).filter((wsPath) => {
62689
+ return Object.keys(workspaceEnrollmentsOf(config)).filter((wsPath) => {
62690
+ const enrollment = workspaceEnrollmentsOf(config)[wsPath];
62618
62691
  const b2 = config.workspaceBindings[wsPath];
62619
- const wsTool = (b2?.ideType || void 0) ?? (wsPath === currentPath ? currentTool : void 0);
62692
+ const wsTool = enrollment.ideType ?? b2?.ideType ?? (wsPath === currentPath ? currentTool : void 0);
62620
62693
  return wsTool === currentTool;
62621
62694
  });
62622
62695
  }
@@ -62676,9 +62749,9 @@ async function buildReportPayload(config, context) {
62676
62749
  const workspace = {
62677
62750
  path: wsPath,
62678
62751
  name: path76.basename(wsPath),
62679
- ide_type: currentTool,
62680
- project_id: wsBinding?.projectId
62752
+ ide_type: currentTool
62681
62753
  };
62754
+ if (wsBinding && wsBinding.projectId > 0) workspace.project_id = wsBinding.projectId;
62682
62755
  if (wsScope.skills.length > 0) workspace.skills = wsScope.skills;
62683
62756
  if (wsScope.rules.length > 0) workspace.rules = wsScope.rules;
62684
62757
  const wsMcps = await scanMcpFromManifest("project", tool, wsPath);
@@ -62707,7 +62780,7 @@ async function buildSyncPayload(config, context) {
62707
62780
  path: wsPath,
62708
62781
  name: path76.basename(wsPath),
62709
62782
  ide_type: currentTool,
62710
- project_id: wsBinding?.projectId
62783
+ ...wsBinding && wsBinding.projectId > 0 ? { project_id: wsBinding.projectId } : {}
62711
62784
  };
62712
62785
  });
62713
62786
  }
@@ -63779,9 +63852,9 @@ async function applyModelConfig(config, command, context) {
63779
63852
  if (scope === "project" && !workspacePath) {
63780
63853
  throw new Error("apply_model_config: workspace command is missing workspace_path");
63781
63854
  }
63782
- if (workspacePath && config.workspaceBindings[workspacePath] === void 0) {
63855
+ if (workspacePath && workspaceEnrollmentsOf(config)[workspacePath] === void 0) {
63783
63856
  throw new Error(
63784
- `apply_model_config: workspace "${path76.basename(workspacePath)}" is not a registered binding`
63857
+ `apply_model_config: workspace "${path76.basename(workspacePath)}" is not enrolled`
63785
63858
  );
63786
63859
  }
63787
63860
  if (agentKind === "claude" && workspacePath) {
@@ -64301,6 +64374,24 @@ async function runMcpCommand(config, command, context) {
64301
64374
  return command.version;
64302
64375
  }
64303
64376
  async function executeCommand(config, command, context) {
64377
+ if (normalizeScope(command.scope) === "project") {
64378
+ const workspacePath2 = await resolveWorkspacePath(command.workspace_path ?? context.cwd);
64379
+ if (!workspacePath2) throw new Error(`${command.type ?? "workspace command"} is missing workspace_path`);
64380
+ if (!workspaceEnrollmentsOf(config)[workspacePath2]) {
64381
+ throw new Error(`Workspace is not enrolled for TeamAI management: ${workspacePath2}`);
64382
+ }
64383
+ if (command.project_id !== void 0) {
64384
+ if (!Number.isInteger(command.project_id) || command.project_id <= 0) {
64385
+ throw new Error(`Workspace command has invalid project_id: ${command.project_id}`);
64386
+ }
64387
+ const binding = config.workspaceBindings[workspacePath2];
64388
+ if (!binding || binding.projectId !== command.project_id) {
64389
+ throw new Error(
64390
+ `Workspace project binding does not match command project_id ${command.project_id}: ${workspacePath2}`
64391
+ );
64392
+ }
64393
+ }
64394
+ }
64304
64395
  if (command.type === "apply_model_config") {
64305
64396
  await applyModelConfig(config, command, context);
64306
64397
  return;
@@ -64379,9 +64470,9 @@ async function reportAndSyncLocalAgent(context) {
64379
64470
  async function syncLocalAgent(context) {
64380
64471
  const config = await loadLocalAgentConfig();
64381
64472
  if (!config) return false;
64473
+ const workspacePath = await resolveWorkspacePath(context.cwd);
64382
64474
  if (isBindPromptEnabled() && isBindPromptTool(context.tool)) {
64383
- const workspacePath = await resolveWorkspacePath(context.cwd);
64384
- if (workspacePath) {
64475
+ if (workspacePath && workspaceEnrollmentsOf(config)[workspacePath]) {
64385
64476
  const sid = context.event?.sessionId;
64386
64477
  if (context.event?.type === "session_start") {
64387
64478
  await ensureWorkspaceBinding(config, workspacePath, sid, context.cwd);
@@ -64404,8 +64495,7 @@ async function syncLocalAgent(context) {
64404
64495
  await maybeReconcilePlugins(context);
64405
64496
  }
64406
64497
  const pruned = await pruneDeadWorkspaceBindings(config);
64407
- const currentPath = await resolveWorkspacePath(context.cwd);
64408
- const stamped = stampWorkspaceTool(config, currentPath, context.tool ?? "workbuddy");
64498
+ const stamped = stampWorkspaceTool(config, workspacePath, context.tool ?? "workbuddy");
64409
64499
  if (pruned || stamped) {
64410
64500
  await saveLocalAgentConfig(config);
64411
64501
  }
@@ -64577,17 +64667,20 @@ async function initLocalAgentHttp(options2) {
64577
64667
  throw new Error("HTTP endpoint is required.");
64578
64668
  }
64579
64669
  const existing2 = await loadLocalAgentConfig();
64580
- if (existing2 && !options2.force) {
64581
- throw new Error("HTTP local agent is already initialized. Re-run with --force to overwrite.");
64670
+ if (existing2 && !options2.force && normalizeEndpoint(existing2.endpoint) !== endpoint) {
64671
+ throw new Error("HTTP local agent is initialized with another endpoint. Re-run with --force to overwrite.");
64582
64672
  }
64583
64673
  const config = {
64584
64674
  endpoint,
64585
- token: options2.token,
64675
+ token: options2.token ?? existing2?.token,
64586
64676
  createdAt: existing2?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
64677
+ workspaceEnrollments: existing2?.workspaceEnrollments ?? {},
64587
64678
  workspaceBindings: existing2?.workspaceBindings ?? {},
64588
64679
  userGroupId: existing2?.userGroupId,
64589
64680
  userGroupName: existing2?.userGroupName
64590
64681
  };
64682
+ const workspacePath = await resolveWorkspacePath(options2.workspacePath ?? process.cwd());
64683
+ if (workspacePath) await enrollWorkspace(config, workspacePath);
64591
64684
  await ensureDir(getLocalAgentHome());
64592
64685
  await saveLocalAgentConfig(config);
64593
64686
  if (options2.token) {
@@ -64829,7 +64922,18 @@ async function bindCurrentProject(options2) {
64829
64922
  if (!config) {
64830
64923
  throw new Error("Local agent not initialized. Run `teamai init --http` first.");
64831
64924
  }
64832
- await persistWorkspaceBinding(config, options2?.cwd ?? process.cwd(), workspacePath, 0, "__skipped__");
64925
+ if (!workspaceEnrollmentsOf(config)[workspacePath]) {
64926
+ throw new Error(
64927
+ "Current workspace is not enrolled. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` here first."
64928
+ );
64929
+ }
64930
+ await persistWorkspaceBinding(
64931
+ config,
64932
+ options2?.cwd ?? process.cwd(),
64933
+ workspacePath,
64934
+ 0,
64935
+ "__skipped__"
64936
+ );
64833
64937
  log.info(`\u5DF2\u8DF3\u8FC7\u7ED1\u5B9A\uFF0C\u4EE5\u540E\u4E0D\u518D\u63D0\u793A\u6B64\u5DE5\u4F5C\u533A\u3002`);
64834
64938
  return;
64835
64939
  }
@@ -65995,7 +66099,13 @@ async function initHttp(url, options2) {
65995
66099
  await reconcileHooksForInit(teamConfig, localConfig2, filterAgents2);
65996
66100
  const { initLocalAgentHttp: initLocalAgentHttp2 } = await Promise.resolve().then(() => (init_local_agent(), local_agent_exports));
65997
66101
  try {
65998
- await initLocalAgentHttp2({ endpoint: url, token: options2.token, force: options2.force, filterAgents: filterAgents2 });
66102
+ await initLocalAgentHttp2({
66103
+ endpoint: url,
66104
+ token: options2.token,
66105
+ force: options2.force,
66106
+ filterAgents: filterAgents2,
66107
+ workspacePath: projectRoot ?? process.cwd()
66108
+ });
65999
66109
  } catch (e) {
66000
66110
  log.debug(`Local agent init: ${e.message}`);
66001
66111
  }
@@ -106694,7 +106804,12 @@ async function sourceAddHttp(endpoint, options2) {
106694
106804
  return;
106695
106805
  }
106696
106806
  const { initLocalAgentHttp: initLocalAgentHttp2 } = await Promise.resolve().then(() => (init_local_agent(), local_agent_exports));
106697
- await initLocalAgentHttp2({ endpoint: trimmed2, token: options2.token, force: true });
106807
+ await initLocalAgentHttp2({
106808
+ endpoint: trimmed2,
106809
+ token: options2.token,
106810
+ force: true,
106811
+ workspacePath: mainConfig?.projectRoot ?? process.cwd()
106812
+ });
106698
106813
  log.success(`HTTP source added (${trimmed2}).`);
106699
106814
  log.info("It will report/sync on the next AI session (via the hook-dispatch hook already installed).");
106700
106815
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-ai/teamai-cli",
3
- "version": "0.30.1",
3
+ "version": "0.30.2",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {