kitty-hive 0.6.2 → 0.6.3
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 +41 -35
- package/README.zh.md +42 -36
- package/dist/db.d.ts +3 -0
- package/dist/db.js +43 -1
- package/dist/db.js.map +1 -1
- package/dist/index.js +351 -28
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +1 -1
- package/dist/models.js +1 -1
- package/dist/models.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -334,11 +334,15 @@ async function cmdDbClear() {
|
|
|
334
334
|
}
|
|
335
335
|
async function cmdAgentRemove() {
|
|
336
336
|
const { dbPath } = parseFlags(1);
|
|
337
|
-
// Flags: --key <K>
|
|
338
|
-
// --yes
|
|
337
|
+
// Flags: --key <K> look up by external_key (idempotent: missing = exit 0)
|
|
338
|
+
// --yes skip confirmation (scripts)
|
|
339
|
+
// --transfer-to <a> transfer hosted teams to this agent before deleting
|
|
340
|
+
// --cascade explicitly accept that hosted teams (and all their members' history) will be wiped
|
|
339
341
|
let target = '';
|
|
340
342
|
let externalKey = '';
|
|
341
343
|
let assumeYes = false;
|
|
344
|
+
let transferTo = '';
|
|
345
|
+
let cascade = false;
|
|
342
346
|
for (let i = 2; i < args.length; i++) {
|
|
343
347
|
if (args[i] === '--key' && args[i + 1]) {
|
|
344
348
|
externalKey = args[i + 1];
|
|
@@ -347,6 +351,13 @@ async function cmdAgentRemove() {
|
|
|
347
351
|
else if (args[i] === '--yes' || args[i] === '-y') {
|
|
348
352
|
assumeYes = true;
|
|
349
353
|
}
|
|
354
|
+
else if (args[i] === '--transfer-to' && args[i + 1]) {
|
|
355
|
+
transferTo = args[i + 1];
|
|
356
|
+
i++;
|
|
357
|
+
}
|
|
358
|
+
else if (args[i] === '--cascade') {
|
|
359
|
+
cascade = true;
|
|
360
|
+
}
|
|
350
361
|
else if (args[i] === '--port' || args[i] === '-p' || args[i] === '--db') {
|
|
351
362
|
i++;
|
|
352
363
|
}
|
|
@@ -365,22 +376,31 @@ async function cmdAgentRemove() {
|
|
|
365
376
|
agent = row;
|
|
366
377
|
}
|
|
367
378
|
else if (target) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
379
|
+
// ID is unique → try ID first; only fall back to display_name match if no ID hit.
|
|
380
|
+
// This avoids a collision where someone registered an agent whose display_name
|
|
381
|
+
// is literally another agent's ID.
|
|
382
|
+
const byId = db.prepare('SELECT id, display_name FROM agents WHERE id = ?').get(target);
|
|
383
|
+
if (byId) {
|
|
384
|
+
agent = byId;
|
|
372
385
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
console.log(`
|
|
377
|
-
|
|
386
|
+
else {
|
|
387
|
+
const matches = db.prepare('SELECT id, display_name FROM agents WHERE display_name = ?').all(target);
|
|
388
|
+
if (matches.length === 0) {
|
|
389
|
+
console.log(`Agent "${target}" not found.`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
if (matches.length > 1) {
|
|
393
|
+
console.log(`"${target}" matches ${matches.length} agents. Use id to disambiguate.`);
|
|
394
|
+
for (const m of matches)
|
|
395
|
+
console.log(` ${m.id} ${m.display_name}`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
agent = matches[0];
|
|
378
399
|
}
|
|
379
|
-
agent = matches[0];
|
|
380
400
|
}
|
|
381
401
|
else {
|
|
382
402
|
if (!isInteractive()) {
|
|
383
|
-
console.log('Usage: kitty-hive agent remove <name-or-id> | --key <key> [--yes]');
|
|
403
|
+
console.log('Usage: kitty-hive agent remove <name-or-id> | --key <key> [--yes] [--transfer-to <agent>] [--cascade]');
|
|
384
404
|
process.exit(1);
|
|
385
405
|
}
|
|
386
406
|
const id = await pickLocalAgent(db, 'Remove which agent?');
|
|
@@ -391,16 +411,121 @@ async function cmdAgentRemove() {
|
|
|
391
411
|
process.exit(1);
|
|
392
412
|
}
|
|
393
413
|
const name = agent.display_name;
|
|
414
|
+
// --- Hosted-team pre-check ---
|
|
415
|
+
const dbMod = await import('./db.js');
|
|
416
|
+
const hostedTeams = dbMod.listTeamsHostedBy(agent.id);
|
|
417
|
+
const hostedRows = hostedTeams.map(t => {
|
|
418
|
+
const members = dbMod.getTeamMembers(t.id);
|
|
419
|
+
return {
|
|
420
|
+
team: t,
|
|
421
|
+
memberCount: members.length,
|
|
422
|
+
otherMemberCount: members.filter(m => m.agent_id !== agent.id).length,
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
if (hostedRows.length > 0) {
|
|
426
|
+
console.log(`\n⚠ Agent "${name}" hosts ${hostedRows.length} team(s):`);
|
|
427
|
+
for (const r of hostedRows) {
|
|
428
|
+
console.log(` • ${r.team.name} (${r.team.id}) — ${r.memberCount} member(s), ${r.otherMemberCount} other`);
|
|
429
|
+
}
|
|
430
|
+
console.log('');
|
|
431
|
+
}
|
|
432
|
+
// Decision is only required when at least one hosted team has *other* members.
|
|
433
|
+
// A team where the leaving agent is the sole member is safe to cascade-delete silently
|
|
434
|
+
// (no other history is destroyed), but we still confirm overall removal below.
|
|
435
|
+
let transferAgent;
|
|
436
|
+
const needsHostDecision = hostedRows.some(r => r.otherMemberCount > 0);
|
|
437
|
+
if (needsHostDecision) {
|
|
438
|
+
if (transferTo) {
|
|
439
|
+
const t = db.prepare('SELECT id, display_name FROM agents WHERE id = ? OR display_name = ?').get(transferTo, transferTo);
|
|
440
|
+
if (!t) {
|
|
441
|
+
console.error(`--transfer-to: agent "${transferTo}" not found.`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
transferAgent = t;
|
|
445
|
+
}
|
|
446
|
+
else if (!cascade && !assumeYes && isInteractive()) {
|
|
447
|
+
// Repeat the team listing inside the prompt itself — the warning lines
|
|
448
|
+
// printed earlier may be scrolled off-screen by clack's render area.
|
|
449
|
+
const teamLines = hostedRows.map(r => {
|
|
450
|
+
const marker = r.otherMemberCount > 0 ? '⚠' : ' ';
|
|
451
|
+
return ` ${marker} ${r.team.name} (${r.memberCount} member${r.memberCount === 1 ? '' : 's'}${r.otherMemberCount > 0 ? `, ${r.otherMemberCount} other` : ''})`;
|
|
452
|
+
}).join('\n');
|
|
453
|
+
const promptMsg = `Agent "${name}" hosts ${hostedRows.length} team(s):\n${teamLines}\n\nHow should they be handled?`;
|
|
454
|
+
const choice = await askSelect({
|
|
455
|
+
message: promptMsg,
|
|
456
|
+
options: [
|
|
457
|
+
{ value: 'transfer', label: 'Transfer host to another local agent', hint: 'keeps team history & members' },
|
|
458
|
+
{ value: 'cascade', label: 'Delete teams (and all their members + history)', hint: 'destructive, irreversible' },
|
|
459
|
+
{ value: 'abort', label: 'Cancel — do nothing' },
|
|
460
|
+
],
|
|
461
|
+
initialValue: 'transfer',
|
|
462
|
+
});
|
|
463
|
+
if (choice === 'abort') {
|
|
464
|
+
console.log('Cancelled.');
|
|
465
|
+
process.exit(0);
|
|
466
|
+
}
|
|
467
|
+
if (choice === 'transfer') {
|
|
468
|
+
const id = await pickLocalAgent(db, 'Transfer host to which agent?');
|
|
469
|
+
if (id === agent.id) {
|
|
470
|
+
console.error('Cannot transfer to self.');
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
transferAgent = db.prepare('SELECT id, display_name FROM agents WHERE id = ?').get(id);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
cascade = true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else if (!cascade) {
|
|
480
|
+
console.error('Hosted teams detected. Pass --transfer-to <agent> to keep them, or --cascade to delete them with their members + history.');
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (transferAgent && transferAgent.id === agent.id) {
|
|
485
|
+
console.error('Cannot transfer hosted teams to the agent being removed.');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
394
488
|
if (!assumeYes) {
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
489
|
+
const summary = transferAgent
|
|
490
|
+
? `Remove agent "${name}" (${agent.id}); transfer ${hostedRows.length} hosted team(s) to "${transferAgent.display_name}".`
|
|
491
|
+
: hostedRows.length > 0
|
|
492
|
+
? `Remove agent "${name}" AND cascade-delete ${hostedRows.length} hosted team(s) with all members + history?`
|
|
493
|
+
: `Remove agent "${name}" (${agent.id}) and all related data?`;
|
|
494
|
+
const ok = await askConfirm({ message: summary, initialValue: false });
|
|
399
495
|
if (!ok) {
|
|
400
496
|
console.log('Cancelled.');
|
|
401
497
|
process.exit(0);
|
|
402
498
|
}
|
|
403
499
|
}
|
|
500
|
+
// --- Apply transfer first (so we don't cascade-delete those teams below) ---
|
|
501
|
+
if (transferAgent) {
|
|
502
|
+
const sessionsMod = await import('./sessions.js');
|
|
503
|
+
for (const r of hostedRows) {
|
|
504
|
+
// Make sure new host is a member; if not, add them.
|
|
505
|
+
if (!dbMod.isTeamMember(r.team.id, transferAgent.id)) {
|
|
506
|
+
dbMod.addTeamMember(r.team.id, transferAgent.id);
|
|
507
|
+
}
|
|
508
|
+
dbMod.transferTeamHost(r.team.id, transferAgent.id);
|
|
509
|
+
dbMod.appendTeamEvent(r.team.id, 'host-transfer', null, {
|
|
510
|
+
from_agent_id: agent.id,
|
|
511
|
+
from_display_name: name,
|
|
512
|
+
to_agent_id: transferAgent.id,
|
|
513
|
+
to_display_name: transferAgent.display_name,
|
|
514
|
+
reason: 'agent-remove',
|
|
515
|
+
});
|
|
516
|
+
try {
|
|
517
|
+
await sessionsMod.notifyTeamMembers(r.team.id, undefined, JSON.stringify({
|
|
518
|
+
type: 'team-host-transfer',
|
|
519
|
+
team_id: r.team.id,
|
|
520
|
+
team_name: r.team.name,
|
|
521
|
+
from: name,
|
|
522
|
+
to: transferAgent.display_name,
|
|
523
|
+
}));
|
|
524
|
+
}
|
|
525
|
+
catch { /* push best-effort */ }
|
|
526
|
+
}
|
|
527
|
+
console.log(`↪ Transferred ${hostedRows.length} hosted team(s) to "${transferAgent.display_name}".`);
|
|
528
|
+
}
|
|
404
529
|
// Delete in dependency order to satisfy foreign keys
|
|
405
530
|
db.prepare('DELETE FROM read_cursors WHERE agent_id = ?').run(agent.id);
|
|
406
531
|
// Tasks
|
|
@@ -413,7 +538,7 @@ async function cmdAgentRemove() {
|
|
|
413
538
|
// Team membership + events
|
|
414
539
|
db.prepare('DELETE FROM team_members WHERE agent_id = ?').run(agent.id);
|
|
415
540
|
db.prepare('DELETE FROM team_events WHERE actor_agent_id = ?').run(agent.id);
|
|
416
|
-
// Teams hosted by this agent (
|
|
541
|
+
// Teams hosted by this agent (only those left — transferred ones now have a different host)
|
|
417
542
|
db.prepare(`DELETE FROM team_events WHERE team_id IN (SELECT id FROM teams WHERE host_agent_id = ?)`).run(agent.id);
|
|
418
543
|
db.prepare(`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE host_agent_id = ?)`).run(agent.id);
|
|
419
544
|
db.prepare(`DELETE FROM read_cursors WHERE target_id IN (SELECT id FROM teams WHERE host_agent_id = ?)`).run(agent.id);
|
|
@@ -499,17 +624,25 @@ async function cmdAgentRename() {
|
|
|
499
624
|
oldDisplay = db.prepare('SELECT display_name FROM agents WHERE id = ?').get(agentId).display_name;
|
|
500
625
|
}
|
|
501
626
|
else {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
627
|
+
// ID-first to avoid collision with display_name that equals some agent's ID.
|
|
628
|
+
const byId = db.prepare('SELECT id, display_name FROM agents WHERE id = ?').get(oldName);
|
|
629
|
+
if (byId) {
|
|
630
|
+
agentId = byId.id;
|
|
631
|
+
oldDisplay = byId.display_name;
|
|
506
632
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
633
|
+
else {
|
|
634
|
+
const matches = db.prepare('SELECT id, display_name FROM agents WHERE display_name = ?').all(oldName);
|
|
635
|
+
if (matches.length === 0) {
|
|
636
|
+
console.log(`Agent "${oldName}" not found.`);
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
if (matches.length > 1) {
|
|
640
|
+
console.log(`"${oldName}" matches ${matches.length} agents. Use agent id to disambiguate.`);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
agentId = matches[0].id;
|
|
644
|
+
oldDisplay = matches[0].display_name;
|
|
510
645
|
}
|
|
511
|
-
agentId = matches[0].id;
|
|
512
|
-
oldDisplay = matches[0].display_name;
|
|
513
646
|
}
|
|
514
647
|
if (!newName) {
|
|
515
648
|
if (!isInteractive()) {
|
|
@@ -535,6 +668,166 @@ async function cmdAgentList() {
|
|
|
535
668
|
}
|
|
536
669
|
console.log(renderTable(AGENT_HEADERS, agentRows(db, agents)));
|
|
537
670
|
}
|
|
671
|
+
// --- Team commands (CLI-only; agents use hive-team-* MCP tools instead) ---
|
|
672
|
+
async function cmdTeamList() {
|
|
673
|
+
const { dbPath } = parseFlags(1);
|
|
674
|
+
initDB(dbPath);
|
|
675
|
+
const dbMod = await import('./db.js');
|
|
676
|
+
const teams = dbMod.listTeams(false);
|
|
677
|
+
if (teams.length === 0) {
|
|
678
|
+
console.log('No teams.');
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
for (const t of teams) {
|
|
682
|
+
const host = t.host_agent_id ? (getAgentById(t.host_agent_id)?.display_name ?? t.host_agent_id.slice(-12)) : '(none)';
|
|
683
|
+
const memberCount = dbMod.countTeamMembers(t.id);
|
|
684
|
+
const status = t.closed_at ? 'closed' : 'active';
|
|
685
|
+
console.log(`${t.id} ${t.name} host=${host} members=${memberCount} ${status}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async function cmdTeamTransfer() {
|
|
689
|
+
const { dbPath } = parseFlags(1);
|
|
690
|
+
let teamArg = '';
|
|
691
|
+
let toArg = '';
|
|
692
|
+
let assumeYes = false;
|
|
693
|
+
let addMember = false;
|
|
694
|
+
for (let i = 2; i < args.length; i++) {
|
|
695
|
+
if (args[i] === '--to' && args[i + 1]) {
|
|
696
|
+
toArg = args[i + 1];
|
|
697
|
+
i++;
|
|
698
|
+
}
|
|
699
|
+
else if (args[i] === '--yes' || args[i] === '-y') {
|
|
700
|
+
assumeYes = true;
|
|
701
|
+
}
|
|
702
|
+
else if (args[i] === '--add-member') {
|
|
703
|
+
addMember = true;
|
|
704
|
+
}
|
|
705
|
+
else if (args[i] === '--port' || args[i] === '-p' || args[i] === '--db') {
|
|
706
|
+
i++;
|
|
707
|
+
}
|
|
708
|
+
else if (!args[i].startsWith('-') && !teamArg)
|
|
709
|
+
teamArg = args[i];
|
|
710
|
+
}
|
|
711
|
+
const db = initDB(dbPath);
|
|
712
|
+
const dbMod = await import('./db.js');
|
|
713
|
+
// --- Resolve team ---
|
|
714
|
+
let team;
|
|
715
|
+
if (teamArg) {
|
|
716
|
+
team = dbMod.getTeamById(teamArg) || dbMod.getTeamByName(teamArg);
|
|
717
|
+
if (!team) {
|
|
718
|
+
console.error(`Team "${teamArg}" not found.`);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
if (!isInteractive()) {
|
|
724
|
+
console.error('Usage: kitty-hive team transfer <team> --to <agent> [--add-member] [--yes]');
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
const teams = dbMod.listTeams(false);
|
|
728
|
+
if (teams.length === 0) {
|
|
729
|
+
console.log('No teams to transfer.');
|
|
730
|
+
process.exit(0);
|
|
731
|
+
}
|
|
732
|
+
const teamId = await askSelect({
|
|
733
|
+
message: 'Transfer which team?',
|
|
734
|
+
options: teams.map(t => ({
|
|
735
|
+
value: t.id,
|
|
736
|
+
label: t.name,
|
|
737
|
+
hint: `host=${t.host_agent_id ? (getAgentById(t.host_agent_id)?.display_name ?? t.host_agent_id.slice(-12)) : '(none)'} · ${dbMod.countTeamMembers(t.id)} member(s)`,
|
|
738
|
+
})),
|
|
739
|
+
});
|
|
740
|
+
team = teams.find(t => t.id === teamId);
|
|
741
|
+
}
|
|
742
|
+
const currentHostName = team.host_agent_id ? (getAgentById(team.host_agent_id)?.display_name ?? team.host_agent_id.slice(-12)) : '(none)';
|
|
743
|
+
console.log(`\nTeam: ${team.name} (${team.id})\nCurrent host: ${currentHostName}\n`);
|
|
744
|
+
// --- Resolve new host ---
|
|
745
|
+
let newHost;
|
|
746
|
+
if (toArg) {
|
|
747
|
+
// ID-first: ID is unique, only fall back to display_name lookup on no ID hit.
|
|
748
|
+
const byId = db.prepare("SELECT id, display_name FROM agents WHERE id = ? AND origin_peer = ''").get(toArg);
|
|
749
|
+
if (byId) {
|
|
750
|
+
newHost = byId;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
const matches = db.prepare("SELECT id, display_name FROM agents WHERE display_name = ? AND origin_peer = ''").all(toArg);
|
|
754
|
+
if (matches.length === 0) {
|
|
755
|
+
console.error(`--to: local agent "${toArg}" not found.`);
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
if (matches.length > 1) {
|
|
759
|
+
console.error(`--to "${toArg}" matches ${matches.length} agents. Use id to disambiguate.`);
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
762
|
+
newHost = matches[0];
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
if (!isInteractive()) {
|
|
767
|
+
console.error('Missing --to <agent>.');
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
const id = await pickLocalAgent(db, 'New host?');
|
|
771
|
+
newHost = db.prepare('SELECT id, display_name FROM agents WHERE id = ?').get(id);
|
|
772
|
+
}
|
|
773
|
+
if (!newHost) {
|
|
774
|
+
console.error('New host not found.');
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
if (newHost.id === team.host_agent_id) {
|
|
778
|
+
console.log(`"${newHost.display_name}" is already the host. Nothing to do.`);
|
|
779
|
+
process.exit(0);
|
|
780
|
+
}
|
|
781
|
+
// --- Membership check ---
|
|
782
|
+
if (!dbMod.isTeamMember(team.id, newHost.id)) {
|
|
783
|
+
if (!addMember && !assumeYes && isInteractive()) {
|
|
784
|
+
const ok = await askConfirm({
|
|
785
|
+
message: `"${newHost.display_name}" is not a member of "${team.name}". Add them as a member?`,
|
|
786
|
+
initialValue: true,
|
|
787
|
+
});
|
|
788
|
+
if (!ok) {
|
|
789
|
+
console.log('Cancelled.');
|
|
790
|
+
process.exit(0);
|
|
791
|
+
}
|
|
792
|
+
addMember = true;
|
|
793
|
+
}
|
|
794
|
+
else if (!addMember) {
|
|
795
|
+
console.error(`"${newHost.display_name}" is not a member of "${team.name}". Re-run with --add-member to auto-join them, or have them join first.`);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
dbMod.addTeamMember(team.id, newHost.id);
|
|
799
|
+
}
|
|
800
|
+
if (!assumeYes) {
|
|
801
|
+
const ok = await askConfirm({
|
|
802
|
+
message: `Transfer host of "${team.name}" from ${currentHostName} → ${newHost.display_name}?`,
|
|
803
|
+
initialValue: true,
|
|
804
|
+
});
|
|
805
|
+
if (!ok) {
|
|
806
|
+
console.log('Cancelled.');
|
|
807
|
+
process.exit(0);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
dbMod.transferTeamHost(team.id, newHost.id);
|
|
811
|
+
dbMod.appendTeamEvent(team.id, 'host-transfer', null, {
|
|
812
|
+
from_agent_id: team.host_agent_id,
|
|
813
|
+
from_display_name: currentHostName,
|
|
814
|
+
to_agent_id: newHost.id,
|
|
815
|
+
to_display_name: newHost.display_name,
|
|
816
|
+
reason: 'team-transfer',
|
|
817
|
+
});
|
|
818
|
+
try {
|
|
819
|
+
const sessionsMod = await import('./sessions.js');
|
|
820
|
+
await sessionsMod.notifyTeamMembers(team.id, undefined, JSON.stringify({
|
|
821
|
+
type: 'team-host-transfer',
|
|
822
|
+
team_id: team.id,
|
|
823
|
+
team_name: team.name,
|
|
824
|
+
from: currentHostName,
|
|
825
|
+
to: newHost.display_name,
|
|
826
|
+
}));
|
|
827
|
+
}
|
|
828
|
+
catch { /* push best-effort */ }
|
|
829
|
+
console.log(`✅ Host of "${team.name}" → ${newHost.display_name}.`);
|
|
830
|
+
}
|
|
538
831
|
// --- Node config ---
|
|
539
832
|
function getConfigPath() {
|
|
540
833
|
return join(homedir(), '.kitty-hive', 'config.json');
|
|
@@ -1436,7 +1729,8 @@ Top-level commands:
|
|
|
1436
1729
|
status [--port 4123] Server & agent status
|
|
1437
1730
|
|
|
1438
1731
|
Command groups:
|
|
1439
|
-
agent Manage local agents (list, rename, remove)
|
|
1732
|
+
agent Manage local agents (list, rename, remove, register)
|
|
1733
|
+
team Inspect/maintain teams (list, transfer)
|
|
1440
1734
|
peer Manage federation peers (invite, accept, add, list, expose, set-url, remove)
|
|
1441
1735
|
log Inspect message history (dm, team, task)
|
|
1442
1736
|
tunnel Cloudflare tunnel control (start, status)
|
|
@@ -1457,9 +1751,25 @@ Subcommands:
|
|
|
1457
1751
|
list List all agents (local + remote placeholders)
|
|
1458
1752
|
rename [old] [new] Rename an agent (interactive when args omitted)
|
|
1459
1753
|
remove [name-or-id] | --key <K> [--yes] Remove an agent. --key is idempotent (missing = exit 0).
|
|
1754
|
+
Hosted teams: pass --transfer-to <agent> to keep them,
|
|
1755
|
+
or --cascade to delete them with all members + history.
|
|
1460
1756
|
register --key <K> --display-name <N> [--roles R] Idempotent upsert by external_key.
|
|
1461
1757
|
Stdout = agent_id (one line). Designed for orchestrator scripts.`);
|
|
1462
1758
|
}
|
|
1759
|
+
function showTeamHelp() {
|
|
1760
|
+
console.log(`🐝 kitty-hive team — local team maintenance
|
|
1761
|
+
|
|
1762
|
+
Usage:
|
|
1763
|
+
kitty-hive team <subcommand> [args]
|
|
1764
|
+
|
|
1765
|
+
Subcommands:
|
|
1766
|
+
list List all teams with host + member counts
|
|
1767
|
+
transfer [<team>] --to <agent> [--add-member] Transfer team host to another local agent.
|
|
1768
|
+
--add-member auto-joins target if not yet a member.
|
|
1769
|
+
|
|
1770
|
+
Note: agents normally manage teams via the hive-team-* MCP tools.
|
|
1771
|
+
This group is for operator-level maintenance (e.g. before removing a host agent).`);
|
|
1772
|
+
}
|
|
1463
1773
|
function showPeerHelp() {
|
|
1464
1774
|
console.log(`🐝 kitty-hive peer — federation peer management
|
|
1465
1775
|
|
|
@@ -1564,6 +1874,19 @@ switch (command) {
|
|
|
1564
1874
|
break;
|
|
1565
1875
|
}
|
|
1566
1876
|
break;
|
|
1877
|
+
case 'team':
|
|
1878
|
+
switch (args[1]) {
|
|
1879
|
+
case 'list':
|
|
1880
|
+
run(cmdTeamList);
|
|
1881
|
+
break;
|
|
1882
|
+
case 'transfer':
|
|
1883
|
+
run(cmdTeamTransfer);
|
|
1884
|
+
break;
|
|
1885
|
+
default:
|
|
1886
|
+
showTeamHelp();
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1889
|
+
break;
|
|
1567
1890
|
case 'peer':
|
|
1568
1891
|
switch (args[1]) {
|
|
1569
1892
|
case 'add':
|