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: 24 additions & 2 deletions src/commands/infoCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class InfoCommand {
this.devMode = devMode;
}

async execute(): Promise<void> {
async execute(opts: { json?: boolean } = {}): Promise<void> {
const pm = new ProjectManager();
const projectState = await pm.detectProjectState();
const hasKeep = projectState.initialized && !!projectState.organizationId;
Expand Down Expand Up @@ -58,19 +58,41 @@ export class InfoCommand {

// Resolve the user's role in the active org (best effort).
let roleLabel = '—';
let roleSlug: string | null = null;
if (activeOrgId && authService.getToken() && authResult.user_id) {
try {
const serviceClient = new ServiceClient(this.apiUrl, this.devMode);
serviceClient.setTokenProvider(() => authService.getValidToken());
const { members } = await serviceClient.listMembers(activeOrgId);
const me = members.find((m: any) => m.userId === authResult.user_id);
const slug = me?.role?.slug;
if (slug) roleLabel = ROLE_LABELS[slug] || slug;
if (slug) {
roleLabel = ROLE_LABELS[slug] || slug;
roleSlug = slug;
}
} catch {
// leave as —
}
}

if (opts.json) {
console.log(
JSON.stringify(
{
user: { email: authResult.user_email ?? null, userId: authResult.user_id ?? null },
org: { id: activeOrgId ?? null, name: activeOrgName ?? null, workosOrgId: workosOrgId ?? null, fromKeepLock: hasKeep },
role: roleSlug,
project: { name: projectState.projectName ?? null, id: projectState.projectId ?? null },
branch: hasKeep ? branch : null,
memberships: allOrgs.map((o) => ({ id: o.id, name: o.name ?? null, workosOrgId: o.workos_org_id ?? null })),
},
null,
2,
),
);
return;
}

const activeOrgLabel = hasKeep
? `${activeOrgName || '—'} ${DIM}(from keep.lock)${RESET}`
: activeOrgId
Expand Down
63 changes: 63 additions & 0 deletions src/commands/listCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ProjectManager } from '../core/projectManager';
import { listAllVarsOnBranch, listManagedKeys, findManagedConnector } from './connectors/shared';

const B = (s: string) => `\x1b[1m${s}\x1b[0m`;
const DIM = '\x1b[90m';
const RESET = '\x1b[0m';

/**
* `capy list` — variable NAMES + connector metadata for the active branch.
* Reads keep.lock only: no auth, no network, no decryption. Never emits values.
*/
export class ListCommand {
constructor(private readonly devMode: boolean = false) {}

async execute(opts: { json?: boolean } = {}): Promise<void> {
const pm = new ProjectManager();
const projectState = await pm.detectProjectState();
if (!projectState.initialized) {
console.error(`No keep.lock found. Run ${B('capy')} to initialize.`);
process.exit(1);
}
const keep = pm.readKeepFile();
if (!keep) {
console.error('Could not read keep.lock');
process.exit(1);
}
const branch = projectState.activeBranch;

const managed = new Set(listManagedKeys(keep, branch).map((m) => m.varName));
const variables = listAllVarsOnBranch(keep, branch).map((name) => {
const c = managed.has(name) ? findManagedConnector(keep, name, branch) : undefined;
return {
name,
managed: !!c,
connector: c
? {
provider: c.provider,
source: c.source,
mode: c.mode ?? null,
accountId: c.account_id ?? null,
fingerprint: c.fingerprint,
createdAt: new Date(c.created_at * 1000).toISOString(),
rotatedAt: c.rotated_at ? new Date(c.rotated_at * 1000).toISOString() : null,
expiresAt: c.expires_at ? new Date(c.expires_at * 1000).toISOString() : null,
}
: null,
};
});

if (opts.json) {
console.log(JSON.stringify({ projectName: keep.project_name, branch, variables }, null, 2));
return;
}

console.log('');
console.log(` ${keep.project_name} ${DIM}·${RESET} ${branch} ${DIM}(${variables.length})${RESET}`);
for (const v of variables) {
const tag = v.connector ? ` ${DIM}(${v.connector.provider})${RESET}` : '';
console.log(` ${v.name}${tag}`);
}
console.log('');
}
}
15 changes: 13 additions & 2 deletions src/index-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,21 @@ program
program
.command('info')
.description('Show current session info')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { InfoCommand } = await import('./commands/infoCommand');
const cmd = new InfoCommand(process.env.CAPY_API_URL, true);
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
.command('list')
.description('List variable names + connector metadata for the active branch (no values)')
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { ListCommand } = await import('./commands/listCommand');
const cmd = new ListCommand(true);
await cmd.execute({ json: options.json });
});

program
Expand Down
16 changes: 14 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,11 +534,23 @@ program
program
.command('info')
.description('Show current session info')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
assertNotLocalOnly('info');
const { InfoCommand } = await import('./commands/infoCommand');
const cmd = new InfoCommand();
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
.command('list')
.description('List variable names + connector metadata for the active branch (no values)')
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
assertNotLocalOnly('list');
const { ListCommand } = await import('./commands/listCommand');
const cmd = new ListCommand();
await cmd.execute({ json: options.json });
});

program
Expand Down
Loading