@team-ai/teamai-cli 0.30.2 → 0.30.3

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 +95 -65
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -61809,19 +61809,26 @@ async function canonicalizeWorkspacePath(value) {
61809
61809
  }
61810
61810
  function mergeWorkspaceBindings(existing2, incoming, canonicalKey) {
61811
61811
  if (!existing2) return incoming;
61812
- const existingReal = existing2.projectId !== 0;
61813
- const incomingReal = incoming.projectId !== 0;
61812
+ const rank2 = (binding) => (binding.projectId ?? 0) > 0 ? 2 : binding.projectId === 0 ? 1 : 0;
61813
+ const existingRank = rank2(existing2);
61814
+ const incomingRank = rank2(incoming);
61815
+ const existingReal = existingRank === 2;
61816
+ const incomingReal = incomingRank === 2;
61814
61817
  if (existingReal && incomingReal && existing2.projectId !== incoming.projectId) {
61815
61818
  log.warn(
61816
61819
  `local-agent: workspace ${canonicalKey} has conflicting project bindings (${existing2.projectId} vs ${incoming.projectId}); keeping ${existing2.projectId}`
61817
61820
  );
61818
61821
  }
61819
- const base = existingReal || !incomingReal ? { ...existing2 } : { ...incoming };
61822
+ const base = existingRank >= incomingRank ? { ...existing2 } : { ...incoming };
61823
+ base.enrolledAt ??= existing2.enrolledAt ?? incoming.enrolledAt;
61820
61824
  if (!base.ideType) base.ideType = existing2.ideType ?? incoming.ideType;
61821
61825
  return base;
61822
61826
  }
61823
- function workspaceEnrollmentsOf(config) {
61824
- return config.workspaceEnrollments ??= {};
61827
+ function isWorkspaceEnrolled(config, workspacePath) {
61828
+ return config.workspaceBindings[workspacePath]?.enrolledAt !== void 0;
61829
+ }
61830
+ function hasWorkspaceBindingDecision(binding) {
61831
+ return binding?.projectId !== void 0;
61825
61832
  }
61826
61833
  async function inferWorkspaceEnrollments(config) {
61827
61834
  const inferred = {};
@@ -61829,8 +61836,43 @@ async function inferWorkspaceEnrollments(config) {
61829
61836
  const canonical2 = await canonicalizeWorkspacePath(workspacePath);
61830
61837
  inferred[canonical2] ??= { enrolledAt, ...ideType ? { ideType } : {} };
61831
61838
  };
61839
+ const inheritedAnchors = /* @__PURE__ */ new Set();
61840
+ const missingBindings = /* @__PURE__ */ new Set();
61841
+ const anchorsByPath = /* @__PURE__ */ new Map();
61842
+ const positiveBindings = Object.entries(config.workspaceBindings).filter(([, binding]) => (binding.projectId ?? 0) > 0);
61843
+ for (const [workspacePath] of positiveBindings) {
61844
+ try {
61845
+ await fs35.promises.stat(workspacePath);
61846
+ } catch (error2) {
61847
+ if (error2.code === "ENOENT") missingBindings.add(workspacePath);
61848
+ continue;
61849
+ }
61850
+ const anchors = await resolveAnchors(workspacePath);
61851
+ anchorsByPath.set(workspacePath, anchors);
61852
+ if (anchors && anchors.projectAnchor !== anchors.workspaceRoot) {
61853
+ inheritedAnchors.add(await canonicalizeWorkspacePath(anchors.projectAnchor));
61854
+ }
61855
+ }
61856
+ const bindingTransactions = /* @__PURE__ */ new Map();
61857
+ for (const [workspacePath, binding] of positiveBindings) {
61858
+ if (!binding.boundAt) continue;
61859
+ const key = `${binding.projectId}\0${binding.boundAt}`;
61860
+ const paths = bindingTransactions.get(key) ?? [];
61861
+ paths.push(workspacePath);
61862
+ bindingTransactions.set(key, paths);
61863
+ }
61864
+ for (const paths of bindingTransactions.values()) {
61865
+ if (!paths.some((workspacePath) => missingBindings.has(workspacePath))) continue;
61866
+ for (const workspacePath of paths) {
61867
+ if (missingBindings.has(workspacePath)) continue;
61868
+ const anchors = anchorsByPath.get(workspacePath);
61869
+ if (anchors && anchors.projectAnchor === anchors.workspaceRoot) inheritedAnchors.add(workspacePath);
61870
+ }
61871
+ }
61832
61872
  for (const [workspacePath, binding] of Object.entries(config.workspaceBindings)) {
61833
- if (binding.projectId > 0) await add(workspacePath, binding.boundAt, binding.ideType);
61873
+ if ((binding.projectId ?? 0) > 0 && !missingBindings.has(workspacePath) && !inheritedAnchors.has(workspacePath)) {
61874
+ await add(workspacePath, binding.boundAt ?? config.createdAt, binding.ideType);
61875
+ }
61834
61876
  }
61835
61877
  const manifest = await readJson(getManifestPath());
61836
61878
  for (const key of Object.keys(manifest?.scopes ?? {})) {
@@ -61857,17 +61899,12 @@ async function loadLocalAgentConfig(options2 = {}) {
61857
61899
  const fileConfig = await readJson(getConfigPath());
61858
61900
  if (fileConfig && "disabled" in fileConfig && fileConfig.disabled === true) return null;
61859
61901
  if (fileConfig && "endpoint" in fileConfig && fileConfig.endpoint) {
61902
+ const { workspaceEnrollments: legacyEnrollments, ...storedConfig } = fileConfig;
61860
61903
  const config = {
61861
- ...fileConfig,
61904
+ ...storedConfig,
61862
61905
  endpoint: normalizeEndpoint(fileConfig.endpoint),
61863
- workspaceEnrollments: fileConfig.workspaceEnrollments ?? {},
61864
61906
  workspaceBindings: fileConfig.workspaceBindings ?? {}
61865
61907
  };
61866
- let enrollmentMigrationChanged = false;
61867
- if (fileConfig.workspaceEnrollments === void 0) {
61868
- config.workspaceEnrollments = await inferWorkspaceEnrollments(config);
61869
- enrollmentMigrationChanged = true;
61870
- }
61871
61908
  const removedLegacyPaths = [];
61872
61909
  for (const [wsPath, binding] of Object.entries(config.workspaceBindings)) {
61873
61910
  if ("groupId" in binding && !("projectId" in binding)) {
@@ -61875,20 +61912,15 @@ async function loadLocalAgentConfig(options2 = {}) {
61875
61912
  removedLegacyPaths.push(wsPath);
61876
61913
  }
61877
61914
  }
61915
+ let migrationChanged = removedLegacyPaths.length > 0;
61878
61916
  if (removedLegacyPaths.length > 0 && options2.dryRun) {
61879
61917
  log.info(`[dry-run] Would remove ${removedLegacyPaths.length} legacy group-based workspace binding(s).`);
61880
61918
  } else if (removedLegacyPaths.length > 0) {
61881
61919
  log.warn(
61882
61920
  `Removed ${removedLegacyPaths.length} legacy group-based workspace binding(s); you will be prompted to re-bind on the next session.`
61883
61921
  );
61884
- try {
61885
- await saveLocalAgentConfig(config);
61886
- } catch (e) {
61887
- log.debug(`local-agent: failed to persist binding cleanup: ${e.message}`);
61888
- }
61889
61922
  }
61890
61923
  const migrated = {};
61891
- let migrationChanged = false;
61892
61924
  for (const [wsPath, binding] of Object.entries(config.workspaceBindings)) {
61893
61925
  const canonicalKey = await canonicalizeWorkspacePath(wsPath);
61894
61926
  if (canonicalKey !== wsPath) migrationChanged = true;
@@ -61896,14 +61928,21 @@ async function loadLocalAgentConfig(options2 = {}) {
61896
61928
  migrated[canonicalKey] = mergeWorkspaceBindings(migrated[canonicalKey], binding, canonicalKey);
61897
61929
  }
61898
61930
  config.workspaceBindings = migrated;
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;
61931
+ if (fileConfig.workspaceStateVersion !== 1) {
61932
+ const enrollments = legacyEnrollments ?? await inferWorkspaceEnrollments(config);
61933
+ for (const [wsPath, enrollment] of Object.entries(enrollments)) {
61934
+ const canonicalKey = await canonicalizeWorkspacePath(wsPath);
61935
+ const binding = config.workspaceBindings[canonicalKey] ?? {};
61936
+ config.workspaceBindings[canonicalKey] = {
61937
+ ...binding,
61938
+ enrolledAt: binding.enrolledAt ?? enrollment.enrolledAt,
61939
+ ideType: enrollment.ideType ?? binding.ideType
61940
+ };
61941
+ }
61942
+ config.workspaceStateVersion = 1;
61943
+ migrationChanged = true;
61904
61944
  }
61905
- config.workspaceEnrollments = migratedEnrollments;
61906
- if ((migrationChanged || enrollmentMigrationChanged) && !options2.dryRun) {
61945
+ if (migrationChanged && !options2.dryRun) {
61907
61946
  await saveLocalAgentConfig(config);
61908
61947
  }
61909
61948
  return config;
@@ -61918,7 +61957,7 @@ async function loadLocalAgentConfig(options2 = {}) {
61918
61957
  endpoint,
61919
61958
  token,
61920
61959
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
61921
- workspaceEnrollments: {},
61960
+ workspaceStateVersion: 1,
61922
61961
  workspaceBindings: {}
61923
61962
  };
61924
61963
  if (options2.dryRun) return backfilled;
@@ -61936,7 +61975,7 @@ async function loadLocalAgentConfig(options2 = {}) {
61936
61975
  endpoint: normalizeEndpoint(envEndpoint),
61937
61976
  token: process.env.TEAMAI_API_TOKEN ?? process.env.TEAMAI_TOKEN,
61938
61977
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
61939
- workspaceEnrollments: {},
61978
+ workspaceStateVersion: 1,
61940
61979
  workspaceBindings: {}
61941
61980
  };
61942
61981
  }
@@ -61944,7 +61983,7 @@ async function saveLocalAgentConfig(config) {
61944
61983
  await writeJsonAtomic(getConfigPath(), {
61945
61984
  ...config,
61946
61985
  endpoint: normalizeEndpoint(config.endpoint),
61947
- workspaceEnrollments: config.workspaceEnrollments ?? {},
61986
+ workspaceStateVersion: 1,
61948
61987
  workspaceBindings: config.workspaceBindings ?? {}
61949
61988
  });
61950
61989
  }
@@ -62352,14 +62391,17 @@ async function persistWorkspaceBinding(config, cwd, resolvedPath, projectId, pro
62352
62391
  await saveLocalAgentConfig(config);
62353
62392
  }
62354
62393
  async function enrollWorkspace(config, resolvedPath) {
62355
- const enrollments = workspaceEnrollmentsOf(config);
62356
- enrollments[resolvedPath] ??= { enrolledAt: (/* @__PURE__ */ new Date()).toISOString() };
62394
+ const binding = config.workspaceBindings[resolvedPath] ?? {};
62395
+ config.workspaceBindings[resolvedPath] = {
62396
+ ...binding,
62397
+ enrolledAt: binding.enrolledAt ?? (/* @__PURE__ */ new Date()).toISOString()
62398
+ };
62357
62399
  }
62358
62400
  async function inheritWorktreeBinding(config, cwd, resolvedPath) {
62359
62401
  const anchors = await resolveAnchors(cwd);
62360
62402
  if (!anchors || anchors.projectAnchor === anchors.workspaceRoot) return false;
62361
62403
  const anchorBinding = config.workspaceBindings[anchors.projectAnchor];
62362
- if (!anchorBinding) return false;
62404
+ if (!hasWorkspaceBindingDecision(anchorBinding)) return false;
62363
62405
  config.workspaceBindings[resolvedPath] = {
62364
62406
  ...config.workspaceBindings[resolvedPath],
62365
62407
  projectId: anchorBinding.projectId,
@@ -62374,7 +62416,7 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
62374
62416
  if (!config) {
62375
62417
  throw new Error("HTTP local agent is not initialized. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` first.");
62376
62418
  }
62377
- if (!workspaceEnrollmentsOf(config)[workspacePath]) {
62419
+ if (!isWorkspaceEnrolled(config, workspacePath)) {
62378
62420
  throw new Error(
62379
62421
  "Current workspace is not enrolled. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` here first."
62380
62422
  );
@@ -62392,8 +62434,8 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
62392
62434
  return binding;
62393
62435
  }
62394
62436
  async function ensureWorkspaceBinding(config, workspacePath, sessionId, cwd) {
62395
- if (!workspaceEnrollmentsOf(config)[workspacePath]) return;
62396
- if (config.workspaceBindings[workspacePath]) return;
62437
+ if (!isWorkspaceEnrolled(config, workspacePath)) return;
62438
+ if (hasWorkspaceBindingDecision(config.workspaceBindings[workspacePath])) return;
62397
62439
  if (await inheritWorktreeBinding(config, cwd, workspacePath)) return;
62398
62440
  const markerKey = sessionId || `ppid-${process.ppid}`;
62399
62441
  const hintMarker = path76.join(os9.tmpdir(), `teamai-bind-session-${markerKey}`);
@@ -62441,8 +62483,8 @@ function isBindPromptTool(tool) {
62441
62483
  return kind === "codebuddy" || kind === "workbuddy";
62442
62484
  }
62443
62485
  async function emitBindingHint(config, workspacePath, sessionId, cwd) {
62444
- if (!workspaceEnrollmentsOf(config)[workspacePath]) return;
62445
- if (config.workspaceBindings[workspacePath]) return;
62486
+ if (!isWorkspaceEnrolled(config, workspacePath)) return;
62487
+ if (hasWorkspaceBindingDecision(config.workspaceBindings[workspacePath])) return;
62446
62488
  if (await inheritWorktreeBinding(config, cwd, workspacePath)) return;
62447
62489
  const markerKey = sessionId || `ppid-${process.ppid}`;
62448
62490
  const hintMarker = path76.join(os9.tmpdir(), `teamai-bind-hint-${markerKey}`);
@@ -62641,14 +62683,9 @@ async function scanModelsFromDisk(tool, workspacePath) {
62641
62683
  }
62642
62684
  async function pruneDeadWorkspaceBindings(config) {
62643
62685
  let changed = false;
62644
- const allPaths = /* @__PURE__ */ new Set([
62645
- ...Object.keys(config.workspaceBindings),
62646
- ...Object.keys(workspaceEnrollmentsOf(config))
62647
- ]);
62648
- for (const workspacePath of allPaths) {
62686
+ for (const workspacePath of Object.keys(config.workspaceBindings)) {
62649
62687
  if (await isUserHomeDir(workspacePath)) {
62650
62688
  delete config.workspaceBindings[workspacePath];
62651
- delete workspaceEnrollmentsOf(config)[workspacePath];
62652
62689
  changed = true;
62653
62690
  continue;
62654
62691
  }
@@ -62657,7 +62694,6 @@ async function pruneDeadWorkspaceBindings(config) {
62657
62694
  } catch (error2) {
62658
62695
  if (error2.code === "ENOENT") {
62659
62696
  delete config.workspaceBindings[workspacePath];
62660
- delete workspaceEnrollmentsOf(config)[workspacePath];
62661
62697
  changed = true;
62662
62698
  } else {
62663
62699
  log.debug(
@@ -62670,26 +62706,20 @@ async function pruneDeadWorkspaceBindings(config) {
62670
62706
  }
62671
62707
  function stampWorkspaceTool(config, currentPath, tool) {
62672
62708
  if (!currentPath) return false;
62673
- const enrollment = workspaceEnrollmentsOf(config)[currentPath];
62674
- if (!enrollment) return false;
62675
62709
  const binding = config.workspaceBindings[currentPath];
62710
+ if (!binding?.enrolledAt) return false;
62676
62711
  const normalized = normalizeAgentType(tool);
62677
- let changed = false;
62678
- if (enrollment.ideType !== normalized) {
62679
- enrollment.ideType = normalized;
62680
- changed = true;
62681
- }
62682
- if (binding && binding.ideType !== normalized) {
62712
+ if (binding.ideType !== normalized) {
62683
62713
  binding.ideType = normalized;
62684
- changed = true;
62714
+ return true;
62685
62715
  }
62686
- return changed;
62716
+ return false;
62687
62717
  }
62688
62718
  function selectToolWorkspaces(config, currentPath, currentTool) {
62689
- return Object.keys(workspaceEnrollmentsOf(config)).filter((wsPath) => {
62690
- const enrollment = workspaceEnrollmentsOf(config)[wsPath];
62719
+ return Object.keys(config.workspaceBindings).filter((wsPath) => {
62691
62720
  const b2 = config.workspaceBindings[wsPath];
62692
- const wsTool = enrollment.ideType ?? b2?.ideType ?? (wsPath === currentPath ? currentTool : void 0);
62721
+ if (!b2.enrolledAt) return false;
62722
+ const wsTool = b2.ideType ?? (wsPath === currentPath ? currentTool : void 0);
62693
62723
  return wsTool === currentTool;
62694
62724
  });
62695
62725
  }
@@ -62751,7 +62781,7 @@ async function buildReportPayload(config, context) {
62751
62781
  name: path76.basename(wsPath),
62752
62782
  ide_type: currentTool
62753
62783
  };
62754
- if (wsBinding && wsBinding.projectId > 0) workspace.project_id = wsBinding.projectId;
62784
+ if ((wsBinding?.projectId ?? 0) > 0) workspace.project_id = wsBinding.projectId;
62755
62785
  if (wsScope.skills.length > 0) workspace.skills = wsScope.skills;
62756
62786
  if (wsScope.rules.length > 0) workspace.rules = wsScope.rules;
62757
62787
  const wsMcps = await scanMcpFromManifest("project", tool, wsPath);
@@ -62780,7 +62810,7 @@ async function buildSyncPayload(config, context) {
62780
62810
  path: wsPath,
62781
62811
  name: path76.basename(wsPath),
62782
62812
  ide_type: currentTool,
62783
- ...wsBinding && wsBinding.projectId > 0 ? { project_id: wsBinding.projectId } : {}
62813
+ ...(wsBinding?.projectId ?? 0) > 0 ? { project_id: wsBinding.projectId } : {}
62784
62814
  };
62785
62815
  });
62786
62816
  }
@@ -63852,7 +63882,7 @@ async function applyModelConfig(config, command, context) {
63852
63882
  if (scope === "project" && !workspacePath) {
63853
63883
  throw new Error("apply_model_config: workspace command is missing workspace_path");
63854
63884
  }
63855
- if (workspacePath && workspaceEnrollmentsOf(config)[workspacePath] === void 0) {
63885
+ if (workspacePath && !isWorkspaceEnrolled(config, workspacePath)) {
63856
63886
  throw new Error(
63857
63887
  `apply_model_config: workspace "${path76.basename(workspacePath)}" is not enrolled`
63858
63888
  );
@@ -64377,7 +64407,7 @@ async function executeCommand(config, command, context) {
64377
64407
  if (normalizeScope(command.scope) === "project") {
64378
64408
  const workspacePath2 = await resolveWorkspacePath(command.workspace_path ?? context.cwd);
64379
64409
  if (!workspacePath2) throw new Error(`${command.type ?? "workspace command"} is missing workspace_path`);
64380
- if (!workspaceEnrollmentsOf(config)[workspacePath2]) {
64410
+ if (!isWorkspaceEnrolled(config, workspacePath2)) {
64381
64411
  throw new Error(`Workspace is not enrolled for TeamAI management: ${workspacePath2}`);
64382
64412
  }
64383
64413
  if (command.project_id !== void 0) {
@@ -64472,7 +64502,7 @@ async function syncLocalAgent(context) {
64472
64502
  if (!config) return false;
64473
64503
  const workspacePath = await resolveWorkspacePath(context.cwd);
64474
64504
  if (isBindPromptEnabled() && isBindPromptTool(context.tool)) {
64475
- if (workspacePath && workspaceEnrollmentsOf(config)[workspacePath]) {
64505
+ if (workspacePath && isWorkspaceEnrolled(config, workspacePath)) {
64476
64506
  const sid = context.event?.sessionId;
64477
64507
  if (context.event?.type === "session_start") {
64478
64508
  await ensureWorkspaceBinding(config, workspacePath, sid, context.cwd);
@@ -64674,7 +64704,7 @@ async function initLocalAgentHttp(options2) {
64674
64704
  endpoint,
64675
64705
  token: options2.token ?? existing2?.token,
64676
64706
  createdAt: existing2?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
64677
- workspaceEnrollments: existing2?.workspaceEnrollments ?? {},
64707
+ workspaceStateVersion: 1,
64678
64708
  workspaceBindings: existing2?.workspaceBindings ?? {},
64679
64709
  userGroupId: existing2?.userGroupId,
64680
64710
  userGroupName: existing2?.userGroupName
@@ -64708,7 +64738,7 @@ async function pullLocalAgentForCwd(context) {
64708
64738
  async function describeLocalAgent(options2 = {}) {
64709
64739
  const config = await loadLocalAgentConfig(options2);
64710
64740
  if (!config) return null;
64711
- const boundProjects = Object.entries(config.workspaceBindings).filter(([, b2]) => b2.projectId !== 0).map(([workspacePath, b2]) => ({ path: workspacePath, projectName: b2.projectName, projectId: b2.projectId }));
64741
+ const boundProjects = Object.entries(config.workspaceBindings).filter(([, b2]) => (b2.projectId ?? 0) > 0).map(([workspacePath, b2]) => ({ path: workspacePath, projectName: b2.projectName, projectId: b2.projectId }));
64712
64742
  const manifest = await loadManifest2();
64713
64743
  const counts = { skills: 0, rules: 0, claudemd: 0 };
64714
64744
  for (const scope of Object.values(manifest.scopes)) {
@@ -64922,7 +64952,7 @@ async function bindCurrentProject(options2) {
64922
64952
  if (!config) {
64923
64953
  throw new Error("Local agent not initialized. Run `teamai init --http` first.");
64924
64954
  }
64925
- if (!workspaceEnrollmentsOf(config)[workspacePath]) {
64955
+ if (!isWorkspaceEnrolled(config, workspacePath)) {
64926
64956
  throw new Error(
64927
64957
  "Current workspace is not enrolled. Run `teamai init --http <ENDPOINT> --token <API_TOKEN>` here first."
64928
64958
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-ai/teamai-cli",
3
- "version": "0.30.2",
3
+ "version": "0.30.3",
4
4
  "description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {