ultraclaude-agent 0.0.19 → 0.0.21
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/__tests__/claude-profiles-ops.test.ts +441 -0
- package/__tests__/claude-profiles.test.ts +407 -0
- package/__tests__/credential-watcher.test.ts +229 -0
- package/dist/claude-profiles.d.ts +83 -0
- package/dist/claude-profiles.d.ts.map +1 -0
- package/dist/claude-profiles.js +499 -0
- package/dist/claude-profiles.js.map +1 -0
- package/dist/cli.js +93 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +68 -0
- package/dist/daemon.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +141 -0
- package/dist/repl.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.d.ts +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/types.d.ts +32 -0
- package/node_modules/@ultra-claude/shared/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/claude-profiles.ts +597 -0
- package/src/cli.ts +117 -0
- package/src/daemon.ts +80 -0
- package/src/repl.ts +164 -0
package/src/cli.ts
CHANGED
|
@@ -23,6 +23,16 @@ import { startDaemon, forkDaemon, getDaemonStatus } from './daemon.js';
|
|
|
23
23
|
import { installService, isServiceInstalled, getServiceType } from './service.js';
|
|
24
24
|
import { initialSync, createSnapshot as createSnapshotOnServer } from './sync.js';
|
|
25
25
|
import { startRepl } from './repl.js';
|
|
26
|
+
import {
|
|
27
|
+
listProfiles,
|
|
28
|
+
switchProfile,
|
|
29
|
+
loginProfile,
|
|
30
|
+
saveCurrentAsProfile,
|
|
31
|
+
deleteProfileByName,
|
|
32
|
+
getClaudeStatus,
|
|
33
|
+
getExpiryStatus,
|
|
34
|
+
loadActiveProfile,
|
|
35
|
+
} from './claude-profiles.js';
|
|
26
36
|
import { logger, initMultistreamLogger } from './logger.js';
|
|
27
37
|
import type { AgentCredentials } from '@ultra-claude/shared';
|
|
28
38
|
|
|
@@ -506,4 +516,111 @@ program
|
|
|
506
516
|
|
|
507
517
|
// unlink removed — projects are auto-discovered from ~/.claude/projects/
|
|
508
518
|
|
|
519
|
+
// --- claude-profile ---
|
|
520
|
+
|
|
521
|
+
const claudeProfile = program
|
|
522
|
+
.command('claude-profile')
|
|
523
|
+
.description('Manage Claude Code credential profiles');
|
|
524
|
+
|
|
525
|
+
claudeProfile
|
|
526
|
+
.command('list')
|
|
527
|
+
.description('List all saved Claude credential profiles')
|
|
528
|
+
.action(async () => {
|
|
529
|
+
const profiles = await listProfiles();
|
|
530
|
+
if (profiles.length === 0) {
|
|
531
|
+
console.log('No saved Claude profiles.');
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const active = await loadActiveProfile();
|
|
536
|
+
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log(
|
|
539
|
+
' ' +
|
|
540
|
+
'Name'.padEnd(16) +
|
|
541
|
+
'Email'.padEnd(30) +
|
|
542
|
+
'Org'.padEnd(16) +
|
|
543
|
+
'Subscription'.padEnd(14) +
|
|
544
|
+
'Active'.padEnd(8) +
|
|
545
|
+
'Token',
|
|
546
|
+
);
|
|
547
|
+
console.log(' ' + '-'.repeat(90));
|
|
548
|
+
|
|
549
|
+
for (const profile of profiles) {
|
|
550
|
+
const isActive = active?.profile === profile.name;
|
|
551
|
+
const expiry = getExpiryStatus(profile);
|
|
552
|
+
console.log(
|
|
553
|
+
' ' +
|
|
554
|
+
profile.name.padEnd(16) +
|
|
555
|
+
profile.email.padEnd(30) +
|
|
556
|
+
(profile.orgName || '-').padEnd(16) +
|
|
557
|
+
(profile.subscriptionType || '-').padEnd(14) +
|
|
558
|
+
(isActive ? '*' : '').padEnd(8) +
|
|
559
|
+
expiry.label,
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
console.log('');
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
claudeProfile
|
|
566
|
+
.command('switch')
|
|
567
|
+
.argument('<name>', 'Profile name to switch to')
|
|
568
|
+
.description('Switch to a saved Claude credential profile')
|
|
569
|
+
.action(async (name: string) => {
|
|
570
|
+
const result = await switchProfile(name);
|
|
571
|
+
console.log(result.message);
|
|
572
|
+
process.exitCode = result.success ? 0 : 1;
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
claudeProfile
|
|
576
|
+
.command('login')
|
|
577
|
+
.argument('<name>', 'Profile name to save as')
|
|
578
|
+
.option('--email <email>', 'Pre-populate login email')
|
|
579
|
+
.description('Open browser login and save as named profile')
|
|
580
|
+
.action(async (name: string, options: { email?: string }) => {
|
|
581
|
+
const result = await loginProfile(name, options.email);
|
|
582
|
+
console.log(result.message);
|
|
583
|
+
process.exitCode = result.success ? 0 : 1;
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
claudeProfile
|
|
587
|
+
.command('save')
|
|
588
|
+
.argument('<name>', 'Profile name')
|
|
589
|
+
.description('Save current credentials as a named profile')
|
|
590
|
+
.action(async (name: string) => {
|
|
591
|
+
const result = await saveCurrentAsProfile(name);
|
|
592
|
+
console.log(result.message);
|
|
593
|
+
process.exitCode = result.success ? 0 : 1;
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
claudeProfile
|
|
597
|
+
.command('delete')
|
|
598
|
+
.argument('<name>', 'Profile name to delete')
|
|
599
|
+
.description('Delete a saved credential profile')
|
|
600
|
+
.action(async (name: string) => {
|
|
601
|
+
const result = await deleteProfileByName(name);
|
|
602
|
+
if (result.wasActive) {
|
|
603
|
+
console.log(`Warning: "${name}" was the active profile.`);
|
|
604
|
+
}
|
|
605
|
+
console.log(result.message);
|
|
606
|
+
process.exitCode = result.success ? 0 : 1;
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
claudeProfile
|
|
610
|
+
.command('status')
|
|
611
|
+
.description('Show current Claude auth status')
|
|
612
|
+
.action(async () => {
|
|
613
|
+
const result = await getClaudeStatus();
|
|
614
|
+
if (!result.success) {
|
|
615
|
+
console.log(result.message);
|
|
616
|
+
process.exitCode = 1;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const id = result.identity!;
|
|
620
|
+
console.log(`Email: ${id.email}`);
|
|
621
|
+
console.log(`Org: ${id.orgName || '-'}`);
|
|
622
|
+
console.log(`Subscription: ${id.subscriptionType || '-'}`);
|
|
623
|
+
console.log(`Logged in: ${id.loggedIn ? 'yes' : 'no'}`);
|
|
624
|
+
});
|
|
625
|
+
|
|
509
626
|
await program.parseAsync(process.argv);
|
package/src/daemon.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { startUsageWatcher, type UsageWatcher } from './usage-sync.js';
|
|
|
30
30
|
import { startStatusLoop, readStatusFile, removeStatusFile, type StatusLoop, type StatusFileData } from './status.js';
|
|
31
31
|
import { isServiceActive } from './service.js';
|
|
32
32
|
import * as socketClient from './socket-client.js';
|
|
33
|
+
import { CREDENTIALS_FILE, handleCredentialChange, pruneBackups } from './claude-profiles.js';
|
|
33
34
|
import { logger } from './logger.js';
|
|
34
35
|
import { ok, err, type Result, type AgentCredentials } from '@ultra-claude/shared';
|
|
35
36
|
import type { ProjectRegistryEntry } from '@ultra-claude/shared';
|
|
@@ -92,6 +93,11 @@ let registryWatcher: ReturnType<typeof chokidar.watch> | null = null;
|
|
|
92
93
|
let discoveryTimer: ReturnType<typeof setInterval> | null = null;
|
|
93
94
|
let versionWatcherTimer: ReturnType<typeof setInterval> | null = null;
|
|
94
95
|
let usageWatcher: UsageWatcher | null = null;
|
|
96
|
+
interface CredentialWatcher {
|
|
97
|
+
close(): Promise<void>;
|
|
98
|
+
}
|
|
99
|
+
let credentialWatcher: CredentialWatcher | null = null;
|
|
100
|
+
let credentialPruneTimer: ReturnType<typeof setInterval> | null = null;
|
|
95
101
|
let statusLoop: StatusLoop | null = null;
|
|
96
102
|
let running = false;
|
|
97
103
|
let daemonStartedAt: string | null = null;
|
|
@@ -193,6 +199,19 @@ export async function startDaemon(): Promise<Result<void, StartDaemonError>> {
|
|
|
193
199
|
// Start global usage watcher (watches ~/.claude/ultra/ for usage-status.json and accounts/)
|
|
194
200
|
usageWatcher = startUsageWatcher();
|
|
195
201
|
|
|
202
|
+
// Start credential watcher for Claude Code profiles
|
|
203
|
+
credentialWatcher = startCredentialWatcher();
|
|
204
|
+
|
|
205
|
+
// Prune old credential backups on startup, then daily
|
|
206
|
+
pruneBackups().catch((pruneErr: unknown) => {
|
|
207
|
+
log.error({ err: pruneErr }, 'Initial backup prune failed');
|
|
208
|
+
});
|
|
209
|
+
credentialPruneTimer = setInterval(() => {
|
|
210
|
+
pruneBackups().catch((pruneErr: unknown) => {
|
|
211
|
+
log.error({ err: pruneErr }, 'Daily backup prune failed');
|
|
212
|
+
});
|
|
213
|
+
}, 24 * 60 * 60 * 1000);
|
|
214
|
+
|
|
196
215
|
// Load initial registry and start watchers
|
|
197
216
|
const registry = await loadRegistry();
|
|
198
217
|
log.info({ projectCount: registry.projects.length }, 'Registry loaded');
|
|
@@ -267,6 +286,16 @@ export async function stopDaemon(): Promise<void> {
|
|
|
267
286
|
usageWatcher = null;
|
|
268
287
|
}
|
|
269
288
|
|
|
289
|
+
// Close credential watcher and prune timer
|
|
290
|
+
if (credentialWatcher) {
|
|
291
|
+
await credentialWatcher.close();
|
|
292
|
+
credentialWatcher = null;
|
|
293
|
+
}
|
|
294
|
+
if (credentialPruneTimer) {
|
|
295
|
+
clearInterval(credentialPruneTimer);
|
|
296
|
+
credentialPruneTimer = null;
|
|
297
|
+
}
|
|
298
|
+
|
|
270
299
|
// Close registry watcher
|
|
271
300
|
if (registryWatcher) {
|
|
272
301
|
await registryWatcher.close();
|
|
@@ -694,5 +723,56 @@ function startVersionWatcher(serverUrl: string): ReturnType<typeof setInterval>
|
|
|
694
723
|
}, 60_000);
|
|
695
724
|
}
|
|
696
725
|
|
|
726
|
+
// --- Credential watcher ---
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Watch ~/.claude/.credentials.json for external changes.
|
|
730
|
+
* Creates timestamped backups, identifies the account, and updates matching profiles.
|
|
731
|
+
* Self-trigger detection skips processing for switch-initiated writes.
|
|
732
|
+
* Returns a lifecycle object with close() that cancels pending debounce timers.
|
|
733
|
+
*/
|
|
734
|
+
function startCredentialWatcher(): CredentialWatcher {
|
|
735
|
+
const log = logger.child({ op: 'credentialWatcher' });
|
|
736
|
+
|
|
737
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
738
|
+
|
|
739
|
+
const watcher = chokidar.watch(CREDENTIALS_FILE, {
|
|
740
|
+
persistent: true,
|
|
741
|
+
ignoreInitial: true,
|
|
742
|
+
awaitWriteFinish: {
|
|
743
|
+
stabilityThreshold: 300,
|
|
744
|
+
pollInterval: 100,
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
watcher
|
|
749
|
+
.on('change', () => {
|
|
750
|
+
// Debounce rapid changes
|
|
751
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
752
|
+
debounceTimer = setTimeout(() => {
|
|
753
|
+
debounceTimer = null;
|
|
754
|
+
handleCredentialChange().catch((changeErr: unknown) => {
|
|
755
|
+
log.error({ err: changeErr }, 'Credential change handler failed');
|
|
756
|
+
});
|
|
757
|
+
}, 500);
|
|
758
|
+
})
|
|
759
|
+
.on('error', (watcherErr) => {
|
|
760
|
+
log.error({ err: watcherErr }, 'Credential watcher error');
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
log.info({ path: CREDENTIALS_FILE }, 'Credential watcher started');
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
async close() {
|
|
767
|
+
if (debounceTimer) {
|
|
768
|
+
clearTimeout(debounceTimer);
|
|
769
|
+
debounceTimer = null;
|
|
770
|
+
}
|
|
771
|
+
await watcher.close();
|
|
772
|
+
log.info('Credential watcher stopped');
|
|
773
|
+
},
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
697
777
|
/** Expose LOADED_VERSION for use by other modules (e.g., status) */
|
|
698
778
|
export { LOADED_VERSION };
|
package/src/repl.ts
CHANGED
|
@@ -23,6 +23,17 @@ import { login } from './auth.js';
|
|
|
23
23
|
import { stopDaemon, getDaemonStatus, isRunningInProcess, forkDaemon } from './daemon.js';
|
|
24
24
|
import { isServiceInstalled } from './service.js';
|
|
25
25
|
import { initialSync, createSnapshot as createSnapshotOnServer } from './sync.js';
|
|
26
|
+
import {
|
|
27
|
+
listProfiles,
|
|
28
|
+
switchProfile,
|
|
29
|
+
loginProfile,
|
|
30
|
+
saveCurrentAsProfile,
|
|
31
|
+
deleteProfileByName,
|
|
32
|
+
getClaudeStatus,
|
|
33
|
+
getExpiryStatus,
|
|
34
|
+
loadActiveProfile,
|
|
35
|
+
loadProfile,
|
|
36
|
+
} from './claude-profiles.js';
|
|
26
37
|
import { logger } from './logger.js';
|
|
27
38
|
import type { AgentCredentials, ProjectRegistryEntry } from '@ultra-claude/shared';
|
|
28
39
|
|
|
@@ -516,6 +527,130 @@ async function cmdReset(_args: string[], ctx: ReplContext): Promise<void> {
|
|
|
516
527
|
ctx.rl.close();
|
|
517
528
|
}
|
|
518
529
|
|
|
530
|
+
async function cmdClaude(args: string[], _ctx: ReplContext): Promise<void> {
|
|
531
|
+
const subcmd = args[0]?.toLowerCase();
|
|
532
|
+
const subArgs = args.slice(1);
|
|
533
|
+
|
|
534
|
+
switch (subcmd) {
|
|
535
|
+
case 'list': {
|
|
536
|
+
const profiles = await listProfiles();
|
|
537
|
+
if (profiles.length === 0) {
|
|
538
|
+
console.log('No saved Claude profiles. Run "claude save <name>" or "claude login <name>" to create one.');
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const active = await loadActiveProfile();
|
|
543
|
+
|
|
544
|
+
// Table header
|
|
545
|
+
console.log('');
|
|
546
|
+
console.log(
|
|
547
|
+
' ' +
|
|
548
|
+
'Name'.padEnd(16) +
|
|
549
|
+
'Email'.padEnd(30) +
|
|
550
|
+
'Org'.padEnd(16) +
|
|
551
|
+
'Subscription'.padEnd(14) +
|
|
552
|
+
'Active'.padEnd(8) +
|
|
553
|
+
'Token',
|
|
554
|
+
);
|
|
555
|
+
console.log(' ' + '-'.repeat(90));
|
|
556
|
+
|
|
557
|
+
for (const profile of profiles) {
|
|
558
|
+
const isActive = active?.profile === profile.name;
|
|
559
|
+
const expiry = getExpiryStatus(profile);
|
|
560
|
+
console.log(
|
|
561
|
+
' ' +
|
|
562
|
+
profile.name.padEnd(16) +
|
|
563
|
+
profile.email.padEnd(30) +
|
|
564
|
+
(profile.orgName || '-').padEnd(16) +
|
|
565
|
+
(profile.subscriptionType || '-').padEnd(14) +
|
|
566
|
+
(isActive ? '*' : '').padEnd(8) +
|
|
567
|
+
expiry.label,
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
console.log('');
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
case 'switch': {
|
|
575
|
+
const name = subArgs[0];
|
|
576
|
+
if (!name) {
|
|
577
|
+
console.log('Usage: claude switch <name>');
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const result = await switchProfile(name);
|
|
581
|
+
console.log(result.message);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
case 'login': {
|
|
586
|
+
const name = subArgs[0];
|
|
587
|
+
if (!name) {
|
|
588
|
+
console.log('Usage: claude login <name> [--email <email>]');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const emailIdx = subArgs.indexOf('--email');
|
|
592
|
+
const email = emailIdx >= 0 ? subArgs[emailIdx + 1] : undefined;
|
|
593
|
+
const result = await loginProfile(name, email);
|
|
594
|
+
console.log(result.message);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
case 'save': {
|
|
599
|
+
const name = subArgs[0];
|
|
600
|
+
if (!name) {
|
|
601
|
+
console.log('Usage: claude save <name>');
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const result = await saveCurrentAsProfile(name);
|
|
605
|
+
console.log(result.message);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
case 'delete': {
|
|
610
|
+
const name = subArgs[0];
|
|
611
|
+
if (!name) {
|
|
612
|
+
console.log('Usage: claude delete <name>');
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const result = await deleteProfileByName(name);
|
|
616
|
+
if (result.wasActive) {
|
|
617
|
+
console.log(`Warning: "${name}" was the active profile.`);
|
|
618
|
+
}
|
|
619
|
+
console.log(result.message);
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
case 'status': {
|
|
624
|
+
const result = await getClaudeStatus();
|
|
625
|
+
if (!result.success) {
|
|
626
|
+
console.log(result.message);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const id = result.identity!;
|
|
630
|
+
console.log('');
|
|
631
|
+
console.log(` Email: ${id.email}`);
|
|
632
|
+
console.log(` Org: ${id.orgName || '-'}`);
|
|
633
|
+
console.log(` Subscription: ${id.subscriptionType || '-'}`);
|
|
634
|
+
console.log(` Logged in: ${id.loggedIn ? 'yes' : 'no'}`);
|
|
635
|
+
console.log('');
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
default: {
|
|
640
|
+
console.log(`
|
|
641
|
+
Claude profile commands:
|
|
642
|
+
claude list Show all saved credential profiles
|
|
643
|
+
claude switch <name> Switch to a saved profile
|
|
644
|
+
claude login <name> Login via browser and save as profile
|
|
645
|
+
claude save <name> Save current credentials as a profile
|
|
646
|
+
claude delete <name> Remove a saved profile
|
|
647
|
+
claude status Show current Claude auth status
|
|
648
|
+
`);
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
519
654
|
function cmdHelp(): void {
|
|
520
655
|
console.log(`
|
|
521
656
|
Available commands:
|
|
@@ -532,6 +667,14 @@ Available commands:
|
|
|
532
667
|
snapshot <project> "label" Create a named snapshot
|
|
533
668
|
auto-assign on|off Toggle automatic project assignment
|
|
534
669
|
reset Wipe all accounts and project mappings
|
|
670
|
+
|
|
671
|
+
claude list Show Claude Code credential profiles
|
|
672
|
+
claude switch <name> Switch to a saved Claude profile
|
|
673
|
+
claude login <name> Login and save as Claude profile
|
|
674
|
+
claude save <name> Save current Claude credentials as profile
|
|
675
|
+
claude delete <name> Remove a Claude profile
|
|
676
|
+
claude status Show current Claude auth status
|
|
677
|
+
|
|
535
678
|
help Show this help
|
|
536
679
|
quit / exit Exit
|
|
537
680
|
|
|
@@ -556,6 +699,7 @@ const COMMANDS: Record<string, CommandHandler> = {
|
|
|
556
699
|
snapshot: cmdSnapshot,
|
|
557
700
|
'auto-assign': cmdAutoAssign,
|
|
558
701
|
reset: cmdReset,
|
|
702
|
+
claude: cmdClaude,
|
|
559
703
|
help: async () => cmdHelp(),
|
|
560
704
|
quit: async () => process.exit(0),
|
|
561
705
|
exit: async () => process.exit(0),
|
|
@@ -574,12 +718,32 @@ export async function startRepl(serverUrl?: string): Promise<void> {
|
|
|
574
718
|
|
|
575
719
|
const mappedCount = Object.keys(config.projectAccounts).length;
|
|
576
720
|
|
|
721
|
+
// Load active Claude profile for banner
|
|
722
|
+
let claudeBannerLine = '';
|
|
723
|
+
try {
|
|
724
|
+
const activeClaudeProfile = await loadActiveProfile();
|
|
725
|
+
if (activeClaudeProfile) {
|
|
726
|
+
const profile = await loadProfile(activeClaudeProfile.profile);
|
|
727
|
+
if (profile) {
|
|
728
|
+
const parts = [profile.email];
|
|
729
|
+
if (profile.orgName) parts.push(profile.orgName);
|
|
730
|
+
if (profile.subscriptionType) parts.push(profile.subscriptionType);
|
|
731
|
+
claudeBannerLine = ` Claude: ${parts[0]}${parts.length > 1 ? ` (${parts.slice(1).join(', ')})` : ''}`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
} catch (profileErr: unknown) {
|
|
735
|
+
logger.debug({ err: profileErr }, 'Failed to load active Claude profile for banner');
|
|
736
|
+
}
|
|
737
|
+
|
|
577
738
|
// Status banner
|
|
578
739
|
console.log('');
|
|
579
740
|
console.log(' Ultra Claude Agent — Interactive Mode');
|
|
580
741
|
console.log(` Server: ${resolvedUrl}`);
|
|
581
742
|
console.log(` Accounts: ${accounts.size}`);
|
|
582
743
|
console.log(` Projects: ${registry.projects.length} discovered, ${mappedCount} mapped`);
|
|
744
|
+
if (claudeBannerLine) {
|
|
745
|
+
console.log(claudeBannerLine);
|
|
746
|
+
}
|
|
583
747
|
console.log('');
|
|
584
748
|
console.log(' Type "help" for available commands.');
|
|
585
749
|
console.log('');
|