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

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
@@ -104,9 +104,9 @@ An identical connection is idempotent. An explicit connect automatically
104
104
  switches the project. GitHub checkouts use the repository's lowercase name in
105
105
  the same flat managed namespace as created trees.
106
106
 
107
- `context-tree list` reports valid, clean managed trees as
108
- `{ schemaVersion: 1, trees: [{ name, tree }] }`; a missing managed directory
109
- is an empty list.
107
+ `context-tree list` reports valid, clean managed trees; `context-tree list --json`
108
+ returns them as `{ schemaVersion: 1, trees: [{ name, tree }] }`, and a missing
109
+ managed directory is an empty list.
110
110
 
111
111
  ### Read
112
112
 
@@ -190,7 +190,21 @@ orchestrates the five concrete workflows. `install` is the distribution
190
190
  entry point, run for you by `npm install`. `resolve`, `sync`, `prepare-write`,
191
191
  `finish-write`, and `verify` are plumbing or diagnostic commands rather than
192
192
  separate user intentions; `list` backs setup's connect-target discovery.
193
- All machine-readable responses use strict schema version `1`.
193
+
194
+ ### Output
195
+
196
+ `create`, `connect`, `list`, `resolve`, `publish`, `read`, and `verify` print
197
+ human-readable text by default and accept `--json` to emit their strict schema
198
+ version `1` payload for scripts and agents; in text mode a failure prints a
199
+ 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
201
+ low-level plumbing and always emit that JSON (with the error envelope on stdout).
202
+ `--help` and `--version` are always plain text.
203
+
204
+ ```bash
205
+ context-tree verify # human-readable report
206
+ context-tree verify --json # { "ok": true, "schemaVersion": 1, ... }
207
+ ```
194
208
 
195
209
  `verify` is intended for CI and diagnostics. Normal skills invoke it only after
196
210
  an operation reports invalid tree content.
@@ -26460,46 +26460,139 @@ function reclaimAbandonedWrites(root, checkoutBranch, runner) {
26460
26460
  }
26461
26461
  }
26462
26462
  //#endregion
26463
+ //#region src/cli/format.ts
26464
+ const POINTER_NOTE = {
26465
+ skipped: "left unchanged",
26466
+ updated: "updated",
26467
+ written: "written"
26468
+ };
26469
+ function treeLines(tree, indent = " ") {
26470
+ const lines = [`${indent}Path: ${tree.path}`];
26471
+ if (tree.kind === "github") lines.push(`${indent}Repository: ${tree.repository}`);
26472
+ return lines;
26473
+ }
26474
+ function formatCreate(result) {
26475
+ return [
26476
+ `${result.created ? "Created" : "Reused"} managed Context Tree "${result.title}".`,
26477
+ ` Path: ${result.treePath}`,
26478
+ ` Branch: ${result.branch}`,
26479
+ ` Commit: ${result.commitSha}`,
26480
+ ` AGENTS.md: ${POINTER_NOTE[result.pointer]}`
26481
+ ].join("\n");
26482
+ }
26483
+ function formatConnect(result) {
26484
+ return [
26485
+ `Connected ${result.tree.kind} Context Tree.`,
26486
+ ...treeLines(result.tree),
26487
+ ` AGENTS.md: ${POINTER_NOTE[result.pointer]}`
26488
+ ].join("\n");
26489
+ }
26490
+ function formatResolve(result) {
26491
+ return [`Connected ${result.tree.kind} Context Tree.`, ...treeLines(result.tree)].join("\n");
26492
+ }
26493
+ function formatList(result) {
26494
+ if (result.trees.length === 0) return "No managed Context Trees.";
26495
+ const count = result.trees.length;
26496
+ const lines = [`${count} managed Context Tree${count === 1 ? "" : "s"}:`];
26497
+ for (const entry of result.trees) lines.push(` ${entry.name} ${entry.tree.kind} ${entry.tree.path}`);
26498
+ return lines.join("\n");
26499
+ }
26500
+ function formatPublish(result) {
26501
+ return [
26502
+ `Published Context Tree to ${result.repository}.`,
26503
+ ` URL: ${result.url}`,
26504
+ ` Branch: ${result.branch}`,
26505
+ ` Commit: ${result.sha}`
26506
+ ].join("\n");
26507
+ }
26508
+ function formatRead(result) {
26509
+ const title = result.node.frontmatter.title;
26510
+ const lines = [];
26511
+ if (typeof title === "string" && title.trim().length > 0) lines.push(title.trim());
26512
+ lines.push(`Path: ${result.node.path} (${result.node.kind}, ${result.node.contentClass})`);
26513
+ lines.push(`Root: ${result.root}`);
26514
+ if (result.node.body.trim().length > 0) lines.push("", result.node.body.trimEnd());
26515
+ if (result.children.length > 0) {
26516
+ lines.push("", "Children:");
26517
+ for (const child of result.children) {
26518
+ const description = child.description ? ` — ${child.description}` : "";
26519
+ lines.push(` ${child.title}${description} [${child.path}]`);
26520
+ }
26521
+ }
26522
+ return lines.join("\n");
26523
+ }
26524
+ function formatVerify(report) {
26525
+ const counts = report.scannedByContentClass;
26526
+ const lines = [
26527
+ report.ok ? "Context Tree OK." : "Context Tree INVALID.",
26528
+ ` Root: ${report.root}`,
26529
+ ` Scanned: normal=${counts.normal} member=${counts.member} repo-infra=${counts["repo-infra"]}`
26530
+ ];
26531
+ if (report.findings.length > 0) {
26532
+ lines.push(" Findings:");
26533
+ for (const finding of report.findings) lines.push(` ${finding.code} ${finding.path}: ${finding.message}`);
26534
+ }
26535
+ return lines.join("\n");
26536
+ }
26537
+ //#endregion
26463
26538
  //#region src/cli/api.ts
26464
26539
  const defaultIo = {
26465
26540
  cwd: () => process.cwd(),
26541
+ stderr: (value) => process.stderr.write(value),
26466
26542
  stdout: (value) => process.stdout.write(value)
26467
26543
  };
26544
+ /** Commands that default to human-readable text and accept --json to restore JSON. */
26545
+ const TEXT_DEFAULT_COMMANDS = new Set([
26546
+ "create",
26547
+ "connect",
26548
+ "list",
26549
+ "resolve",
26550
+ "publish",
26551
+ "read",
26552
+ "verify"
26553
+ ]);
26468
26554
  function line(io, value) {
26469
26555
  io.stdout(`${value}\n`);
26470
26556
  }
26557
+ function errline(io, value) {
26558
+ (io.stderr ?? ((text) => process.stderr.write(text)))(`${value}\n`);
26559
+ }
26560
+ function emit(io, json, result, format) {
26561
+ line(io, json ? JSON.stringify(result) : format(result));
26562
+ }
26563
+ const jsonOption = ["--json", "print machine-readable JSON (schema version 1) instead of text"];
26471
26564
  function createContextTreeCli(io = defaultIo) {
26472
26565
  const program = new Command().name("context-tree").description("Create, connect, list, read, write, and publish Context Trees.").addHelpCommand(false).version(readPackageVersion()).exitOverride().configureOutput({
26473
26566
  writeErr: () => void 0,
26474
26567
  writeOut: io.stdout
26475
26568
  });
26476
- program.command("create").description("Create and connect one uniquely named managed Context Tree for the current project.").option("--project-path <path>", "project directory", ".").action((options) => {
26477
- line(io, JSON.stringify(createProject(resolve(io.cwd(), options.projectPath))));
26569
+ program.command("create").description("Create and connect one uniquely named managed Context Tree for the current project.").option("--project-path <path>", "project directory", ".").option(...jsonOption).action((options) => {
26570
+ emit(io, options.json, createProject(resolve(io.cwd(), options.projectPath)), formatCreate);
26478
26571
  });
26479
- program.command("connect").description("Connect the project by managed tree name, GitHub OWNER/REPO, or exact disk path.").argument("[name-or-repository]", "managed tree name or GitHub OWNER/REPO").option("--project-path <path>", "project directory", ".").option("--tree-path <path>", "exact Context Tree Git root to connect in place").action((target, options) => {
26572
+ program.command("connect").description("Connect the project by managed tree name, GitHub OWNER/REPO, or exact disk path.").argument("[name-or-repository]", "managed tree name or GitHub OWNER/REPO").option("--project-path <path>", "project directory", ".").option("--tree-path <path>", "exact Context Tree Git root to connect in place").option(...jsonOption).action((target, options) => {
26480
26573
  const projectPath = resolve(io.cwd(), options.projectPath);
26481
26574
  if (target !== void 0 && options.treePath !== void 0) throw new Error("Connect requires exactly one of a name/repository or --tree-path.");
26482
26575
  if (target !== void 0) {
26483
- line(io, JSON.stringify(connectProject({
26576
+ emit(io, options.json, connectProject({
26484
26577
  projectPath,
26485
26578
  target
26486
- })));
26579
+ }), formatConnect);
26487
26580
  return;
26488
26581
  }
26489
26582
  if (options.treePath !== void 0) {
26490
- line(io, JSON.stringify(connectProject({
26583
+ emit(io, options.json, connectProject({
26491
26584
  projectPath,
26492
26585
  treePath: resolve(io.cwd(), options.treePath)
26493
- })));
26586
+ }), formatConnect);
26494
26587
  return;
26495
26588
  }
26496
26589
  throw new Error("Connect requires a managed tree name, GitHub OWNER/REPO, or --tree-path.");
26497
26590
  });
26498
- program.command("list").description("List valid clean managed Context Trees.").action(() => {
26499
- line(io, JSON.stringify(listManagedTrees()));
26591
+ program.command("list").description("List valid clean managed Context Trees.").option(...jsonOption).action((options) => {
26592
+ emit(io, options.json, listManagedTrees(), formatList);
26500
26593
  });
26501
- program.command("resolve").description("Resolve the connected Context Tree for a project.").option("--project-path <path>", "project directory", ".").action((options) => {
26502
- line(io, JSON.stringify(resolveConnection(resolve(io.cwd(), options.projectPath))));
26594
+ program.command("resolve").description("Resolve the connected Context Tree for a project.").option("--project-path <path>", "project directory", ".").option(...jsonOption).action((options) => {
26595
+ emit(io, options.json, resolveConnection(resolve(io.cwd(), options.projectPath)), formatResolve);
26503
26596
  });
26504
26597
  program.command("sync").description("Synchronize the connected Context Tree for a project.").option("--project-path <path>", "project directory", ".").action((options) => {
26505
26598
  line(io, JSON.stringify(syncProject(resolve(io.cwd(), options.projectPath))));
@@ -26514,17 +26607,17 @@ function createContextTreeCli(io = defaultIo) {
26514
26607
  worktreePath: resolve(io.cwd(), options.worktreePath)
26515
26608
  })));
26516
26609
  });
26517
- program.command("publish").description("Publish the local tree as a new private GitHub repository.").argument("[repository]", "GitHub OWNER/REPO override; defaults to the authenticated account and tree name").option("--project-path <path>", "project directory", ".").action((repository, options) => {
26518
- line(io, JSON.stringify(publishProject(resolve(io.cwd(), options.projectPath), { repository })));
26610
+ program.command("publish").description("Publish the local tree as a new private GitHub repository.").argument("[repository]", "GitHub OWNER/REPO override; defaults to the authenticated account and tree name").option("--project-path <path>", "project directory", ".").option(...jsonOption).action((repository, options) => {
26611
+ emit(io, options.json, publishProject(resolve(io.cwd(), options.projectPath), { repository }), formatPublish);
26519
26612
  });
26520
- program.command("read").description("Read an indexed Context Tree directory or Markdown leaf.").argument("[path]", "tree-relative path", ".").option("--tree-path <path>", "Context Tree root", ".").action((path, options) => {
26613
+ program.command("read").description("Read an indexed Context Tree directory or Markdown leaf.").argument("[path]", "tree-relative path", ".").option("--tree-path <path>", "Context Tree root", ".").option(...jsonOption).action((path, options) => {
26521
26614
  const treePath = resolve(io.cwd(), options.treePath);
26522
26615
  if (!verifyTree(treePath).ok) throw new ContextTreeError(CLI_ERROR_CODES.invalidTree, `Refusing to read an invalid Context Tree; run context-tree verify --tree-path ${treePath}.`);
26523
- line(io, JSON.stringify(readTree(treePath, path)));
26616
+ emit(io, options.json, readTree(treePath, path), formatRead);
26524
26617
  });
26525
- program.command("verify").description("Validate Context Tree structure and safety.").option("--tree-path <path>", "Context Tree root", ".").action((options) => {
26618
+ program.command("verify").description("Validate Context Tree structure and safety.").option("--tree-path <path>", "Context Tree root", ".").option(...jsonOption).action((options) => {
26526
26619
  const result = verifyTree(resolve(io.cwd(), options.treePath));
26527
- line(io, JSON.stringify(result));
26620
+ emit(io, options.json, result, formatVerify);
26528
26621
  if (!result.ok) process.exitCode = 1;
26529
26622
  });
26530
26623
  program.command("install").description("Install the packaged Context Tree skills into each agent's skill directory.").option("--host <host>", "restrict to one host: claude, codex, or all", "all").option("--project <path>", "install below this project root instead of the home directory").action((options) => {
@@ -26542,19 +26635,27 @@ async function runContextTreeCli(argv = process.argv, io = defaultIo) {
26542
26635
  return typeof process.exitCode === "number" && process.exitCode !== 0 ? process.exitCode : 0;
26543
26636
  } catch (error) {
26544
26637
  if (error instanceof CommanderError && error.exitCode === 0) return 0;
26545
- const envelope = {
26638
+ const code = error instanceof ContextTreeError ? error.code : CLI_ERROR_CODES.failed;
26639
+ const message = sanitizeCommandOutput(error instanceof Error ? error.message : String(error));
26640
+ if (usesTextErrors(argv)) errline(io, `context-tree: ${message}`);
26641
+ else line(io, JSON.stringify({
26546
26642
  error: {
26547
- code: error instanceof ContextTreeError ? error.code : CLI_ERROR_CODES.failed,
26548
- message: sanitizeCommandOutput(error instanceof Error ? error.message : String(error))
26643
+ code,
26644
+ message
26549
26645
  },
26550
26646
  ok: false,
26551
26647
  schemaVersion: 1
26552
- };
26553
- line(io, JSON.stringify(envelope));
26648
+ }));
26554
26649
  process.exitCode = 1;
26555
26650
  return 1;
26556
26651
  }
26557
26652
  }
26653
+ /** A text-default command failing without --json reports a human-readable line on stderr. */
26654
+ function usesTextErrors(argv) {
26655
+ if (argv.includes("--json")) return false;
26656
+ const subcommand = argv.slice(2).find((token) => !token.startsWith("-"));
26657
+ return subcommand !== void 0 && TEXT_DEFAULT_COMMANDS.has(subcommand);
26658
+ }
26558
26659
  //#endregion
26559
26660
  //#region src/cli/index.ts
26560
26661
  await runContextTreeCli();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@first-tree-ai/context-tree",
3
- "version": "0.1.8",
3
+ "version": "0.1.9-alpha.202609020917",
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",
@@ -15,9 +15,9 @@ If `context-tree` is not found, stop and ask the user to run
15
15
  Connect exactly one target supplied by the user:
16
16
 
17
17
  - A managed tree name or GitHub `OWNER/REPO`:
18
- `context-tree connect "<name-or-OWNER/REPO>"`.
18
+ `context-tree connect "<name-or-OWNER/REPO>" --json`.
19
19
  - An exact path to an existing Context Tree checkout:
20
- `context-tree connect --tree-path "<path>"`.
20
+ `context-tree connect --tree-path "<path>" --json`.
21
21
  That checkout is attached where it already lives and is never copied, moved,
22
22
  or deleted.
23
23
 
@@ -12,7 +12,7 @@ metadata:
12
12
  If `context-tree` is not found, stop and ask the user to run
13
13
  `npm install --global @first-tree-ai/context-tree`.
14
14
 
15
- Run `context-tree create`. Report whether the managed tree was created or
15
+ Run `context-tree create --json`. Report whether the managed tree was created or
16
16
  already existed, together with its name, path, and exact commit SHA.
17
17
 
18
18
  The managed name is derived from the project directory's name. If that name is
@@ -25,7 +25,7 @@ sessions and other agents find it without any host-specific setup. The result's
25
25
  `pointer` field reports `written`, `updated`, or `skipped`; when it is not
26
26
  `skipped`, tell the user that `AGENTS.md` in their project changed.
27
27
 
28
- After the tree is created or reused, run `context-tree resolve`. When the tree is
28
+ After the tree is created or reused, run `context-tree resolve --json`. When the tree is
29
29
  local, ask the user whether to publish it as a private GitHub repository. An
30
30
  explicit prior request to publish counts as confirmation; otherwise a "no"
31
31
  leaves the tree local, and a "yes" delegates to `$context-tree-publish`. Never
@@ -12,7 +12,7 @@ metadata:
12
12
  If `context-tree` is not found, stop and ask the user to run
13
13
  `npm install --global @first-tree-ai/context-tree`.
14
14
 
15
- Run `context-tree publish`. When the user explicitly supplies an alternative,
15
+ Run `context-tree publish --json`. When the user explicitly supplies an alternative,
16
16
  append the validated `OWNER/REPO` argument. Never accept a repository URL.
17
17
 
18
18
  Publication creates one new private repository, and the local connection update
@@ -15,7 +15,7 @@ run `npm install --global @first-tree-ai/context-tree`. If it reports
15
15
  run `sync` again once.
16
16
 
17
17
  Use the returned `tree.path` for narrow, task-relevant reads with
18
- `context-tree read [path] --tree-path "<tree-path>"`. Start at the root index,
18
+ `context-tree read [path] --tree-path "<tree-path>" --json`. Start at the root index,
19
19
  then open only the immediate children that bear on the task. Do not scan the
20
20
  whole tree.
21
21
 
@@ -12,14 +12,14 @@ metadata:
12
12
  If `context-tree` is not found, stop and ask the user to run
13
13
  `npm install --global @first-tree-ai/context-tree`.
14
14
 
15
- Run `context-tree resolve`. If it succeeds, report whether the tree is local or
15
+ Run `context-tree resolve --json`. If it succeeds, report whether the tree is local or
16
16
  GitHub-backed, with its canonical path, and stop; the project is already set up.
17
17
 
18
18
  If `resolve` reports `NO_CONNECTION`, ask the user whether to create a new
19
19
  Context Tree or connect an existing one:
20
20
 
21
21
  - To create, delegate to `$context-tree-create`.
22
- - To connect, run `context-tree list` and offer every listed managed name, a
22
+ - To connect, run `context-tree list --json` and offer every listed managed name, a
23
23
  GitHub `OWNER/REPO`, and an exact disk path. Delegate the chosen target to
24
24
  `$context-tree-connect`, which owns the rules for accepting it.
25
25