@fluid-app/fluid-cli-genealogy 0.0.0-stage → 0.1.0

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
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ # @fluid-app/fluid-cli-genealogy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Fluid CLI plugin for genealogy trees: parents, children, legs, placements and
4
+ moves, over the company genealogy API (v2026-10).
5
+
6
+ ```bash
7
+ npm install -g @fluid-app/fluid-cli @fluid-app/fluid-cli-genealogy
8
+ fluid login
9
+ fluid genealogy trees
10
+ fluid genealogy show --member <member-id> # seat in the placement tree, with counts
11
+ fluid genealogy ancestors <node> --as-of 2026-09-01T00:00:00Z
12
+ fluid genealogy children <node> --limit 50 # prints next_cursor if more remain
13
+ fluid genealogy grandchildren <node>
14
+ fluid genealogy legs <node> # legs ranked by headcount
15
+ fluid genealogy strong-leg <node>
16
+ fluid genealogy tree <node> --depth 3 # indented text downline
17
+ fluid genealogy move <node> --to <parent> --dry-run # preview only
18
+ fluid genealogy move <node> --to <parent> --yes
19
+ ```
20
+
21
+ A node is a member's seat in one tree. Commands take a node UUID, or
22
+ `--member <member-id>` with `--tree <key-or-id>` (default `placement`) to use
23
+ that member's seat. A member with no seat, or several, in the tree is an error.
24
+
25
+ Reads: `show`, `counts`, `parent`, `root`, `ancestors`, `sponsor`, `enroller`,
26
+ `children`, `siblings`, `frontline`, `personally-enrolled`, `grandchildren`,
27
+ `level --depth N`, `descendants --depth N`, `subtree --depth N`,
28
+ `level-counts --depth N`, `relation <node> <other> [--kind]`, `placements`,
29
+ `holding-tank`, `trees`. Deeper reads take depths 1-7. Lists follow every page;
30
+ `--limit` caps the total and the output's `next_cursor` resumes with
31
+ `--cursor`.
32
+
33
+ Legs are measured by headcount only: each leg counts the child's seat plus its
34
+ `downline_count`. The API has no leg volume. Counts include retained seats of
35
+ discarded members. The API has no per-leg count endpoint, so `legs`,
36
+ `strong-leg` and `weak-leg` read each direct child's counts with its own
37
+ request. They refuse a node with more than 200 direct children; pass
38
+ `--limit <n>` to count up to `n`. The children read leaves out a direct child
39
+ whose member was discarded, while the node's own counts keep it. There is no
40
+ single read of a node's children with their counts, so `legs` reads the node's
41
+ counts, then the children and each child's counts twice, then the node's counts
42
+ again, and uses them only when every read agrees; otherwise it reads again (up
43
+ to three times). When the node has more children than were listed, the output
44
+ carries `unlisted` (`children`, `seats`, `ranked`, `unknown_empty_legs`). A
45
+ single unlisted child is ranked with its exact size and `unlisted: true`, in the
46
+ named leg left free when there is only one. Empty named legs are not listed
47
+ when they cannot be told from an unlisted child's leg. `strong-leg` and
48
+ `weak-leg` name a leg only when the unlisted children cannot change it; a
49
+ strong leg that has more seats than all of them together still holds.
50
+ Otherwise they return `leg: null` with `best_listed`, the pick among the listed
51
+ legs.
52
+
53
+ Reads need a company admin with `members.view` (or the legacy `users.view`);
54
+ a company API token (`C-...`) with that permission works too. Naming a tree
55
+ by key, including the default `placement`, looks it up with
56
+ `GET /api/company/v2026-10/genealogy/tree-keys`, which lists only each tree's
57
+ id, key and name and needs the same `members.view`; without it, the key is
58
+ looked up in the tree definitions, which need `developer.view`. An API
59
+ without the `tree-keys` endpoint (it answers 404) falls back the same way. Only `fluid genealogy
60
+ trees`, which prints the full tree definitions, needs `developer.view`, because
61
+ those definitions carry company configuration. `legs` reads a tree's leg names
62
+ from those definitions too: without `developer.view` it still ranks the
63
+ occupied legs but cannot list empty named legs, so `weak-leg` returns
64
+ `leg: null` with `best_listed` (an unseen empty leg may be the weak one) and
65
+ the report carries `leg_names_known: false`.
66
+
67
+ Writes: `place`, `move`, `move-requests approve|reject`. They need an admin
68
+ user's login: run `fluid login` as a company admin with `members.manage` (for
69
+ `place` and `move`) or `team_moves.approve` (for `move-requests
70
+ approve|reject`). A company API token (`C-...`) is refused with 403 "A company
71
+ admin identity is required ...". Writes change the live
72
+ tree, so they ask for confirmation unless you pass `--yes`. `move --validate`
73
+ (or `--dry-run`) previews without writing. `move-requests list|show` and
74
+ `move-jobs list|show` read move receipts.
75
+
76
+ Tree definitions: `trees create`, `trees update <tree>` and `trees delete
77
+ <tree>` (a key or UUID) write the company's tree registry. They need an admin
78
+ login with `developer.update` (a company API token works only if its role
79
+ grants it). Every writable field is a flag: `--key`, `--name`, `--kind
80
+ unilevel|binary|matrix`, `--role`, `--leg-names left,right`,
81
+ `--placement-rule`, `--movement-approval immediate|require_approval`,
82
+ `--holding-tank-days`, `--max-depth`, `--max-movement-levels`,
83
+ `--movement-window-days`, `--volume-on-move`, `--volume-key`,
84
+ `--allow-multiple-positions true|false`, `--authority
85
+ authoritative|replica|unconfigured`, `--owning-company-integration-id`,
86
+ `--upstream-tree-ref` and `--external-integration-market-id`; `none` clears a
87
+ nullable field. Create needs `--key`, `--name`, `--kind` and `--authority`;
88
+ binary and matrix trees also need `--volume-on-move`. Binary and matrix trees accept the default rules `exact`, `first_empty`,
89
+ `left`, `right` and `defer`; unilevel trees `exact`, `under_enroller` and
90
+ `mirror_enrollment` (other rules are refused with a typed error). `place
91
+ --parent` with neither `--leg` nor `--rule` uses the tree's default rule. Only an authoritative
92
+ tree takes placements, and placing in a binary or matrix tree also needs the
93
+ company's `GENEALOGY_LEGGED_TREES` flag. Update sends only the flags given, at
94
+ the tree's current `lock_version` unless `--lock-version` pins one; an
95
+ occupied tree can only be renamed. Delete fails with `tree_has_nodes` while
96
+ the tree holds any seat, and the default `enrollment` and `placement` trees
97
+ can't be deleted. Each write shows the request and asks first unless `--yes`;
98
+ `--dry-run` prints it and sends nothing.
99
+
100
+ ```bash
101
+ fluid genealogy trees create --key team-binary --name "Team Binary" --kind binary \
102
+ --authority authoritative --volume-on-move follows_structure --dry-run
103
+ fluid genealogy trees update team-binary --movement-approval require_approval --yes
104
+ fluid genealogy trees delete team-binary --yes
105
+ ```
106
+
107
+ `trees mirror [tree]` (default `placement`) backfills the placement tree
108
+ from the enrollment tree for a company whose placement tree uses
109
+ `mirror_enrollment` and has no legacy placement tree: every enrolled member
110
+ without a placement seat is seated under their enroller's seat, roots
111
+ first, and unpinned seats that no longer match are re-parented. Pinned
112
+ seats stay. It is idempotent and needs an admin login with
113
+ `members.manage`. Member writes keep the trees mirrored afterwards.
114
+
115
+ `--dry-run` reports the counts at once and writes nothing (up to 5,000
116
+ enrollment seats). A real run asks first unless `--yes`, then queues a
117
+ background run with no seat limit and prints it (`mirror_run`, plus
118
+ `started: false` when the company already had a run queued or running;
119
+ there is one at a time). `--wait` follows the run, printing a progress line
120
+ per reading on stderr (every `--interval` seconds, default 2), then prints
121
+ the finished run and exits non-zero if it `failed` or `halted`. A halted
122
+ run kept the batches it wrote; run the mirror again to finish.
123
+
124
+ ```bash
125
+ fluid genealogy trees mirror --dry-run
126
+ fluid genealogy trees mirror --yes --wait
127
+ fluid genealogy trees mirror-runs list --status running
128
+ fluid genealogy trees mirror-runs show <run-id>
129
+ ```
130
+
131
+ Every command except `tree` prints JSON. Failures print a JSON `error` (with the
132
+ API's `type` when there is one) on stderr and exit non-zero.
@@ -0,0 +1,15 @@
1
+ import { CommandContext, FluidPlugin } from "@fluid-app/fluid-cli";
2
+ import { Command } from "commander";
3
+
4
+ //#region src/index.d.ts
5
+ /**
6
+ * Placements and moves change the live tree and need an explicit yes.
7
+ * Scripts and agents pass `--yes`; an interactive terminal is asked. With
8
+ * neither, nothing runs.
9
+ */
10
+ declare function confirmChange(question: string, yes: boolean, interactive?: boolean): Promise<void>;
11
+ declare function registerGenealogyCommands(parent: Command, ctx: CommandContext): void;
12
+ declare const plugin: FluidPlugin;
13
+ //#endregion
14
+ export { confirmChange, plugin as default, registerGenealogyCommands };
15
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;AAwEA;;;iBAAsB,aAAA,CACpB,QAAA,UACA,GAAA,WACA,WAAA,aACC,OAAA;AAAA,iBAyWa,yBAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;AAAA,cA+dD,MAAA,EAAQ,WAAA"}