Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/squad-cli/src/cli-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ const VERSION = getPackageVersion();

async function main(): Promise<void> {
const args = process.argv.slice(2);

// --team-root flag: override team root for resolution
const teamRootIdx = args.indexOf('--team-root');
if (teamRootIdx !== -1 && args[teamRootIdx + 1]) {
process.env['SQUAD_TEAM_ROOT'] = args[teamRootIdx + 1]!;
// Remove --team-root and its value from args
args.splice(teamRootIdx, 2);
}

const hasGlobal = args.includes('--global');
// --economy activates economy mode for this session (sets env var for spawner)
const hasEconomy = args.includes('--economy');
Expand Down Expand Up @@ -193,6 +202,9 @@ async function main(): Promise<void> {
console.log(` Flags: --docker (force Docker), --port <n> (dashboard port)`);
console.log(` ${BOLD}schedule${RESET} Manage scheduled tasks`);
console.log(` Usage: schedule list | run <id> | init | status`);
console.log(` ${BOLD}personal${RESET} Manage your personal squad (ambient agents)`);
console.log(` Usage: personal init | list | add <name> --role <role> | remove <name>`);
console.log(` ${BOLD}cast${RESET} Show current session cast (project + personal agents)`);
console.log(` ${BOLD}rc${RESET} Start Remote Control bridge (phone/browser → Copilot)`);
console.log(` Usage: rc [--tunnel] [--port <n>] [--path <dir>]`);
console.log(` ${BOLD}copilot-bridge${RESET} Check Copilot ACP stdio compatibility`);
Expand All @@ -216,6 +228,7 @@ async function main(): Promise<void> {
console.log(` ${BOLD}--help, -h${RESET} Show help`);
console.log(` ${BOLD}--global${RESET} Use personal (global) squad path (for init, upgrade)`);
console.log(` ${BOLD}--economy${RESET} Activate economy mode for this session (cheaper models)`);
console.log(` ${BOLD}--team-root${RESET} Override team root path for resolution`);
console.log(`\nInstallation:`);
console.log(` npm install --save-dev @bradygaster/squad-cli`);
console.log(`\nInsider channel:`);
Expand Down Expand Up @@ -571,6 +584,19 @@ async function main(): Promise<void> {
return;
}

if (cmd === 'personal') {
const { runPersonal } = await import('./cli/commands/personal.js');
const subcommand = args[1] || 'list';
await runPersonal(process.cwd(), subcommand, args.slice(2));
return;
}

if (cmd === 'cast') {
const { runCast } = await import('./cli/commands/cast.js');
await runCast(process.cwd());
return;
}

if (cmd === 'upstream') {
const { upstreamCommand } = await import('./cli/commands/upstream.js');
await upstreamCommand(args.slice(1));
Expand Down
101 changes: 101 additions & 0 deletions packages/squad-cli/src/cli/commands/cast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* squad cast — show current session cast (project + personal agents merged)
*
* Displays the merged session cast with project agents and personal agents,
* highlighting which agents come from the personal squad.
*
* @module cli/commands/cast
*/

import { LocalAgentSource } from '@bradygaster/squad-sdk/config/agent-source';
import { resolvePersonalAgents, mergeSessionCast } from '@bradygaster/squad-sdk/agents/personal';
import { resolveSquadPaths } from '@bradygaster/squad-sdk/resolution';
import { BOLD, RESET, DIM, GREEN, YELLOW } from '../core/output.js';
import { fatal } from '../core/errors.js';

/**
* Run the cast command — show merged session cast.
*/
export async function runCast(cwd: string): Promise<void> {
// Resolve squad paths
const paths = resolveSquadPaths(cwd);
if (!paths) {
fatal('No squad found. Run "squad init" first.');
}

// Discover project agents
const projectSource = new LocalAgentSource(paths.teamDir);
const projectAgents = await projectSource.listAgents();

// Discover personal agents
const personalAgents = await resolvePersonalAgents();

// Merge session cast
const cast = mergeSessionCast(projectAgents, personalAgents);

if (cast.length === 0) {
console.log('\nNo agents found in current session cast.\n');
return;
}

// Count project vs personal
const projectCount = projectAgents.length;
const personalCount = personalAgents.filter(
pa => !projectAgents.some(pra => pra.name.toLowerCase() === pa.name.toLowerCase())
).length;

console.log(`\n${BOLD}Session Cast${RESET} (${cast.length} agents):\n`);

if (projectCount > 0) {
console.log(` Project agents: ${projectCount}`);
}
if (personalCount > 0) {
console.log(` Personal agents: ${personalCount} ${YELLOW}(ambient)${RESET}`);
}
console.log();

// Calculate column widths
const maxNameLen = Math.max(...cast.map(a => a.name.length), 4);
const maxRoleLen = Math.max(...cast.map(a => a.role.length), 4);

// Header
console.log(
` ${'Name'.padEnd(maxNameLen)} ` +
`${'Role'.padEnd(maxRoleLen)} ` +
`Origin Ghost Protocol`
);
console.log(
` ${'─'.repeat(maxNameLen)} ` +
`${'─'.repeat(maxRoleLen)} ` +
`${'─'.repeat(13)} ${'─'.repeat(14)}`
);

// Rows
for (const agent of cast) {
const isPersonal = 'personal' in agent;
const nameDisplay = isPersonal
? `👤 ${agent.name}`.padEnd(maxNameLen + 3) // +3 for emoji width
: agent.name.padEnd(maxNameLen);

const origin = isPersonal ? `${YELLOW}personal${RESET}` : 'project';
const originPadded = isPersonal ? origin + ' '.repeat(6) : origin.padEnd(13);

const ghostProtocol = isPersonal && agent.personal.ghostProtocol
? `${GREEN}✓ enforced${RESET}`
: '–';

console.log(
` ${nameDisplay} ` +
`${agent.role.padEnd(maxRoleLen)} ` +
`${originPadded} ${ghostProtocol}`
);
}

console.log();

// Legend
if (personalCount > 0) {
console.log(`${DIM}👤 = Personal agent (ambient, Ghost Protocol enforced in projects)${RESET}`);
console.log();
}
}
Loading
Loading