OverviewFilesCommitsIssuesDocsReleasesPeers

keet-cli commits / 965ead5

Expose full Keet API: introspect, identity, rooms, DMs, files, members, contacts, sub-APIs
Add ~40 new wrapped RPC routes (89 total, up from 48): - Introspection: enumerate live methods on all 5 sub-APIs (core/call/preferences/network/debug) - Identity: confirm, store, register, checkName, lookup, lookupMember - Room config: get/set config, bookmark, preferences, search, unread - DMs: list (via getDmRequestsByStatus), requests, accept, reject - Files: send, list, info, download, cancel, remove, clear - Members: active, mods, count, connected, search - Contacts: info, self, dmFromContact - Sub-APIs: network query, preferences get/set, debug stats - System: version, stats CLI commands: identity confirm/store/register/check/lookup, config, dms, dm accept/reject, files, download, version, introspect Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
965ead5eb842b587c50c7e200a924883ce6f3225
by plankatron <fleeky@gmail.com> · 2026-02-14T14:46:33.000Z

Changed files (2) +566 -2

index.js +245 -2
……@@ -50,21 +50,49 @@ Daemon Commands:
5050 daemon stop Stop running daemon
5151 daemon status Check if daemon is running
5252
53Client Commands (require running daemon):
53Identity:
5454 identity Show current identity
5555 identity recovery Show recovery mnemonic phrase
5656 identity restore <phrase> Restore from mnemonic
57 identity confirm Confirm recovery phrase (onboarding step)
58 identity store <phrase> Store recovery phrase for HD key derivation
59 identity register <name> Register username (must include number: name0)
60 identity check <name> Check if username is available
61 identity lookup <name> Lookup user by username
62
63Rooms:
5764 rooms List recent rooms
5865 room <roomId> Show room info
5966 join <link|token> Join room via invite link or raw token
6067 create <title> Create a new room
6168 invite <roomId> Create an invite for a room
69 leave <roomId> Leave a room
70
71Room Config:
72 config <roomId> Show room config
73 config <roomId> <key> <v> Set room config (title, description, etc.)
74
75Messages:
6276 send <roomId> <message> Send a chat message
6377 read <roomId> [count] Read recent messages (default: 20)
6478 watch <roomId> Watch for new messages (SSE stream)
79
80Members:
6581 members <roomId> List room members
66 leave <roomId> Leave a room
82
83DMs:
84 dms List DM rooms
85 dm accept <roomId> Accept DM request
86 dm reject <roomId> Reject DM request
87
88Files:
89 files <roomId> List local files in room
90 download <roomId> <id> Download a file
91
92System:
6793 status Daemon status (uptime, rooms, identity)
94 version Keet version info
95 introspect [api] List all methods on api (core|call|preferences|network|debug)
6896 help Show this help
6997
7098Quick Start:
……@@ -176,6 +204,27 @@ try {
176204 case 'status':
177205 await clientStatus()
178206 break
207 case 'config':
208 await clientConfig(args[1], args[2], args.slice(3).join(' '))
209 break
210 case 'dms':
211 await clientDms()
212 break
213 case 'dm':
214 await clientDm(args[1], args[2])
215 break
216 case 'files':
217 await clientFiles(args[1])
218 break
219 case 'download':
220 await clientDownload(args[1], args[2])
221 break
222 case 'version':
223 await clientVersion()
224 break
225 case 'introspect':
226 await clientIntrospect(args[1])
227 break
179228 default:
180229 console.error(`Unknown command: ${command}`)
181230 printHelp()
……@@ -219,6 +268,73 @@ async function clientIdentity (subArgs) {
219268 }
220269 return
221270 }
271 if (sub === 'confirm') {
272 const result = await rpc('identity.confirm')
273 if (result.ok) {
274 console.log('[keet-cli] Recovery phrase confirmed.')
275 } else {
276 console.error('[keet-cli] Error:', result.error)
277 }
278 return
279 }
280 if (sub === 'store') {
281 const phrase = subArgs.slice(1).join(' ')
282 if (!phrase) {
283 console.error('Usage: identity store <mnemonic phrase>')
284 return
285 }
286 const result = await rpc('identity.store', { phrase })
287 if (result.ok) {
288 console.log('[keet-cli] Recovery phrase stored.')
289 } else {
290 console.error('[keet-cli] Error:', result.error)
291 }
292 return
293 }
294 if (sub === 'register') {
295 const username = subArgs[1]
296 if (!username) {
297 console.error('Usage: identity register <username> (must include number: name0)')
298 return
299 }
300 const result = await rpc('identity.register', { username })
301 if (result.ok) {
302 console.log('[keet-cli] Username registration initiated:', username)
303 console.log(' (async — wait ~15s, then check with: identity)')
304 } else {
305 console.error('[keet-cli] Error:', result.error)
306 }
307 return
308 }
309 if (sub === 'check') {
310 const username = subArgs[1]
311 if (!username) {
312 console.error('Usage: identity check <username>')
313 return
314 }
315 const result = await rpc('identity.checkName', { username })
316 if (result.ok) {
317 console.log(`[keet-cli] "${username}": ${result.data.available ? 'available' : 'taken'}`)
318 } else {
319 console.error('[keet-cli] Error:', result.error)
320 }
321 return
322 }
323 if (sub === 'lookup') {
324 const username = subArgs[1]
325 if (!username) {
326 console.error('Usage: identity lookup <username>')
327 return
328 }
329 const result = await rpc('identity.lookup', { username })
330 if (result.ok) {
331 console.log('[keet-cli] Lookup result:')
332 console.log(JSON.stringify(result.data, null, 2))
333 } else {
334 console.error('[keet-cli] Error:', result.error)
335 }
336 return
337 }
222338 // Default: show identity
223339 const result = await get('/rpc/identity')
224340 if (result.ok && result.data) {
……@@ -458,6 +574,133 @@ async function clientStatus () {
458574 }
459575}
460576
577async function clientConfig (roomId, key, value) {
578 if (!roomId) {
579 console.error('Usage: config <roomId> [key] [value]')
580 return
581 }
582 if (!key) {
583 // Show config
584 const result = await rpc('room.config', { roomId })
585 if (result.ok) {
586 console.log('[keet-cli] Room config:')
587 console.log(JSON.stringify(result.data, null, 2))
588 } else {
589 console.error('[keet-cli] Error:', result.error)
590 }
591 return
592 }
593 // Set config
594 if (!value) {
595 console.error('Usage: config <roomId> <key> <value>')
596 return
597 }
598 const result = await rpc('room.config.set', { roomId, key, value })
599 if (result.ok) {
600 console.log(`[keet-cli] Config "${key}" updated.`)
601 } else {
602 console.error('[keet-cli] Error:', result.error)
603 }
604}
605
606async function clientDms () {
607 const result = await rpc('dm.list')
608 if (!result.ok) {
609 console.error('[keet-cli] Error:', result.error)
610 return
611 }
612 const dms = Array.isArray(result.data) ? result.data : []
613 if (dms.length === 0) {
614 console.log('[keet-cli] No DMs.')
615 return
616 }
617 console.log(`[keet-cli] ${dms.length} DM(s):`)
618 for (const dm of dms) {
619 console.log(` ${dm.roomId || dm.id || '?'} ${dm.title || dm.name || '(unnamed)'}`)
620 }
621}
622
623async function clientDm (action, roomId) {
624 if (!action) {
625 console.error('Usage: dm accept|reject <roomId>')
626 return
627 }
628 if (action === 'accept') {
629 if (!roomId) { console.error('Usage: dm accept <roomId>'); return }
630 const result = await rpc('dm.accept', { roomId })
631 if (result.ok) {
632 console.log('[keet-cli] DM request accepted.')
633 } else {
634 console.error('[keet-cli] Error:', result.error)
635 }
636 } else if (action === 'reject') {
637 if (!roomId) { console.error('Usage: dm reject <roomId>'); return }
638 const result = await rpc('dm.reject', { roomId })
639 if (result.ok) {
640 console.log('[keet-cli] DM request rejected.')
641 } else {
642 console.error('[keet-cli] Error:', result.error)
643 }
644 } else {
645 console.error('Usage: dm accept|reject <roomId>')
646 }
647}
648
649async function clientFiles (roomId) {
650 if (!roomId) {
651 console.error('Usage: files <roomId>')
652 return
653 }
654 const result = await rpc('file.list', { roomId })
655 if (!result.ok) {
656 console.error('[keet-cli] Error:', result.error)
657 return
658 }
659 const files = Array.isArray(result.data) ? result.data : []
660 if (files.length === 0) {
661 console.log('[keet-cli] No files.')
662 return
663 }
664 console.log(`[keet-cli] ${files.length} file(s):`)
665 for (const f of files) {
666 console.log(` ${f.id || f.fileId || '?'} ${f.name || f.filename || '(unnamed)'} ${f.size || '?'} bytes`)
667 }
668}
669
670async function clientDownload (roomId, fileId) {
671 if (!roomId || !fileId) {
672 console.error('Usage: download <roomId> <fileId>')
673 return
674 }
675 const result = await rpc('file.download', { roomId, fileId })
676 if (result.ok) {
677 console.log('[keet-cli] Download started.')
678 } else {
679 console.error('[keet-cli] Error:', result.error)
680 }
681}
682
683async function clientVersion () {
684 const result = await rpc('version')
685 if (result.ok) {
686 console.log('[keet-cli] Version:', JSON.stringify(result.data, null, 2))
687 } else {
688 console.error('[keet-cli] Error:', result.error)
689 }
690}
691
692async function clientIntrospect (api) {
693 const result = await rpc('introspect', { api: api || 'core' })
694 if (!result.ok) {
695 console.error('[keet-cli] Error:', result.error)
696 return
697 }
698 console.log(`[keet-cli] ${result.data.api} API — ${result.data.count} methods:`)
699 for (const method of result.data.methods) {
700 console.log(` ${method}`)
701 }
702}
703
461704// ============================================================
462705// HTTP helpers
463706// ============================================================
lib/router.js +321 -0
……@@ -64,6 +64,65 @@ export class Router {
6464 case 'raw': return this._raw(params)
6565 case 'status': return this._status()
6666 case 'shutdown': return this._shutdown()
67
68 // --- Introspection ---
69 case 'introspect': return this._introspect(params)
70
71 // --- Identity & Username ---
72 case 'identity.confirm': return this._identityConfirm()
73 case 'identity.store': return this._identityStore(params)
74 case 'identity.register': return this._identityRegister(params)
75 case 'identity.checkName': return this._identityCheckName(params)
76 case 'identity.lookup': return this._identityLookup(params)
77 case 'identity.lookupMember': return this._identityLookupMember(params)
78
79 // --- Room Config & Management ---
80 case 'room.config': return this._roomConfig(params)
81 case 'room.config.set': return this._roomConfigSet(params)
82 case 'room.bookmark': return this._roomBookmark(params)
83 case 'room.unbookmark': return this._roomUnbookmark(params)
84 case 'room.preferences': return this._roomPreferences(params)
85 case 'room.preferences.set': return this._roomPreferencesSet(params)
86 case 'room.search': return this._roomSearch(params)
87 case 'room.unread': return this._roomUnread(params)
88
89 // --- DM Management ---
90 case 'dm.list': return this._dmList()
91 case 'dm.requests': return this._dmRequests(params)
92 case 'dm.accept': return this._dmAccept(params)
93 case 'dm.reject': return this._dmReject(params)
94
95 // --- File Management ---
96 case 'file.send': return this._fileSend(params)
97 case 'file.list': return this._fileList(params)
98 case 'file.info': return this._fileInfo(params)
99 case 'file.download': return this._fileDownload(params)
100 case 'file.cancel': return this._fileCancel(params)
101 case 'file.remove': return this._fileRemove(params)
102 case 'file.clear': return this._fileClear(params)
103
104 // --- Advanced Members ---
105 case 'members.active': return this._membersActive(params)
106 case 'members.mods': return this._membersMods(params)
107 case 'members.count': return this._membersCount(params)
108 case 'members.connected': return this._membersConnected(params)
109 case 'members.search': return this._membersSearch(params)
110
111 // --- Contacts ---
112 case 'contact.info': return this._contactInfo(params)
113 case 'contact.self': return this._contactSelf()
114 case 'dm.fromContact': return this._dmFromContact(params)
115
116 // --- Sub-APIs ---
117 case 'network': return this._networkQuery()
118 case 'preferences': return this._preferencesQuery()
119 case 'preferences.set': return this._preferencesSet(params)
120 case 'debug.stats': return this._debugStats(params)
121
122 // --- System ---
123 case 'version': return this._getVersion()
124 case 'stats': return this._getStats()
125
67126 default: throw new Error(`Unknown method: ${method}`)
68127 }
69128 }
……@@ -511,4 +570,266 @@ export class Router {
511570 }, 500)
512571 return { ok: true, message: 'shutting down' }
513572 }
573
574 // --- Introspection ---
575
576 async _introspect (params) {
577 const target = params.api || 'core'
578 const api = target === 'core' ? this.backend.api.core
579 : target === 'call' ? this.backend.api.call
580 : target === 'preferences' ? this.backend.api.preferences
581 : target === 'network' ? this.backend.api.network
582 : target === 'debug' ? this.backend.api.debug
583 : null
584 if (!api) throw new Error(`Unknown API: ${target}`)
585 const methods = []
586 for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(api))) {
587 if (key === 'constructor') continue
588 if (typeof api[key] === 'function') methods.push(key)
589 }
590 for (const key of Object.keys(api)) {
591 if (typeof api[key] === 'function' && !methods.includes(key)) methods.push(key)
592 }
593 return { api: target, methods: methods.sort(), count: methods.length }
594 }
595
596 // --- Identity & Username ---
597
598 async _identityConfirm () {
599 await this.backend.api.core.confirmRecoveryPhrase()
600 return { ok: true }
601 }
602
603 async _identityStore (params) {
604 if (!params.phrase) throw new Error('Missing required param: phrase')
605 await this.backend.api.core.storeRecoveryPhrase(params.phrase)
606 return { ok: true }
607 }
608
609 async _identityRegister (params) {
610 if (!params.username) throw new Error('Missing required param: username')
611 const result = await this.backend.api.core.registerUsername(params.username)
612 return { ok: true, result }
613 }
614
615 async _identityCheckName (params) {
616 if (!params.username) throw new Error('Missing required param: username')
617 const available = await this.backend.api.core.checkUsername(params.username)
618 return { username: params.username, available }
619 }
620
621 async _identityLookup (params) {
622 if (!params.username) throw new Error('Missing required param: username')
623 return this.backend.api.core.lookupUsername(params.username)
624 }
625
626 async _identityLookupMember (params) {
627 if (!params.roomId) throw new Error('Missing required param: roomId')
628 if (!params.memberId) throw new Error('Missing required param: memberId')
629 return this.backend.api.core.lookupUserByMemberId(params.roomId, params.memberId)
630 }
631
632 // --- Room Config & Management ---
633
634 async _roomConfig (params) {
635 if (!params.roomId) throw new Error('Missing required param: roomId')
636 return this.backend.api.core.getConfig(params.roomId)
637 }
638
639 async _roomConfigSet (params) {
640 if (!params.roomId) throw new Error('Missing required param: roomId')
641 if (!params.key) throw new Error('Missing required param: key')
642 if (params.value === undefined) throw new Error('Missing required param: value')
643 await this.backend.api.core.updateConfig(params.roomId, params.key, params.value)
644 return { ok: true }
645 }
646
647 async _roomBookmark (params) {
648 if (!params.roomId) throw new Error('Missing required param: roomId')
649 await this.backend.api.core.bookmark(params.roomId)
650 return { ok: true }
651 }
652
653 async _roomUnbookmark (params) {
654 if (!params.roomId) throw new Error('Missing required param: roomId')
655 await this.backend.api.core.unbookmark(params.roomId)
656 return { ok: true }
657 }
658
659 async _roomPreferences (params) {
660 if (!params.roomId) throw new Error('Missing required param: roomId')
661 return this.backend.api.core.getRoomPreferences(params.roomId)
662 }
663
664 async _roomPreferencesSet (params) {
665 if (!params.roomId) throw new Error('Missing required param: roomId')
666 const { roomId, ...prefs } = params
667 await this.backend.api.core.updateRoomPreferences(roomId, prefs)
668 return { ok: true }
669 }
670
671 async _roomSearch (params) {
672 if (!params.query) throw new Error('Missing required param: query')
673 return this.backend.api.core.searchRooms(params.query)
674 }
675
676 async _roomUnread (params) {
677 if (!params.roomId) throw new Error('Missing required param: roomId')
678 if (params.unread === undefined) throw new Error('Missing required param: unread')
679 await this.backend.api.core.setRoomUnread(params.roomId, params.unread)
680 return { ok: true }
681 }
682
683 // --- DM Management ---
684
685 async _dmList () {
686 // No getDmList — use getDmRequestsByStatus(1) for accepted DMs
687 return this.backend.api.core.getDmRequestsByStatus(1)
688 }
689
690 async _dmRequests (params) {
691 const status = params.status || 3 // default: pending
692 return this.backend.api.core.getDmRequestsByStatus(status)
693 }
694
695 async _dmAccept (params) {
696 if (!params.roomId) throw new Error('Missing required param: roomId')
697 await this.backend.api.core.acceptDmRequest(params.roomId)
698 return { ok: true }
699 }
700
701 async _dmReject (params) {
702 if (!params.roomId) throw new Error('Missing required param: roomId')
703 await this.backend.api.core.rejectDmRequest(params.roomId)
704 return { ok: true }
705 }
706
707 // --- File Management ---
708
709 async _fileSend (params) {
710 if (!params.roomId) throw new Error('Missing required param: roomId')
711 if (!params.file) throw new Error('Missing required param: file')
712 return this.backend.api.core.sendFile(params.roomId, params.file)
713 }
714
715 async _fileList (params) {
716 if (!params.roomId) throw new Error('Missing required param: roomId')
717 return this.backend.api.core.getLocalFiles(params.roomId)
718 }
719
720 async _fileInfo (params) {
721 if (!params.roomId) throw new Error('Missing required param: roomId')
722 if (!params.fileId) throw new Error('Missing required param: fileId')
723 return this.backend.api.core.getFileEntry(params.roomId, params.fileId)
724 }
725
726 async _fileDownload (params) {
727 if (!params.roomId) throw new Error('Missing required param: roomId')
728 if (!params.fileId) throw new Error('Missing required param: fileId')
729 await this.backend.api.core.startDownload(params.roomId, params.fileId)
730 return { ok: true }
731 }
732
733 async _fileCancel (params) {
734 if (!params.roomId) throw new Error('Missing required param: roomId')
735 if (!params.fileId) throw new Error('Missing required param: fileId')
736 await this.backend.api.core.cancelDownload(params.roomId, params.fileId)
737 return { ok: true }
738 }
739
740 async _fileRemove (params) {
741 if (!params.roomId) throw new Error('Missing required param: roomId')
742 if (!params.fileId) throw new Error('Missing required param: fileId')
743 await this.backend.api.core.removeFile(params.roomId, params.fileId)
744 return { ok: true }
745 }
746
747 async _fileClear (params) {
748 if (!params.roomId) throw new Error('Missing required param: roomId')
749 if (!params.fileId) throw new Error('Missing required param: fileId')
750 await this.backend.api.core.clearFile(params.roomId, params.fileId)
751 return { ok: true }
752 }
753
754 // --- Advanced Members ---
755
756 async _membersActive (params) {
757 if (!params.roomId) throw new Error('Missing required param: roomId')
758 return this.backend.api.core.getActiveMembers(params.roomId)
759 }
760
761 async _membersMods (params) {
762 if (!params.roomId) throw new Error('Missing required param: roomId')
763 return this.backend.api.core.getModerators(params.roomId)
764 }
765
766 async _membersCount (params) {
767 if (!params.roomId) throw new Error('Missing required param: roomId')
768 return this.backend.api.core.getMemberCount(params.roomId)
769 }
770
771 async _membersConnected (params) {
772 if (!params.roomId) throw new Error('Missing required param: roomId')
773 return this.backend.api.core.getConnectedMembers(params.roomId)
774 }
775
776 async _membersSearch (params) {
777 if (!params.roomId) throw new Error('Missing required param: roomId')
778 if (!params.query) throw new Error('Missing required param: query')
779 return this.backend.api.core.getSearchMembers(params.roomId, params.query)
780 }
781
782 // --- Contacts ---
783
784 async _contactInfo (params) {
785 if (!params.memberId) throw new Error('Missing required param: memberId')
786 return this.backend.api.core.getContactInfo(params.memberId)
787 }
788
789 async _contactSelf () {
790 return this.backend.api.core.getSelfContactInfo()
791 }
792
793 async _dmFromContact (params) {
794 if (!params.memberId) throw new Error('Missing required param: memberId')
795 return this.backend.api.core.sendDMFromContact(params.memberId)
796 }
797
798 // --- Sub-APIs ---
799
800 async _networkQuery () {
801 const api = this.backend.api.network
802 if (!api) throw new Error('Network API not available')
803 return api.query()
804 }
805
806 async _preferencesQuery () {
807 const api = this.backend.api.preferences
808 if (!api) throw new Error('Preferences API not available')
809 return api.query()
810 }
811
812 async _preferencesSet (params) {
813 const api = this.backend.api.preferences
814 if (!api) throw new Error('Preferences API not available')
815 await api.update(params)
816 return { ok: true }
817 }
818
819 async _debugStats (params) {
820 const api = this.backend.api.debug
821 if (!api) throw new Error('Debug API not available')
822 if (!params.roomId) throw new Error('Missing required param: roomId')
823 return api.getCurrentRoomStats(params.roomId)
824 }
825
826 // --- System (extended) ---
827
828 async _getVersion () {
829 return this.backend.api.core.getVersion()
830 }
831
832 async _getStats () {
833 return this.backend.api.core.getStats()
834 }
514835}