Skip to content

Commit 6156500

Browse files
Add delete command
1 parent d2f9e94 commit 6156500

5 files changed

Lines changed: 253 additions & 1 deletion

File tree

.claude/agents/new-command.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
name: new-command
3+
description: Use this agent when the user needs to create a new Oclif command for the SimpleLogin CLI application. This includes scenarios such as:\n\n<example>\nContext: User wants to add a new command to list aliases with filtering options.\nuser: "I need to create a command 'sl alias list' that shows all aliases with optional filtering by enabled status"\nassistant: "I'll use the Task tool to launch the new-command agent to create this Oclif command following the project's patterns and the create-command.md documentation."\n<commentary>\nThe user is requesting a new command implementation, so the new-command agent should be used to create it according to the project's Oclif patterns and CLI design specifications.\n</commentary>\n</example>\n\n<example>\nContext: User has just finished designing a feature and wants to implement the corresponding CLI command.\nuser: "Now that we've designed the mailbox creation flow, let's implement the 'sl mailbox create' command"\nassistant: "I'll use the Task tool to launch the new-command agent to implement this command with proper Oclif structure, flags, arguments, and tests."\n<commentary>\nThe user needs a new command implemented. The new-command agent will handle creating the command file, test file, and ensuring all Oclif patterns are followed.\n</commentary>\n</example>\n\n<example>\nContext: User mentions needing to add a subcommand to an existing command hierarchy.\nuser: "We need to add a 'world' subcommand under the hello command"\nassistant: "Let me use the new-command agent to create this subcommand with the correct file structure and Oclif configuration."\n<commentary>\nCreating new commands or subcommands falls under the new-command agent's responsibility.\n</commentary>\n</example>
4+
model: sonnet
5+
---
6+
7+
You are an expert Oclif CLI developer specializing in the SimpleLogin CLI application. Your primary responsibility is to create production-quality Oclif commands that seamlessly integrate with this project's architecture, coding standards, and CLI design specifications.
8+
9+
Read this documentation for knowledge about Command creation:
10+
@docs/development/create-command.md
11+
12+
## Your Expertise
13+
14+
You have deep knowledge of:
15+
- Oclif v4 framework patterns and best practices
16+
- TypeScript ES modules (ES2022, Node16 module resolution)
17+
- The SimpleLogin CLI's resource-action command structure
18+
- The simplelogin-client SDK and its API endpoints
19+
- Testing with Mocha, Chai, and @oclif/test
20+
- YAML configuration management and file security
21+
- Multi-format output (plain/json/yaml)
22+
23+
## Core Responsibilities
24+
25+
When creating a new command, you will:
26+
27+
1. **Analyze Requirements**: Carefully review the user's request and cross-reference with CLI_DESIGN.md specifications. Identify:
28+
- Command path and hierarchy (e.g., `sl alias list``src/commands/alias/list.ts`)
29+
- Required flags and arguments
30+
- Expected behavior and output formats
31+
- API endpoints to use from simplelogin-client
32+
- Error handling requirements
33+
34+
2. **Create Command File Structure**: Generate a complete Oclif command class that includes:
35+
- Proper imports from `@oclif/core` and necessary SDKs
36+
- Class extending `Command`
37+
- Static properties: `description`, `examples`, `flags`, `args`
38+
- Global flags support: `--format` (plain/json/yaml), `--config`
39+
- Async `run()` method with complete implementation
40+
- Use `.js` extensions in relative imports (Node16 modules requirement)
41+
42+
3. **Implement Command Logic**: Write production-ready code that:
43+
- Uses `await this.parse(ClassName)` to get args and flags
44+
- Loads and validates configuration from `~/.config/simplelogin-cli/config.yaml`
45+
- Checks file permissions (600 for config, 700 for directory)
46+
- Initializes appropriate SDK clients (AccountApi, AliasApi, MailboxApi, CustomDomainApi)
47+
- Makes API calls with proper error handling
48+
- Formats output according to `--format` flag (plain/json/yaml)
49+
- Uses `this.log()`, `this.error()`, `this.warn()` appropriately
50+
- Returns correct exit codes (0=success, 1=general error, 2=invalid args, 3=auth required, 4=API error, 5=network error)
51+
52+
4. **Create Test File**: Generate comprehensive tests that:
53+
- Mirror the command's directory structure in `test/commands/`
54+
- Use `runCommand()` from `@oclif/test`
55+
- Test successful execution paths
56+
- Test error conditions
57+
- Verify output formatting
58+
- Use Chai assertions
59+
60+
5. **Follow Project Patterns**: Ensure all code adheres to:
61+
- TypeScript strict mode and ES2022 features
62+
- Resource-action naming conventions from CLI_DESIGN.md
63+
- Docker-style command aliases where applicable (ls, rm, etc.)
64+
- Existing code style and formatting
65+
- ESLint rules
66+
67+
6. **Provide Implementation Guidance**: After creating files, instruct the user to:
68+
- Run `pnpm run prepack` to update README and oclif.manifest.json
69+
- Run `pnpm run test` to verify implementation
70+
- Test locally with `./bin/run.js <command>` or `sl <command>` after npm link
71+
72+
## Quality Standards
73+
74+
Every command you create must:
75+
- Be production-ready with no placeholders or TODO comments
76+
- Handle all error cases gracefully
77+
- Support all three output formats correctly
78+
- Include at least 2-3 meaningful usage examples in static examples
79+
- Have comprehensive test coverage
80+
- Follow the exact file structure and naming conventions of the project
81+
- Use the simplelogin-client SDK correctly for API interactions
82+
- Validate configuration and authentication before API calls
83+
84+
## Decision-Making Framework
85+
86+
1. **Check CLI_DESIGN.md first**: Always verify the command specification exists and matches user requirements
87+
2. **Determine command hierarchy**: Identify correct file path based on command structure
88+
3. **Identify SDK requirements**: Determine which API clients and methods are needed
89+
4. **Plan flag/arg structure**: Define all flags and arguments with proper types and descriptions
90+
5. **Design output format**: Plan how data will be displayed in plain/json/yaml formats
91+
6. **Anticipate errors**: Identify potential failure points and plan error handling
92+
93+
## When to Seek Clarification
94+
95+
- If the command specification is ambiguous or conflicts with CLI_DESIGN.md
96+
- If required API endpoints are unclear or undocumented in simplelogin-client
97+
- If the command's behavior differs significantly from existing patterns
98+
- If security implications of the command need discussion
99+
100+
You write complete, tested, production-ready Oclif commands that integrate seamlessly with the SimpleLogin CLI project. Your code is clear, follows all project conventions, and requires no additional refinement.

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ USAGE
3131
<!-- commands -->
3232
* [`sl alias custom PREFIX SUFFIX`](#sl-alias-custom-prefix-suffix)
3333
* [`sl alias ls`](#sl-alias-ls)
34+
* [`sl alias rm ALIAS-ID`](#sl-alias-rm-alias-id)
3435
* [`sl hello PERSON`](#sl-hello-person)
3536
* [`sl hello world`](#sl-hello-world)
3637
* [`sl help [COMMAND]`](#sl-help-command)
@@ -124,6 +125,39 @@ EXAMPLES
124125
$ sl alias ls --format json
125126
```
126127

128+
## `sl alias rm ALIAS-ID`
129+
130+
Delete an alias by ID
131+
132+
```
133+
USAGE
134+
$ sl alias rm ALIAS-ID [--config <value>] [--format plain|json|yaml] [--confirm]
135+
136+
ARGUMENTS
137+
ALIAS-ID Alias ID to delete
138+
139+
FLAGS
140+
--config=<value> [env: SIMPLELOGIN_CONFIG] Path to config file containing credentials
141+
--confirm Skip confirmation prompt
142+
--format=<option> [default: plain] Output format
143+
<options: plain|json|yaml>
144+
145+
DESCRIPTION
146+
Delete an alias by ID
147+
148+
ALIASES
149+
$ sl alias rm
150+
151+
EXAMPLES
152+
$ sl alias rm 123
153+
154+
$ sl alias rm 123 --confirm
155+
156+
$ sl alias rm 123 --format json
157+
158+
$ sl alias rm 123 --confirm
159+
```
160+
127161
## `sl hello PERSON`
128162

129163
Say hello

src/commands/alias/delete.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {Args, Flags} from '@oclif/core'
2+
import {BaseCommand} from '../base.js'
3+
import {AliasApi} from 'simplelogin-client'
4+
import {getSimpleLoginConfig} from '../../utils/simplelogin-client.js'
5+
import * as readline from 'node:readline/promises'
6+
import {stdin as input, stdout as output} from 'node:process'
7+
8+
export default class AliasDelete extends BaseCommand<typeof AliasDelete> {
9+
static description = 'Delete an alias by ID'
10+
11+
static examples = [
12+
'<%= config.bin %> <%= command.id %> 123',
13+
'<%= config.bin %> <%= command.id %> 123 --confirm',
14+
'<%= config.bin %> <%= command.id %> 123 --format json',
15+
'<%= config.bin %> alias rm 123 --confirm',
16+
]
17+
18+
static aliases = ['alias:rm']
19+
20+
static args = {
21+
'alias-id': Args.integer({
22+
description: 'Alias ID to delete',
23+
required: true,
24+
}),
25+
}
26+
27+
static flags = {
28+
...BaseCommand.baseFlags,
29+
confirm: Flags.boolean({
30+
description: 'Skip confirmation prompt',
31+
default: false,
32+
}),
33+
}
34+
35+
async run(): Promise<void> {
36+
try {
37+
const {args, flags} = await this.parse(AliasDelete)
38+
const aliasId = args['alias-id'] as number
39+
const format = this.getFormat()
40+
const shouldConfirm = flags.confirm as boolean
41+
42+
// Require authentication
43+
await this.requireAuth(flags.config as string | undefined)
44+
45+
// If not confirmed, prompt for confirmation (skip in json/yaml mode)
46+
if (!shouldConfirm && format === 'plain') {
47+
const rl = readline.createInterface({input, output})
48+
const answer = await rl.question(`Are you sure you want to delete alias ${aliasId}? (y/N): `)
49+
rl.close()
50+
51+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
52+
this.log('Deletion cancelled.')
53+
return
54+
}
55+
}
56+
57+
// Initialize API client
58+
const config = await getSimpleLoginConfig(flags.config as string | undefined)
59+
const api = new AliasApi(config)
60+
61+
// Delete the alias
62+
const result = await api.deleteAlias({
63+
aliasId,
64+
})
65+
66+
// Output result
67+
if (format === 'json' || format === 'yaml') {
68+
this.output({
69+
success: true,
70+
deleted: result.deleted || true,
71+
})
72+
} else {
73+
this.log(`Alias ${aliasId} deleted successfully.`)
74+
}
75+
} catch (error) {
76+
if (error instanceof Error) {
77+
this.outputError(error.message, 'API_ERROR')
78+
} else {
79+
this.outputError('An unknown error occurred', 'API_ERROR')
80+
}
81+
82+
this.exit(4)
83+
}
84+
}
85+
}

test/commands/alias/delete.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {runCommand} from '@oclif/test'
2+
import {expect} from 'chai'
3+
4+
describe('alias:delete', () => {
5+
it('shows help', async () => {
6+
const {stdout} = await runCommand('alias delete --help')
7+
expect(stdout).to.contain('Delete an alias by ID')
8+
})
9+
10+
it('requires alias-id argument', async () => {
11+
try {
12+
await runCommand('alias delete')
13+
expect.fail('Should have thrown an error for missing alias-id argument')
14+
} catch (error: any) {
15+
expect(error.message).to.contain('Missing required arg')
16+
}
17+
})
18+
19+
it('requires authentication', async () => {
20+
try {
21+
await runCommand('alias delete 123 --confirm --config /nonexistent/config.yaml')
22+
expect.fail('Should have thrown an error for missing authentication')
23+
} catch (error: any) {
24+
// Should fail with auth error or config file not found
25+
expect(error.message).to.match(/login|config|not found/i)
26+
}
27+
})
28+
29+
it('works with alias rm', async () => {
30+
const {stdout} = await runCommand('alias rm --help')
31+
expect(stdout).to.contain('Delete an alias by ID')
32+
})
33+
})

tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/index.ts","./src/commands/base.ts","./src/commands/config.ts","./src/commands/login.ts","./src/commands/logout.ts","./src/commands/whoami.ts","./src/commands/alias/alias-create-base.ts","./src/commands/alias/alias-list-base.ts","./src/commands/alias/create-custom.ts","./src/commands/alias/create.ts","./src/commands/alias/list.ts","./src/commands/alias/search.ts","./src/commands/hello/index.ts","./src/commands/hello/world.ts","./src/utils/config.ts","./src/utils/simplelogin-client.ts"],"version":"5.9.3"}
1+
{"root":["./src/index.ts","./src/commands/base.ts","./src/commands/config.ts","./src/commands/login.ts","./src/commands/logout.ts","./src/commands/whoami.ts","./src/commands/alias/alias-create-base.ts","./src/commands/alias/alias-list-base.ts","./src/commands/alias/create-custom.ts","./src/commands/alias/create.ts","./src/commands/alias/delete.ts","./src/commands/alias/list.ts","./src/commands/alias/search.ts","./src/commands/hello/index.ts","./src/commands/hello/world.ts","./src/utils/config.ts","./src/utils/simplelogin-client.ts"],"version":"5.9.3"}

0 commit comments

Comments
 (0)