OverviewFilesCommitsIssuesDocsReleasesPeers

keet-cli commits / fde4593

Add graph CLI commands with algo file support
graph clusters/edges/compute commands with --algo=<file> for loading JSON algo configs, --features/--threshold/--minSize for inline params. Bundled configs: members-only, topics-only, category-only, combined. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fde459318b945840c65015ee2e71d71817828dee
by plankatron <fleeky@gmail.com> · 2026-02-23T02:16:38.000Z

Changed files (5) +197 -0

graph-algos/category-only.json +6 -0
……@@ -0,0 +1,6 @@
1{
2 "name": "category-only",
3 "features": [{ "type": "category", "weight": 1.0 }],
4 "threshold": 0.1,
5 "minSize": 2
6}
graph-algos/combined.json +9 -0
……@@ -0,0 +1,9 @@
1{
2 "name": "combined",
3 "features": [
4 { "type": "members", "weight": 1.0 },
5 { "type": "topics", "weight": 0.5 }
6 ],
7 "threshold": 0.4,
8 "minSize": 2
9}
graph-algos/members-only.json +6 -0
……@@ -0,0 +1,6 @@
1{
2 "name": "members-only",
3 "features": [{ "type": "members", "weight": 1.0 }],
4 "threshold": 0.5,
5 "minSize": 2
6}
graph-algos/topics-only.json +6 -0
……@@ -0,0 +1,6 @@
1{
2 "name": "topics-only",
3 "features": [{ "type": "topics", "weight": 1.0 }],
4 "threshold": 0.3,
5 "minSize": 2
6}
index.js +170 -0
……@@ -104,6 +104,14 @@ Metadata (Device Pairing):
104104 meta invite Get invite key for device pairing
105105 meta join <key> Pair with remote device's MetadataBase
106106
107Graph (MI Clustering):
108 graph clusters Show current clusters (default algo)
109 graph edges Show MI edges between rooms
110 graph compute Compute with default algo (members-only)
111 graph compute --algo=<f> Compute with algo file (JSON config)
112 graph compute --features=topics --threshold=0.3
113 Compute with inline params
114
107115System:
108116 status Daemon status (uptime, rooms, identity)
109117 version Keet version info
……@@ -285,6 +293,9 @@ try {
285293 case 'meta':
286294 await clientMeta(args.slice(1))
287295 break
296 case 'graph':
297 await clientGraph(args.slice(1))
298 break
288299 default:
289300 console.error(`Unknown command: ${command}`)
290301 printHelp()
……@@ -794,4 +805,163 @@ async function clientMeta (subArgs) {
794805 console.error('Usage: meta [key|invite|join <key>]')
795806}
796807
808async function clientGraph (subArgs) {
809 // Parse --algo=<path>, --features=X, --threshold=N, --minSize=N from subArgs
810 let sub = null
811 let algoPath = null
812 let inlineFeatures = null
813 let inlineThreshold = null
814 let inlineMinSize = null
815 let force = false
816
817 const positional = []
818 for (const arg of subArgs) {
819 if (arg.startsWith('--algo=')) {
820 algoPath = arg.slice(7)
821 } else if (arg.startsWith('--features=')) {
822 inlineFeatures = arg.slice(11)
823 } else if (arg.startsWith('--threshold=')) {
824 inlineThreshold = parseFloat(arg.slice(12))
825 } else if (arg.startsWith('--minSize=')) {
826 inlineMinSize = parseInt(arg.slice(10))
827 } else if (arg === '--force') {
828 force = true
829 } else {
830 positional.push(arg)
831 }
832 }
833
834 sub = positional[0] || 'clusters'
835
836 if (sub === 'compute') {
837 const params = { force }
838
839 if (algoPath) {
840 // Load algo file
841 try {
842 const fs = await import('fs')
843 const raw = fs.readFileSync(algoPath, 'utf8')
844 params.algo = JSON.parse(raw)
845 } catch (err) {
846 console.error('[keet-cli] Failed to load algo file:', algoPath, '-', err.message)
847 return
848 }
849 } else if (inlineFeatures) {
850 // Build inline algo from flags
851 const features = inlineFeatures.split(',').map(t => ({ type: t.trim(), weight: 1.0 }))
852 params.algo = {
853 name: 'inline-' + inlineFeatures,
854 features,
855 threshold: inlineThreshold ?? 0.5,
856 minSize: inlineMinSize ?? 2
857 }
858 }
859
860 const result = await rpc('graph.compute', params)
861 if (!result.ok) {
862 console.error('[keet-cli] Error:', result.error)
863 return
864 }
865 printGraphResult(result.data)
866 return
867 }
868
869 if (sub === 'clusters') {
870 const params = {}
871 if (algoPath) {
872 try {
873 const fs = await import('fs')
874 params.algo = JSON.parse(fs.readFileSync(algoPath, 'utf8'))
875 } catch (err) {
876 console.error('[keet-cli] Failed to load algo file:', err.message)
877 return
878 }
879 }
880 const result = await rpc('graph.clusters', params)
881 if (!result.ok) {
882 console.error('[keet-cli] Error:', result.error)
883 return
884 }
885 const clusters = Array.isArray(result.data) ? result.data : []
886 if (clusters.length === 0) {
887 console.log('[keet-cli] No clusters found.')
888 return
889 }
890 console.log(`[keet-cli] ${clusters.length} cluster(s):`)
891 for (const c of clusters) {
892 const label = c.label ? ` [${c.label}]` : ''
893 const topics = c.topics?.length ? ` topics: ${c.topics.join(', ')}` : ''
894 console.log(` Cluster ${c.id}${label} — ${c.rooms.length} rooms${topics}`)
895 for (const r of c.rooms) {
896 console.log(` ${r}`)
897 }
898 }
899 return
900 }
901
902 if (sub === 'edges') {
903 const params = {}
904 if (algoPath) {
905 try {
906 const fs = await import('fs')
907 params.algo = JSON.parse(fs.readFileSync(algoPath, 'utf8'))
908 } catch (err) {
909 console.error('[keet-cli] Failed to load algo file:', err.message)
910 return
911 }
912 }
913 const result = await rpc('graph.edges', params)
914 if (!result.ok) {
915 console.error('[keet-cli] Error:', result.error)
916 return
917 }
918 const edges = Array.isArray(result.data) ? result.data : []
919 if (edges.length === 0) {
920 console.log('[keet-cli] No MI edges.')
921 return
922 }
923 console.log(`[keet-cli] ${edges.length} edge(s):`)
924 for (const e of edges) {
925 console.log(` ${e.from.slice(0, 8)}..${e.to.slice(0, 8)} MI: ${e.mi} shared: ${e.shared}`)
926 }
927 return
928 }
929
930 console.error('Usage: graph [clusters|edges|compute] [--algo=<file>] [--features=X] [--threshold=N]')
931}
932
933function printGraphResult (data) {
934 const algo = data.algo || 'default'
935 console.log(`[keet-cli] Graph computed (algo: ${algo})`)
936
937 const clusters = data.clusters || []
938 const edges = data.edges || []
939 const suggestions = data.suggestions || []
940
941 console.log(` ${clusters.length} cluster(s), ${edges.length} edge(s), ${suggestions.length} suggestion(s)`)
942
943 if (clusters.length > 0) {
944 console.log('')
945 console.log(' Clusters:')
946 for (const c of clusters) {
947 const label = c.label ? ` [${c.label}]` : ''
948 const topics = c.topics?.length ? ` topics: ${c.topics.join(', ')}` : ''
949 console.log(` Cluster ${c.id}${label} — ${c.rooms.length} rooms${topics}`)
950 for (const r of c.rooms) {
951 console.log(` ${r}`)
952 }
953 }
954 }
955
956 if (edges.length > 0) {
957 console.log('')
958 console.log(' Top edges:')
959 const top = edges.slice(0, 10)
960 for (const e of top) {
961 console.log(` ${e.from.slice(0, 8)}..${e.to.slice(0, 8)} MI: ${e.mi} shared: ${e.shared}`)
962 }
963 if (edges.length > 10) console.log(` ... and ${edges.length - 10} more`)
964 }
965}
966
797967// (UDS client helpers moved above client switch to avoid TDZ)