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
28 changes: 25 additions & 3 deletions src/commands/statusCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,17 @@ export class StatusCommand {
this.serviceClient.setTokenProvider(() => this.authService.getValidToken());
}

async execute(): Promise<void> {
async execute(opts: { json?: boolean } = {}): Promise<void> {
try {
await this._execute();
await this._execute(opts);
} catch {
// Exit silently on any error (auth, network, etc.)
// Hooks must never block git operations
process.exit(0);
}
}

private async _execute(): Promise<void> {
private async _execute(opts: { json?: boolean } = {}): Promise<void> {
const projectState = await this.projectManager.detectProjectState();
if (!projectState.initialized) {
if (this.terse) return;
Expand Down Expand Up @@ -247,6 +247,28 @@ export class StatusCommand {
: undefined;
const { diffs, showLocal, showRemote } = compareSecrets(pinned, localHashes, remoteHashes);

if (opts.json) {
// diffs carry value HASHES only (sha256 prefix), never plaintext.
const totalSecrets = new Set([...Object.keys(pinned), ...Object.keys(localHashes)]).size;
console.log(
JSON.stringify(
{
projectName: keep.project_name,
branch,
totalSecrets,
inSync: diffs.length === 0,
localMatchesPinned: !showLocal,
remoteMatchesPinned: !showRemote,
remoteFailure: remoteFailure ?? null,
diffs,
},
null,
2,
),
);
return;
}

if (this.terse) {
// Terse mode for git hooks
if (diffs.length === 0) return; // Silent when synced
Expand Down
33 changes: 32 additions & 1 deletion src/commands/usersCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class UsersCommand {
return { projectId: project.id, branchId: (branch as any).id, userId: member.userId };
}

async execute(): Promise<void> {
async execute(opts: { json?: boolean } = {}): Promise<void> {
const pm = new ProjectManager();
const projectState = await pm.detectProjectState();

Expand Down Expand Up @@ -141,6 +141,37 @@ export class UsersCommand {
process.exit(1);
}

if (opts.json) {
console.log(
JSON.stringify(
{
members: members.map((m: any) => ({
membershipId: m.membershipId,
userId: m.userId,
email: m.email,
role: m.role,
status: m.status,
joinedAt: m.createdAt,
projects: (m.projects || []).map((p: any) => ({
id: p.id,
name: p.name,
role: p.role ?? null,
branches: (p.branches || []).map((b: any) => ({
id: b.id,
name: b.name,
isProtected: b.isProtected,
hasAccess: b.hasAccess,
})),
})),
})),
},
null,
2,
),
);
return;
}

if (members.length === 0) {
console.log('\n No members found.\n');
return;
Expand Down
32 changes: 28 additions & 4 deletions src/index-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ program
.command('branch')
.description('List secret branches')
.option('-D <name>', 'Delete a branch')
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { AuthService } = await import('./auth/authService');
const { ServiceClient } = await import('./service/serviceClient');
Expand Down Expand Up @@ -181,6 +182,27 @@ program
const activeBranch = projectState.activeBranch;
const projectName = projectState.projectName || 'project';

if (options.json) {
console.log(
JSON.stringify(
{
projectName,
activeBranch,
branches: branches.map((b) => ({
id: b.id,
name: b.name,
isProtected: b.is_protected,
createdAt: b.created_at ?? null,
isCurrent: b.name === activeBranch,
})),
},
null,
2,
),
);
return;
}

// Tree view
console.log('');
console.log(`Project "${projectName}"`);
Expand Down Expand Up @@ -250,10 +272,11 @@ program
program
.command('status')
.description('Show secret drift between local, pinned, and remote')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { StatusCommand } = await import('./commands/statusCommand');
const cmd = new StatusCommand(false, true);
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
Expand Down Expand Up @@ -547,10 +570,11 @@ program
program
.command('users')
.description('List organization members and their project access')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { UsersCommand } = await import('./commands/usersCommand');
const cmd = new UsersCommand(process.env.CAPY_API_URL, true);
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
Expand Down
32 changes: 28 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ program
program
.command('status')
.description('Show secret drift between local, pinned, and remote')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
const { StatusCommand } = await import('./commands/statusCommand');
const cmd = new StatusCommand();
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
Expand All @@ -126,6 +127,7 @@ program
.command('branch')
.description('List secret branches')
.option('-D <name>', 'Delete a branch')
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
assertNotLocalOnly('branch');
const { AuthService } = await import('./auth/authService');
Expand Down Expand Up @@ -186,6 +188,27 @@ program
const activeBranch = projectState.activeBranch;
const projectName = projectState.projectName || 'project';

if (options.json) {
console.log(
JSON.stringify(
{
projectName,
activeBranch,
branches: branches.map((b) => ({
id: b.id,
name: b.name,
isProtected: b.is_protected,
createdAt: b.created_at ?? null,
isCurrent: b.name === activeBranch,
})),
},
null,
2,
),
);
return;
}

// Tree view
console.log('');
console.log(` Project "${projectName}"`);
Expand Down Expand Up @@ -556,11 +579,12 @@ program
program
.command('users')
.description('List organization members and their project access')
.action(async () => {
.option('--json', 'emit machine-readable JSON instead of the human UI')
.action(async (options) => {
assertNotLocalOnly('users');
const { UsersCommand } = await import('./commands/usersCommand');
const cmd = new UsersCommand();
await cmd.execute();
await cmd.execute({ json: options.json });
});

program
Expand Down
Loading