Files
teamai-cli/package.json
T
Saul Moro 21cb76aa49 feat: one namespace model for every resource type (#707) (#816)
* chore: start one namespace model for every resource type (#707)

* refactor(pull): check agent and skill namespace collisions with one resolver (#707)

Add src/namespace-resolver.ts, the pure rule tickets 02-05 build on: an
active namespace item replaces the root item of the same name, and a name
twice in one place or in two active namespaces is a tagged conflict naming
both sources. The result depends only on the active order, not read order.

Agents and skills now run their duplicate checks through it and throw the
same messages. Agents still treat root + namespace as an error.

Add fast-check for the resolver's property tests.

* feat(env,hooks,mcp): scope env, hooks and MCP servers by namespace (#707)

env/<ns>/env.yaml, hooks/<ns>/hooks.yaml and mcp/<ns>/mcp.yaml are read where
<ns> is active in resources.env/hooks/mcp; a namespace entry replaces the root
entry of the same key, hook id or server name. A broken active file, a name
twice in one file or in two active namespaces stops that type for the run and
keeps what is installed. Per-entry projects: (and roles: on env) reach nobody;
roles: on hooks and MCP keeps filtering with a deprecation warning. Unknown
resources: keys warn instead of failing the manifest.

* feat(pull): let an active namespace item replace the root item for skills, agents, rules and claudemd (#707)

With a role or project configured, an item in an active namespace now
replaces the root item of the same name, whole:

- agents by stem: root + namespace is no longer a duplicate error, and a
  recorded (placed) agent replaces the root agent too
- skills by name, including a root skill received through a tag; an
  install removes the files of the version it replaces
- rules by first-level file name, in tool dirs and Hermes' SOUL.md block
- claudemd files by name in the managed block

Two active namespaces with one rule or claudemd name stop that type for
the run and keep what is installed. Push writes an edited overridden
skill, agent or rule back to its namespace, and the skills push scan
covers role and project namespaces. The placement record is withdrawn
by a same-name shared-root file only in legacy mode. Recall indexes the
skills pull delivers. doctor lists overrides, and in legacy mode repeated
names, as notes. Legacy mode is otherwise unchanged.

* fix(pull): deliver both namespace rules and claudemd files of one name (#707)

Rules and claudemd have no namespace-vs-namespace conflict: each
namespace rule keeps its own local path and each claudemd file its own
place in the block, so two active namespaces with one first-level name
are both delivered, as before. Only root suppression applies.

doctor override and legacy repeated-name notes now use the same wording
as the env, hooks and MCP ones. The usage guide and admin reference say
to keep overridable shared content at the root, with an example.

* fix(pull): stop only skills or agents on a namespace collision (#707)

Two active namespaces with one skill name or agent stem used to throw
and abort the whole scope, so rules, env, docs, cleanup and the search
index were skipped too. resolveDesiredSkills, resolveDesiredAgents,
scanRoleAwareSkills and filterAgentsByNamespaces now return a tagged
conflict. pull warns, leaves that type as installed (no install, no
inactive-namespace sweep) and syncs the rest. doctor reports the
collision as before; recall indexes no skills while it stands.

* feat(env,hooks,mcp): namespace flags, origins in status and doctor, docs (#707)

env add/remove take --role/--project; remove mcp searches every file and asks
for --role/--project when several define the name; push picks up
env/<ns>/env.yaml. status, list and doctor show where each entry comes from;
doctor lists overrides as notes and per-entry roles:/projects: as one
informational check. Usage guides and the admin skill reference describe the
namespace files; the #668 e2e moves onto them.

* test(env): show a broken env file stops env only (#707)

* fix(remove): remove an MCP server from the root file by default (#707)

remove mcp <name> follows push's convention: mcp/mcp.yaml when it defines the
name, else the one namespace file that does; --role/--project pick a namespace.
Only a name several namespace files (and not the root) define is refused.

* test(remove): expect namespace files in sorted order (#707)

* feat(docs): deliver a declared docs namespace only where it is active (#707)

A docs/<ns>/ that any role or project lists under resources.docs now
reaches only members with that namespace active; an undeclared
docs/<dir>/ stays shared. Leaving a namespace removes its local docs that
are byte-equal to the team copy and keeps edited ones with a line.
team-codebase is rejected as a docs namespace. The search index (pull,
recall, contribute) and doctor's "Team docs delivered" use the same set.

* feat(models): scope team model profiles by namespace and bind keys to their gateway (#707)

models/<ns>/models.yaml, declared under resources.models, replaces the root
profile with the same id while <ns> is active. A team API key is stored per
profile id and base_url origin, so pull never writes a key next to a gateway
on another origin; it prints the models switch line instead. Conflicts and
broken files stop model updates for the run. models list and doctor show
where each profile comes from; push validates every models file.

* docs(models): describe model profiles by namespace and key binding (#707)

* docs: list docs among the axes declared by hand (#707)

* docs: describe one namespace model for every resource type (#707)

Rewrite the multi-project design doc's precedence section for
namespace-over-root, add the per-type conflict, failure and legacy-mode
rules, and replace the per-entry key rows in the product overview. The
JSON doctor notes now also carry namespace notes.

* docs(changelog): replace per-entry scoping with namespace files (#707)

Drop the beta-only per-entry projects: entry, add the namespace axes,
the override, the per-type failure policy, the roles: deprecation on
hooks and MCP, and the upgrade-every-member-first note.

* docs(changelog): say the model key binding re-keys once and affects only betas (#707)

* refactor(namespaces): one warn-once registry instead of the quiet flag (#707)

Namespace fallback warnings, entry notices and unknown resources: keys
now go through utils/warn-once, reset once per pull, so the quiet option
threaded through eleven signatures is gone. The one-line wrappers
resolveTeamEnv, resolveTeamMcpServers and resolveTeamProfiles are removed;
every caller uses resolveEntries/resolveEntriesFor with the type's reader.

* refactor(namespaces): shared entry-file helpers, no unsafe casts in new code (#707)

- listEntryFiles/entryFileAbsolutePath replace the per-type file listers
  in env, mcp and models and the repeated path joins.
- gatewaySuffix replaces three spellings of the gateway suffix.
- LATER_RESOURCE_TYPES and friends are named for what they are:
  HAND_DECLARED_RESOURCE_TYPES, HandDeclaredNamespacesShape.
- Error messages use instanceof Error; manifest role/project ids are
  narrowed instead of cast; mapResources builds a typed object; pull
  writes env through an EnvHandler instance instead of a cast.
- status keys counts by entry type; rules localNameFor reuses
  deliversEveryNamespace, whose false answer is now documented.

* refactor(desired): move the desired-set resolvers out of pull.ts (#707)

Commands must stay thin, and recall, contribute and doctor imported
pull.js only to learn what a member receives. The skills, agents, rules
and claudemd resolvers, RolePullContext and the index sources now live in
src/resources/desired.ts; root suppression, the override note and the
repeated-name grouping live in namespace-resolver.

- A skill or agent conflict is a tagged DeliveryConflict carrying the
  resolver's NamespaceConflict, rendered once by describeDeliveryConflict
  (wording unchanged); DesiredItems names the result union.
- DesiredItems keeps each override, so doctor no longer rebuilds skill
  and agent overrides by hand.
- recall and contribute share deliveredIndexSources; pull indexes through
  the same indexedSkills instead of a second copy.
- doctor: one unresolvableCheck for skills, agents and docs; the docs
  check reports an unreadable manifest instead of returning nothing; the
  namespace notes catch only the team-repo reads.
- docs withdrawal reuses utils pruneEmptyDirs.

* fix(pull): name both files in a skill or agent conflict (#707)

Story 8 asks for a message naming both files. A skill or agent conflict
named only the namespaces, and an agent defined twice inside one
namespace (agents/a/x.md next to agents/a/x.yaml) read as 'found in
active namespaces "a" and "a"'. The duplicate case now names its one
place, and both cases list the two files.

* fix(rules): only a delivered namespace rule replaces the root rule, in every tool (#707)

- The rules override ran before the tag filter, so a namespace rule the
  member's tag subscription excludes still suppressed the root rule and
  the member received neither. The tag filter now runs first.
- JoyCode, OMP, Pi and Copilot share their rule directory with the
  member's own rules, so the stale sweep deletes nothing there unless a
  tombstone names it: the root rule a namespace rule replaced stayed
  installed and both versions loaded (story 6). pullAllRules now removes
  such a copy while it is byte-equal to its render, as agents do.

* fix(recall): keep the indexed skills while a skills conflict holds them (#707)

On a skills conflict pull keeps the installed skills, but the index was
rebuilt with none, so recall returned none of the skills the member still
has. The index now keeps the skills entries it already held, and pull
does the same when resolving the skills fails.

* fix(hooks,mcp): fail hooks inject and mcp inject when team entries do not resolve (#707)

reconcileTeamHooksForConfig returned [] when the team hooks could not be
resolved, the same value as a team without hooks, so hooks inject printed
'Hooks injected into all AI tool settings' and exited 0 over a broken
hooks/<ns>/hooks.yaml. It now returns { ok: false }, and hooks inject
exits 1 after the warning that names the file. mcp inject said 'Already
up to date.' in the same case; the MCP reconcile now marks the result
unresolved and mcp inject exits 1.

* fix(models): bind a beta API key to the gateway it was sent to, once (#707)

- A key a 0.26.0 beta stored under team:<id> counted for whatever origin
  the root profile had now, so a root profile moved to another host got
  the old key written next to it. The first pull or models command that
  reads such a key now binds it to the origin TeamAI last wrote into the
  agents switched to that profile (the root's current origin when it is
  among them), else to the root's current origin, and never re-reads the
  unbound key. Pull then leaves a moved agent alone and asks for the
  switch.
- The 'switch to set a key' and 'no longer active' lines are written to
  debug.log too: SessionStart pulls run silent.
- Legacy mode, which reads no namespace, says a profile 'was removed'.
- A models command whose profiles do not resolve reports it with
  log.error and exit code 1, as env list and mcp list do, instead of
  throwing.

* fix(entries): keep 0.25 files that repeat a name under different roles: working (#707)

0.25.0 let hooks.yaml and mcp.yaml repeat a hook or server name under
different roles:, delivering every copy that passed the role filter (MCP
kept the last). The namespace resolver treated that as a duplicate, so a
member holding both roles, or a role-less member in a team with
projects.yaml, stopped receiving hooks or MCP entirely. During the
roles: deprecation window such a repeat is delivered as in 0.25; a name
repeated without roles: on every copy is still a duplicate.

Also restores the test that the role filter runs before
requireTeamScripts, so the transparency print lists only what will run.

* feat(doctor): say where each entry type's entries come from (#707)

The spec asks doctor, like status and the list commands, to show each
entry's namespace; doctor listed overrides only. For env, hooks, MCP and
models, a namespace contributing any entry now adds a note counting the
entries by origin, 'env: 3 received here (2 root, 1 checkout)', from the
describeOrigins that status uses.

* test(pull): env and hooks conflicts between two namespaces, and builtin: in a namespace file (#707)

Seam 1 asks every type to show, through pull, that two active namespaces
defining one name keep the installed state and name both files. Env and
hooks were covered only through doctor and the handler; so was the
warning for builtin: in a namespace hooks file.

* docs(skill-data): hooks and MCP edits are published with git, not teamai push (#707)

manage-admin.md told admins to publish hooks/MCP file edits with
teamai push, which sweeps only rules/, env/ and .codebuddy-plugin/, so
an agent following it would push nothing. It now says to commit and
push the file with git, as the usage guide does.

* refactor(models): read switched agents without a cast (#707)

* docs(changelog): doctor counts entries per namespace; inject fails on unresolved entries (#707)

* refactor(pull): drop imports the resolver move left unused (#707)

* fix(hooks): install the built-in hooks when the team hooks do not resolve (#707)

A first init or bootstrap whose team hooks did not resolve (a broken
namespace file, a clash, a duplicate id) installed no built-in hook, so
the session-start pull that heals the member never ran. Installed team
hooks are still kept; the built-in hooks are now installed where missing,
with the root file's builtin: overrides whenever hooks/hooks.yaml parses,
and with their defaults only in a tool with no teamai hook when it does
not. init and bootstrap say that the team hooks were not installed.

* fix(manifest): keep an unknown resources: key when roles and projects save (#707)

zod stripped the key this CLI only warns about, so a projects or roles
command run on this version deleted a newer CLI's type from the team
repo for everyone.

* fix(docs): withdraw a copy the team edited after delivery, not only an unchanged one (#707)

Withdrawing an inactive docs namespace compared the local copy with the
current team file only. A doc the team changed after the member received
it was then kept forever with a false 'you edited it' line. A copy equal
to an earlier team commit is what the mirror delivered, so it goes too.

* fix(rules): withdraw a replaced root rule edited in the same push, name a kept copy (#707)

In the JoyCode, OMP, Pi and Copilot rule dirs, a replaced root rule's
copy was removed only while it matched the current root rule. When the
admin edited the root rule and added its namespace override in one push,
the member's unedited copy stayed loaded beside the override, silently.
It is now also compared with the render at the last pull, and a copy
that is kept is named with the fix.

* fix(doctor): fail a check when team hooks or model profiles do not resolve (#707)

teamai status counts such a type as 0 and says to run doctor, but doctor
had failing checks only for env and MCP, so a duplicate hook id or a
two-namespace clash showed nothing there.

* fix(env): warn when --role names a namespace nothing declares (#707)

env add/remove --role <ns> wrote env/<ns>/env.yaml for a namespace no
role or project lists under resources.env, so the variable reached
nobody and nothing said so. The same applies to remove mcp --role.

* fix(env): find a changed namespace env file whose name is not ASCII on push (#707)

git ls-files quotes such a path by default, so it never matched the name
on disk and push skipped the change.

* fix(entries): an active env, hooks, MCP or models file that cannot be read stops the type (#707)

The readers folded every read error into 'file does not exist', so an
unreadable namespace file silently delivered the root entry in place of
its override. Only ENOENT is absence now; any other error is a broken
file, like one that does not parse.

* fix(entries): match env, hooks, MCP and models namespace dirs case-folded, as docs does (#707)

A declared namespace was joined onto the path as written, so with
env: [checkout] and a directory env/Checkout/, macOS and Windows members
got the override and Linux members the root value.

* fix(doctor): split the legacy claudemd paths on '/', not path.sep (#707)

listFilesRecursive always joins with '/', so on Windows every path was
one segment and a claudemd/<ns>/x.md beside claudemd/x.md was never
reported.

* fix(recall): index the rules pull delivers, not the whole rules/ tree (#707)

A namespace rule replaces the root rule of its name, but recall, contribute
and pull indexed every file under rules/: the replaced root rule and the rules
of inactive namespaces came back from recall. Index the resolved rule set, as
docs and skills already do.

* fix(entries): write a namespace file into the directory pull reads it from (#707)

Pull matches a declared namespace to its directory case-folded, but --role and
--project returned the spelling typed. On a case-sensitive filesystem
`env add --project checkout` created env/checkout/, which shadowed
env/Checkout/ and dropped its variables from delivery.

* fix(remove): remove no MCP server by a bare name while an MCP file does not parse (#707)

The team scan skips a file that does not parse. With mcp/mcp.yaml broken,
`remove mcp db` took the one readable checkout/db as the target and removed
it. Refuse and name the file unless the readable root defines the name.

* docs: rules in recall, namespace writes and remove mcp on a broken file (#707)

* fix(remove): say the MCP file --role or --project names does not parse, not that the name is missing (#707)

The team scan skips a file that does not parse, so `remove mcp db --project
checkout` with a broken mcp/checkout/mcp.yaml reported "Not found". Name the
file and remove nothing.

* fix(skills): remove a leftover of another skill version only when it matches that version (#707)

Install removed any installed file at a path another team version of the skill
has, by path alone. A file a member added under that name, e.g. README.md
beside a namespace they never had, was deleted on every pull. Remove it only
when it is byte for byte that version's file; keep any other and name it.

* fix(env): edit no env file that does not parse, and no --project target after a failed refresh (#707)

env add and env remove read the target through parseEnvYaml, which answers an
empty list for a file that does not parse, then wrote that back: every
variable the file had was replaced. They now refuse and name the file.

--project resolves through manifest/projects.yaml. After a failed pull that
copy may be stale and name a namespace the project no longer uses, whose file
push would publish, so --project now changes nothing then. The root file and
--role do not depend on the manifest and still only warn.

* fix(pull): let no unusable namespace item replace the root one (#707)

A skill directory without SKILL.md replaced the root skill of its name:
install overlaid it and removed the installed SKILL.md as the other version's
leftover, while pull still counted the skill as synced. Such a directory is
no longer a skill; pull names it and keeps delivering the root one.

An agent file that does not parse delivers nothing, yet it still replaced the
root agent, and cleanup removed the unchanged root copy because no active
destination held that stem. The root agent now stays while its replacement
cannot be read or parsed.

* fix(push): take no namespace directory without SKILL.md for a member's skill (#707)

Pull stopped delivering such a directory in 6c7df8e3, but the push scan still
mapped the skill name to it. An unedited root skill then showed as modified,
and push wrote it into skills/<ns>/<name>/, deleting what was there and making
it a namespace skill that replaces the root one.

Also keep only a replaced root agent while its replacement does not parse, so
an unchanged copy from an inactive namespace is still removed, and put
renderedForTool's doc comment back on it.
2026-09-25 17:59:19 +08:00

97 lines
2.2 KiB
JSON

{
"name": "teamai-cli",
"version": "0.22.0",
"description": "TeamAI — Make Every Team AI Native (skill sync + shared knowledge base, powered by Git)",
"type": "module",
"bin": {
"teamai": "dist/index.js"
},
"files": [
"dist/**/*.js",
"skills",
"skill-data",
"!**/__pycache__",
"!**/*.pyc",
"agents",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run",
"test:e2e": "vitest run --config vitest.e2e.config.ts",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"release": "standard-version",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Tencent/teamai-cli.git"
},
"homepage": "https://github.com/Tencent/teamai-cli#readme",
"bugs": {
"url": "https://github.com/Tencent/teamai-cli/issues"
},
"keywords": [
"ai",
"team",
"devkit",
"skills",
"sharing"
],
"author": "jeffyxu <jeffyxu@tencent.com>",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^12.1.0",
"cross-spawn": "^7.0.6",
"fflate": "^0.8.3",
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"listr2": "^9.0.5",
"ora": "^8.1.0",
"semver": "^7.8.5",
"simple-git": "^3.36.0",
"smol-toml": "^1.7.1",
"tree-sitter-wasms": "0.1.13",
"web-tree-sitter": "0.25.10",
"yaml": "^2.6.0",
"zod": "^3.24.0"
},
"overrides": {
"js-yaml": "^3.15.2",
"brace-expansion@1": "1.1.18",
"brace-expansion@2": "2.1.4",
"brace-expansion@5": "5.0.9",
"nanoid": "3.3.19",
"postcss": "8.5.28",
"picomatch": "4.0.7"
},
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.17.0",
"@types/semver": "^7.8.0",
"@vitest/coverage-v8": "^3.2.7",
"fast-check": "^4.10.2",
"opencode-ai": "1.18.23",
"standard-version": "^9.5.0",
"tsup": "^8.3.0",
"typescript": "^5.7.0",
"vitest": "^3.2.7"
},
"main": "index.js",
"directories": {
"doc": "docs",
"example": "examples",
"test": "test"
}
}