Skip to content

Commit 676d78e

Browse files
designcodeclaude
andauthored
feat: add --json and --yes for agents (#39)
* test: add more tests * test: enhances integration test suite * feat: implement global --json * fix: confirmation on destructive commands * feat: add global --yes flag * chore: pr comments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f643218 commit 676d78e

55 files changed

Lines changed: 3082 additions & 193 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- next
8+
push:
9+
branches:
10+
- main
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: '22'
22+
- run: npm ci
23+
- run: npm run test
24+
- run: npm run build
25+
26+
- uses: oven-sh/setup-bun@v2
27+
- run: bun run build:binary
28+
29+
integration:
30+
runs-on: ubuntu-latest
31+
if: github.event_name == 'push'
32+
steps:
33+
- uses: actions/checkout@v4
34+
with:
35+
fetch-depth: 0
36+
- uses: actions/setup-node@v4
37+
with:
38+
node-version: '22'
39+
- run: npm ci
40+
- run: npm run build
41+
- run: npm run test:integration
42+
env:
43+
TIGRIS_STORAGE_ACCESS_KEY_ID: ${{ secrets.TIGRIS_STORAGE_ACCESS_KEY_ID }}
44+
TIGRIS_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.TIGRIS_STORAGE_SECRET_ACCESS_KEY }}

.github/workflows/pr.yaml

Lines changed: 0 additions & 25 deletions
This file was deleted.

.github/workflows/release.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ jobs:
3434
- run: npm ci
3535
- run: npm run lint
3636
- run: npm run build
37-
- run: npm run test
37+
- run: npm run test:all
38+
env:
39+
TIGRIS_STORAGE_ACCESS_KEY_ID: ${{ secrets.TIGRIS_STORAGE_ACCESS_KEY_ID }}
40+
TIGRIS_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.TIGRIS_STORAGE_SECRET_ACCESS_KEY }}
3841
- run: npm run publint
3942
- run: npm audit signatures
4043

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
"lint:fix": "eslint src --fix",
3030
"format": "prettier --write \"src/**/*.ts\"",
3131
"format:check": "prettier --check \"src/**/*.ts\"",
32-
"test": "vitest run",
32+
"test": "vitest run test/utils test/cli-core.test.ts test/specs-completeness.test.ts",
3333
"test:watch": "vitest",
34-
"test:unit": "vitest run test/utils",
34+
"test:all": "vitest run",
3535
"test:integration": "vitest run test/cli.test.ts",
3636
"publint": "publint",
3737
"updatedocs": "tsx scripts/update-docs.ts",

src/cli-core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ export function createProgram(config: CLIConfig): CommanderCommand {
515515

516516
const program = new CommanderCommand();
517517
program.name(specs.name).description(specs.description).version(version);
518+
program.option('-y, --yes', 'Skip all confirmation prompts');
518519

519520
registerCommands(config, program, specs.commands);
520521

src/lib/access-keys/assign.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ function normalizeToArray<T>(value: T | T[] | undefined): T[] {
2424
export default async function assign(options: Record<string, unknown>) {
2525
printStart(context);
2626

27+
const json = getOption<boolean>(options, ['json']);
28+
const format = json
29+
? 'json'
30+
: getOption<string>(options, ['format', 'f', 'F'], 'table');
31+
2732
const id = getOption<string>(options, ['id']);
2833
const admin = getOption<boolean>(options, ['admin']);
2934
const revokeRoles = getOption<boolean>(options, [
@@ -83,6 +88,10 @@ export default async function assign(options: Record<string, unknown>) {
8388
process.exit(1);
8489
}
8590

91+
if (format === 'json') {
92+
console.log(JSON.stringify({ action: 'revoked', id }));
93+
}
94+
8695
printSuccess(context);
8796
return;
8897
}
@@ -149,5 +158,9 @@ export default async function assign(options: Record<string, unknown>) {
149158
process.exit(1);
150159
}
151160

161+
if (format === 'json') {
162+
console.log(JSON.stringify({ action: 'assigned', id, assignments }));
163+
}
164+
152165
printSuccess(context);
153166
}

src/lib/access-keys/create.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const context = msg('access-keys', 'create');
1616
export default async function create(options: Record<string, unknown>) {
1717
printStart(context);
1818

19+
const json = getOption<boolean>(options, ['json']);
20+
const format = json
21+
? 'json'
22+
: getOption<string>(options, ['format', 'f', 'F'], 'table');
23+
1924
const name = getOption<string>(options, ['name']);
2025

2126
if (!name) {
@@ -58,13 +63,24 @@ export default async function create(options: Record<string, unknown>) {
5863
process.exit(1);
5964
}
6065

61-
console.log(` Name: ${data.name}`);
62-
console.log(` Access Key ID: ${data.id}`);
63-
console.log(` Secret Access Key: ${data.secret}`);
64-
console.log('');
65-
console.log(
66-
' Save these credentials securely. The secret will not be shown again.'
67-
);
66+
if (format === 'json') {
67+
console.log(
68+
JSON.stringify({
69+
action: 'created',
70+
name: data.name,
71+
id: data.id,
72+
secret: data.secret,
73+
})
74+
);
75+
} else {
76+
console.log(` Name: ${data.name}`);
77+
console.log(` Access Key ID: ${data.id}`);
78+
console.log(` Secret Access Key: ${data.secret}`);
79+
console.log('');
80+
console.log(
81+
' Save these credentials securely. The secret will not be shown again.'
82+
);
83+
}
6884

6985
printSuccess(context);
7086
}

src/lib/access-keys/delete.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ import {
1010
printFailure,
1111
msg,
1212
} from '../../utils/messages.js';
13+
import { requireInteractive, confirm } from '../../utils/interactive.js';
1314

1415
const context = msg('access-keys', 'delete');
1516

1617
export default async function remove(options: Record<string, unknown>) {
1718
printStart(context);
1819

20+
const json = getOption<boolean>(options, ['json']);
21+
const format = json
22+
? 'json'
23+
: getOption<string>(options, ['format', 'f', 'F'], 'table');
24+
1925
const id = getOption<string>(options, ['id']);
26+
const force = getOption<boolean>(options, ['force', 'yes', 'y']);
2027

2128
if (!id) {
2229
printFailure(context, 'Access key ID is required');
@@ -41,6 +48,15 @@ export default async function remove(options: Record<string, unknown>) {
4148
process.exit(1);
4249
}
4350

51+
if (!force) {
52+
requireInteractive('Use --yes to skip confirmation');
53+
const confirmed = await confirm(`Delete access key '${id}'?`);
54+
if (!confirmed) {
55+
console.log('Aborted');
56+
return;
57+
}
58+
}
59+
4460
const accessToken = await authClient.getAccessToken();
4561
const selectedOrg = getSelectedOrganization();
4662
const tigrisConfig = getTigrisConfig();
@@ -58,5 +74,9 @@ export default async function remove(options: Record<string, unknown>) {
5874
process.exit(1);
5975
}
6076

77+
if (format === 'json') {
78+
console.log(JSON.stringify({ action: 'deleted', id }));
79+
}
80+
6181
printSuccess(context);
6282
}

src/lib/access-keys/get.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const context = msg('access-keys', 'get');
1616
export default async function get(options: Record<string, unknown>) {
1717
printStart(context);
1818

19+
const json = getOption<boolean>(options, ['json']);
20+
const format = json
21+
? 'json'
22+
: getOption<string>(options, ['format', 'f', 'F'], 'table');
23+
1924
const id = getOption<string>(options, ['id']);
2025

2126
if (!id) {
@@ -58,19 +63,23 @@ export default async function get(options: Record<string, unknown>) {
5863
process.exit(1);
5964
}
6065

61-
console.log(` Name: ${data.name}`);
62-
console.log(` ID: ${data.id}`);
63-
console.log(` Status: ${data.status}`);
64-
console.log(` Created: ${data.createdAt}`);
65-
console.log(` Organization: ${data.organizationId}`);
66+
if (format === 'json') {
67+
console.log(JSON.stringify(data));
68+
} else {
69+
console.log(` Name: ${data.name}`);
70+
console.log(` ID: ${data.id}`);
71+
console.log(` Status: ${data.status}`);
72+
console.log(` Created: ${data.createdAt}`);
73+
console.log(` Organization: ${data.organizationId}`);
6674

67-
if (data.roles && data.roles.length > 0) {
68-
console.log(` Roles:`);
69-
for (const role of data.roles) {
70-
console.log(` - ${role.bucket}: ${role.role}`);
75+
if (data.roles && data.roles.length > 0) {
76+
console.log(` Roles:`);
77+
for (const role of data.roles) {
78+
console.log(` - ${role.bucket}: ${role.role}`);
79+
}
80+
} else {
81+
console.log(` Roles: None`);
7182
}
72-
} else {
73-
console.log(` Roles: None`);
7483
}
7584

7685
printSuccess(context);

src/lib/access-keys/list.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { formatOutput } from '../../utils/format.js';
2+
import { getOption } from '../../utils/options.js';
23
import { getLoginMethod } from '../../auth/s3-client.js';
34
import { getAuthClient } from '../../auth/client.js';
45
import { getSelectedOrganization } from '../../auth/storage.js';
@@ -14,9 +15,14 @@ import {
1415

1516
const context = msg('access-keys', 'list');
1617

17-
export default async function list() {
18+
export default async function list(options: Record<string, unknown>) {
1819
printStart(context);
1920

21+
const json = getOption<boolean>(options, ['json']);
22+
const format = json
23+
? 'json'
24+
: getOption<string>(options, ['format', 'f', 'F'], 'table');
25+
2026
const loginMethod = await getLoginMethod();
2127

2228
if (loginMethod !== 'oauth') {
@@ -64,7 +70,7 @@ export default async function list() {
6470
created: key.createdAt,
6571
}));
6672

67-
const output = formatOutput(keys, 'table', 'keys', 'key', [
73+
const output = formatOutput(keys, format!, 'keys', 'key', [
6874
{ key: 'name', header: 'Name' },
6975
{ key: 'id', header: 'ID' },
7076
{ key: 'status', header: 'Status' },

0 commit comments

Comments
 (0)