Skip to content

Commit e154aea

Browse files
katspaughclaude
andauthored
feat: add non-interactive support to account add-owner command (#34)
Add full non-interactive mode support to the add-owner command: Changes: - Import isNonInteractiveMode, outputSuccess, outputError - Skip intro prompt in non-interactive mode - Replace p.log.error + p.outro with outputError and exit codes - Require owner address argument in non-interactive mode - Skip confirmation prompt in non-interactive mode - Add JSON output mode with transaction details - Use nullable spinner for non-interactive mode JSON Output Format: ```json { "success": true, "message": "Add owner transaction created", "data": { "safeTxHash": "0x...", "safeAddress": "0x...", "chainId": "1", "chainName": "Ethereum", "newOwner": "0x...", "newThreshold": 2, "totalOwners": 3 } } ``` Related: Fixes inconsistent error handling identified in regression review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 5734e07 commit e154aea

File tree

1 file changed

+68
-50
lines changed

1 file changed

+68
-50
lines changed

src/commands/account/add-owner.ts

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import {
1010
ensureChainConfigured,
1111
checkCancelled,
1212
handleCommandError,
13+
isNonInteractiveMode,
14+
outputSuccess,
15+
outputError,
1316
} from '../../utils/command-helpers.js'
17+
import { ExitCode } from '../../constants/exit-codes.js'
1418
import {
1519
selectDeployedSafe,
1620
fetchSafeOwnersAndThreshold,
@@ -34,7 +38,9 @@ export async function addOwner(
3438
ownerAddress?: string,
3539
options: AddOwnerOptions = {}
3640
) {
37-
p.intro(pc.bgCyan(pc.black(' Add Safe Owner ')))
41+
if (!isNonInteractiveMode()) {
42+
p.intro(pc.bgCyan(pc.black(' Add Safe Owner ')))
43+
}
3844

3945
try {
4046
const ctx = createCommandContext()
@@ -62,15 +68,11 @@ export async function addOwner(
6268

6369
const safe = ctx.safeStorage.getSafe(chainId, address)
6470
if (!safe) {
65-
p.log.error(`Safe not found: ${address} on chain ${chainId}`)
66-
p.cancel('Operation cancelled')
67-
return
71+
outputError(`Safe not found: ${address} on chain ${chainId}`, ExitCode.SAFE_NOT_FOUND)
6872
}
6973

7074
if (!safe.deployed) {
71-
p.log.error('Safe must be deployed before adding owners')
72-
p.cancel('Operation cancelled')
73-
return
75+
outputError('Safe must be deployed before adding owners', ExitCode.ERROR)
7476
}
7577

7678
// Get chain
@@ -100,16 +102,18 @@ export async function addOwner(
100102

101103
// Check for duplicates
102104
if (isAddressAlreadyOwner(newOwner, currentOwners)) {
103-
p.log.error('Address is already an owner')
104-
p.outro('Failed')
105-
return
105+
outputError('Address is already an owner', ExitCode.INVALID_ARGS)
106106
}
107107
} catch (error) {
108-
p.log.error(error instanceof Error ? error.message : 'Invalid address')
109-
p.outro('Failed')
110-
return
108+
outputError(
109+
error instanceof Error ? error.message : 'Invalid address',
110+
ExitCode.INVALID_ARGS
111+
)
111112
}
112113
} else {
114+
if (isNonInteractiveMode()) {
115+
outputError('Owner address is required in non-interactive mode', ExitCode.INVALID_ARGS)
116+
}
113117
// Prompt for new owner address
114118
const newOwnerInput = await p.text({
115119
message: 'New owner address (supports EIP-3770 format: shortName:address):',
@@ -149,9 +153,10 @@ export async function addOwner(
149153
'Owner address'
150154
)
151155
} catch (error) {
152-
p.log.error(error instanceof Error ? error.message : 'Invalid address')
153-
p.outro('Failed')
154-
return
156+
outputError(
157+
error instanceof Error ? error.message : 'Invalid address',
158+
ExitCode.INVALID_ARGS
159+
)
155160
}
156161
}
157162

@@ -162,14 +167,13 @@ export async function addOwner(
162167
// Use provided threshold
163168
const num = parseInt(options.threshold, 10)
164169
if (isNaN(num) || num < 1) {
165-
p.log.error('Threshold must be at least 1')
166-
p.outro('Failed')
167-
return
170+
outputError('Threshold must be at least 1', ExitCode.INVALID_ARGS)
168171
}
169172
if (num > currentOwners.length + 1) {
170-
p.log.error(`Threshold cannot exceed ${currentOwners.length + 1} owners`)
171-
p.outro('Failed')
172-
return
173+
outputError(
174+
`Threshold cannot exceed ${currentOwners.length + 1} owners`,
175+
ExitCode.INVALID_ARGS
176+
)
173177
}
174178
thresholdNum = num
175179
} else {
@@ -194,29 +198,31 @@ export async function addOwner(
194198
thresholdNum = parseInt(newThreshold as string, 10)
195199
}
196200

197-
// Show summary
198-
console.log('')
199-
console.log(pc.bold('Add Owner Summary:'))
200-
console.log(` ${pc.dim('Safe:')} ${safe.name}`)
201-
console.log(` ${pc.dim('New Owner:')} ${newOwner}`)
202-
console.log(` ${pc.dim('Current Owners:')} ${currentOwners.length}`)
203-
console.log(` ${pc.dim('New Owners:')} ${currentOwners.length + 1}`)
204-
console.log(` ${pc.dim('Old Threshold:')} ${currentThreshold}`)
205-
console.log(` ${pc.dim('New Threshold:')} ${thresholdNum}`)
206-
console.log('')
207-
208-
const confirm = await p.confirm({
209-
message: 'Create transaction to add this owner?',
210-
initialValue: true,
211-
})
212-
213-
if (!checkCancelled(confirm) || !confirm) {
214-
p.cancel('Operation cancelled')
215-
return
201+
if (!isNonInteractiveMode()) {
202+
// Show summary
203+
console.log('')
204+
console.log(pc.bold('Add Owner Summary:'))
205+
console.log(` ${pc.dim('Safe:')} ${safe.name}`)
206+
console.log(` ${pc.dim('New Owner:')} ${newOwner}`)
207+
console.log(` ${pc.dim('Current Owners:')} ${currentOwners.length}`)
208+
console.log(` ${pc.dim('New Owners:')} ${currentOwners.length + 1}`)
209+
console.log(` ${pc.dim('Old Threshold:')} ${currentThreshold}`)
210+
console.log(` ${pc.dim('New Threshold:')} ${thresholdNum}`)
211+
console.log('')
212+
213+
const confirm = await p.confirm({
214+
message: 'Create transaction to add this owner?',
215+
initialValue: true,
216+
})
217+
218+
if (!checkCancelled(confirm) || !confirm) {
219+
p.cancel('Operation cancelled')
220+
return
221+
}
216222
}
217223

218-
const spinner = p.spinner()
219-
spinner.start('Creating add owner transaction...')
224+
const spinner = !isNonInteractiveMode() ? p.spinner() : null
225+
spinner?.start('Creating add owner transaction...')
220226

221227
// The addOwnerWithThreshold method encodes the transaction data
222228
const txService = new TransactionService(chain)
@@ -235,14 +241,26 @@ export async function addOwner(
235241
activeWallet.address as Address
236242
)
237243

238-
spinner.stop('Transaction created')
244+
spinner?.stop('Transaction created')
239245

240-
await renderScreen(OwnerAddSuccessScreen, {
241-
safeTxHash: safeTransaction.safeTxHash,
242-
safeAddress: safe.address as Address,
243-
chainId: safe.chainId,
244-
threshold: currentThreshold,
245-
})
246+
if (isNonInteractiveMode()) {
247+
outputSuccess('Add owner transaction created', {
248+
safeTxHash: safeTransaction.safeTxHash,
249+
safeAddress: safe.address,
250+
chainId: safe.chainId,
251+
chainName: chain.name,
252+
newOwner,
253+
newThreshold: thresholdNum,
254+
totalOwners: currentOwners.length + 1,
255+
})
256+
} else {
257+
await renderScreen(OwnerAddSuccessScreen, {
258+
safeTxHash: safeTransaction.safeTxHash,
259+
safeAddress: safe.address as Address,
260+
chainId: safe.chainId,
261+
threshold: currentThreshold,
262+
})
263+
}
246264
} catch (error) {
247265
handleCommandError(error)
248266
}

0 commit comments

Comments
 (0)