@first-tree-ai/context-tree 0.1.9-alpha.202609020917 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,12 +44,14 @@ new agent, or scope it to one project:
44
44
  context-tree install # every agent you have
45
45
  context-tree install --host codex # one agent
46
46
  context-tree install --project . # ./.claude/skills and ./.codex/skills
47
+ context-tree uninstall # remove context-tree-* skills
47
48
  ```
48
49
 
49
- Installing only ever writes `context-tree-*` skill directories, never touches
50
- skills it does not own, and never creates a configuration directory for an agent
51
- that is not present. Adding support for another agent is one entry in the host
52
- table in `src/core/install.ts`.
50
+ Install and uninstall own exactly the `context-tree-*` skill directories.
51
+ Install never touches skills it does not own or creates a configuration directory
52
+ for an agent that is not present; uninstall removes every owned-prefix directory
53
+ and nothing else. Adding support for another agent is one entry in the host table
54
+ in `src/core/install.ts`.
53
55
 
54
56
  Once a project is connected, `create` and `connect` record the tree in the
55
57
  project's own `AGENTS.md`, so any agent that reads instruction files knows the
@@ -181,13 +183,14 @@ commit or discard them), `INVALID_TREE` (structure fails `verify`),
181
183
  The public command inventory is:
182
184
 
183
185
  ```text
184
- install create connect list resolve sync prepare-write
186
+ install uninstall create connect list resolve sync prepare-write
185
187
  finish-write publish read verify
186
188
  ```
187
189
 
188
190
  Setup, create, connect, read, write, and publish ship as six skills; setup
189
191
  orchestrates the five concrete workflows. `install` is the distribution
190
- entry point, run for you by `npm install`. `resolve`, `sync`, `prepare-write`,
192
+ entry point, run for you by `npm install`; `uninstall` is its supported reverse.
193
+ `resolve`, `sync`, `prepare-write`,
191
194
  `finish-write`, and `verify` are plumbing or diagnostic commands rather than
192
195
  separate user intentions; `list` backs setup's connect-target discovery.
193
196
 
@@ -197,7 +200,7 @@ separate user intentions; `list` backs setup's connect-target discovery.
197
200
  human-readable text by default and accept `--json` to emit their strict schema
198
201
  version `1` payload for scripts and agents; in text mode a failure prints a
199
202
  sanitized message to stderr with a non-zero exit code. The six skills always
200
- pass `--json`. `sync`, `prepare-write`, `finish-write`, and `install` are
203
+ pass `--json`. `sync`, `prepare-write`, `finish-write`, `install`, and `uninstall` are
201
204
  low-level plumbing and always emit that JSON (with the error envelope on stdout).
202
205
  `--help` and `--version` are always plain text.
203
206
 
@@ -7368,6 +7368,12 @@ object({
7368
7368
  skipped: array(skillInstallSkipSchema),
7369
7369
  version: string$2().trim().min(1)
7370
7370
  }).strict();
7371
+ object({
7372
+ removed: array(skillInstallationSchema),
7373
+ schemaVersion: literal(1),
7374
+ skipped: array(skillInstallSkipSchema),
7375
+ version: string$2().trim().min(1)
7376
+ }).strict();
7371
7377
  const contextTreeReadCommonFields = {
7372
7378
  contentClass: contextContentClassSchema,
7373
7379
  kind: _enum(["directory", "file"]),
@@ -25986,6 +25992,18 @@ function hostDestination(host, root, isProjectInstall) {
25986
25992
  }
25987
25993
  return { destination: ensureRealDirectory(root, [configDirectory, SKILLS_DIRECTORY]) };
25988
25994
  }
25995
+ /** Resolve one host's existing skills root without creating or following anything. */
25996
+ function hostSkillsRoot(host, root) {
25997
+ const hostRoot = join(root, HOST_CONFIG_DIRECTORY[host]);
25998
+ const hostEntry = lstatSync(hostRoot, { throwIfNoEntry: false });
25999
+ if (hostEntry === void 0) return { reason: `${hostRoot} does not exist; nothing to remove.` };
26000
+ if (hostEntry.isSymbolicLink() || !hostEntry.isDirectory()) return { reason: `${hostRoot} is not a real directory.` };
26001
+ const skillsRoot = join(hostRoot, SKILLS_DIRECTORY);
26002
+ const skillsEntry = lstatSync(skillsRoot, { throwIfNoEntry: false });
26003
+ if (skillsEntry === void 0) return { reason: `${skillsRoot} does not exist; nothing to remove.` };
26004
+ if (skillsEntry.isSymbolicLink() || !skillsEntry.isDirectory()) return { reason: `${skillsRoot} is not a real directory.` };
26005
+ return { destination: skillsRoot };
26006
+ }
25989
26007
  /**
25990
26008
  * Copy the packaged skills into each requested host's skill directory.
25991
26009
  *
@@ -26032,6 +26050,53 @@ function installSkills(options = {}) {
26032
26050
  version: readPackageVersion()
26033
26051
  };
26034
26052
  }
26053
+ /** Remove every skill owned by the `context-tree-` prefix from each requested host. */
26054
+ function uninstallSkills(options = {}) {
26055
+ const hosts = options.hosts === void 0 || options.hosts.length === 0 ? SKILL_HOSTS : options.hosts;
26056
+ const root = options.projectPath === void 0 ? realHome() : resolve(options.projectPath);
26057
+ const removed = [];
26058
+ const skipped = [];
26059
+ for (const host of hosts) {
26060
+ const resolved = hostSkillsRoot(host, root);
26061
+ if ("reason" in resolved) {
26062
+ skipped.push({
26063
+ host,
26064
+ reason: resolved.reason
26065
+ });
26066
+ continue;
26067
+ }
26068
+ const skills = [];
26069
+ for (const entry of readdirSync(resolved.destination, { withFileTypes: true })) {
26070
+ if (!entry.name.startsWith(OWNED_SKILL_PREFIX)) continue;
26071
+ const target = join(resolved.destination, entry.name);
26072
+ const targetEntry = lstatSync(target, { throwIfNoEntry: false });
26073
+ if (targetEntry === void 0) continue;
26074
+ if (targetEntry.isSymbolicLink() || !targetEntry.isDirectory()) {
26075
+ skipped.push({
26076
+ host,
26077
+ reason: `${target} is not a real directory.`
26078
+ });
26079
+ continue;
26080
+ }
26081
+ rmSync(target, {
26082
+ force: true,
26083
+ recursive: true
26084
+ });
26085
+ skills.push(entry.name);
26086
+ }
26087
+ removed.push({
26088
+ host,
26089
+ path: resolved.destination,
26090
+ skills: skills.sort()
26091
+ });
26092
+ }
26093
+ return {
26094
+ removed,
26095
+ schemaVersion: 1,
26096
+ skipped,
26097
+ version: readPackageVersion()
26098
+ };
26099
+ }
26035
26100
  //#endregion
26036
26101
  //#region src/core/publish.ts
26037
26102
  function authenticatedAccount(runner) {
@@ -26626,6 +26691,12 @@ function createContextTreeCli(io = defaultIo) {
26626
26691
  if (options.project !== void 0) request.projectPath = resolve(io.cwd(), options.project);
26627
26692
  line(io, JSON.stringify(installSkills(request)));
26628
26693
  });
26694
+ program.command("uninstall").description("Remove packaged Context Tree skills from each agent's skill directory.").option("--host <host>", "restrict to one host: claude, codex, or all", "all").option("--project <path>", "remove below this project root instead of the home directory").action((options) => {
26695
+ const request = {};
26696
+ if (options.host !== "all") request.hosts = [skillHostSchema.parse(options.host)];
26697
+ if (options.project !== void 0) request.projectPath = resolve(io.cwd(), options.project);
26698
+ line(io, JSON.stringify(uninstallSkills(request)));
26699
+ });
26629
26700
  return program;
26630
26701
  }
26631
26702
  async function runContextTreeCli(argv = process.argv, io = defaultIo) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@first-tree-ai/context-tree",
3
- "version": "0.1.9-alpha.202609020917",
3
+ "version": "0.1.9",
4
4
  "description": "Durable, structured project context for coding agents: a CLI plus framework-neutral skills.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",