@newpeak/barista-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +23 -0
- package/.prettierrc +9 -0
- package/.sisyphus/notepads/liberica-employees/learnings.md +73 -0
- package/AGENTS.md +270 -0
- package/CONTRIBUTING.md +291 -0
- package/README.md +707 -0
- package/bin/barista +6 -0
- package/bin/barista.js +3 -0
- package/docs/ARCHITECTURE.md +184 -0
- package/docs/COMMANDS.md +352 -0
- package/docs/COMMAND_DESIGN_SPEC.md +811 -0
- package/docs/INTEGRATION_NOTES.md +270 -0
- package/docs/commands/REFERENCE.md +297 -0
- package/docs/commands/arabica/auth/index.md +296 -0
- package/docs/commands/liberica/auth/index.md +133 -0
- package/docs/commands/liberica/context/index.md +60 -0
- package/docs/commands/liberica/employees/create.md +185 -0
- package/docs/commands/liberica/employees/disable.md +138 -0
- package/docs/commands/liberica/employees/enable.md +137 -0
- package/docs/commands/liberica/employees/get.md +153 -0
- package/docs/commands/liberica/employees/list.md +168 -0
- package/docs/commands/liberica/employees/update.md +180 -0
- package/docs/commands/liberica/orgs/list.md +62 -0
- package/docs/commands/liberica/positions/list.md +61 -0
- package/docs/commands/liberica/roles/list.md +67 -0
- package/docs/commands/liberica/users/create.md +170 -0
- package/docs/commands/liberica/users/get.md +151 -0
- package/docs/commands/liberica/users/list.md +175 -0
- package/package.json +37 -0
- package/src/commands/arabica/auth/index.ts +277 -0
- package/src/commands/arabica/auth/login.ts +5 -0
- package/src/commands/arabica/auth/logout.ts +5 -0
- package/src/commands/arabica/auth/register.ts +5 -0
- package/src/commands/arabica/auth/status.ts +5 -0
- package/src/commands/arabica/index.ts +23 -0
- package/src/commands/auth.ts +107 -0
- package/src/commands/context.ts +60 -0
- package/src/commands/liberica/auth/index.ts +170 -0
- package/src/commands/liberica/context/index.ts +43 -0
- package/src/commands/liberica/employees/create.ts +275 -0
- package/src/commands/liberica/employees/delete.ts +122 -0
- package/src/commands/liberica/employees/disable.ts +97 -0
- package/src/commands/liberica/employees/enable.ts +97 -0
- package/src/commands/liberica/employees/get.ts +115 -0
- package/src/commands/liberica/employees/index.ts +23 -0
- package/src/commands/liberica/employees/list.ts +131 -0
- package/src/commands/liberica/employees/update.ts +157 -0
- package/src/commands/liberica/index.ts +36 -0
- package/src/commands/liberica/orgs/index.ts +35 -0
- package/src/commands/liberica/positions/index.ts +30 -0
- package/src/commands/liberica/roles/index.ts +59 -0
- package/src/commands/liberica/users/create.ts +132 -0
- package/src/commands/liberica/users/delete.ts +49 -0
- package/src/commands/liberica/users/disable.ts +41 -0
- package/src/commands/liberica/users/enable.ts +30 -0
- package/src/commands/liberica/users/get.ts +46 -0
- package/src/commands/liberica/users/index.ts +27 -0
- package/src/commands/liberica/users/list.ts +68 -0
- package/src/commands/liberica/users/me.ts +42 -0
- package/src/commands/liberica/users/reset-password.ts +42 -0
- package/src/commands/liberica/users/update.ts +48 -0
- package/src/core/api/client.ts +825 -0
- package/src/core/auth/token-manager.ts +183 -0
- package/src/core/config/manager.ts +164 -0
- package/src/index.ts +37 -0
- package/src/types/employee.ts +102 -0
- package/src/types/index.ts +75 -0
- package/src/types/org.ts +25 -0
- package/src/types/position.ts +24 -0
- package/src/types/user.ts +64 -0
- package/tests/unit/commands/arabica/auth.test.ts +230 -0
- package/tests/unit/commands/liberica/auth.test.ts +175 -0
- package/tests/unit/commands/liberica/context.test.ts +98 -0
- package/tests/unit/commands/liberica/employees/create.test.ts +463 -0
- package/tests/unit/commands/liberica/employees/disable.test.ts +82 -0
- package/tests/unit/commands/liberica/employees/enable.test.ts +82 -0
- package/tests/unit/commands/liberica/employees/get.test.ts +111 -0
- package/tests/unit/commands/liberica/employees/list.test.ts +294 -0
- package/tests/unit/commands/liberica/employees/update.test.ts +210 -0
- package/tests/unit/config.test.ts +141 -0
- package/tests/unit/types.test.ts +195 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Architecture Documentation
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
Barista CLI is an AI-native command-line tool that bridges AI agents (Claude Code, etc.) with the Liberica production management SaaS and Arabica sales platform. It provides a unified interface for managing production orders, products, and related operations across both services.
|
|
6
|
+
|
|
7
|
+
## Technical Stack
|
|
8
|
+
|
|
9
|
+
| Component | Technology | Purpose |
|
|
10
|
+
|-----------|------------|---------|
|
|
11
|
+
| Language | TypeScript 5.4+ | Type safety, modern JS features |
|
|
12
|
+
| CLI Framework | Commander.js 12 | Command parsing, help generation |
|
|
13
|
+
| HTTP Client | Axios | API communication |
|
|
14
|
+
| Auth Storage | Keytar | System keychain integration |
|
|
15
|
+
| Config Storage | YAML (js-yaml) | Human-readable config files |
|
|
16
|
+
| Output | Chalk + cli-table3 | Formatted CLI output |
|
|
17
|
+
| Testing | Vitest | Unit and integration testing |
|
|
18
|
+
|
|
19
|
+
## Architecture Decisions
|
|
20
|
+
|
|
21
|
+
### 1. Direct HTTP Calls (NOT MCP)
|
|
22
|
+
|
|
23
|
+
We use direct HTTP API calls instead of MCP (Model Context Protocol) for several reasons:
|
|
24
|
+
|
|
25
|
+
- **Simplicity**: MCP adds unnecessary complexity for a CLI tool
|
|
26
|
+
- **Control**: Direct HTTP gives full control over request/response handling
|
|
27
|
+
- **Compatibility**: Works with any HTTP-capable environment
|
|
28
|
+
- **Performance**: Lower overhead than protocol buffering
|
|
29
|
+
|
|
30
|
+
### 2. Token-Based Auth (NOT OAuth2)
|
|
31
|
+
|
|
32
|
+
Token-based authentication was chosen over OAuth2 because:
|
|
33
|
+
|
|
34
|
+
- **CLI-First**: OAuth2 flows are browser-based, not suited for CLI
|
|
35
|
+
- **Simplicity**: Token storage and refresh is straightforward
|
|
36
|
+
- **Security**: Tokens stored in system keychain, not files
|
|
37
|
+
- **Backend Constraints**: Cannot modify backend authentication flow
|
|
38
|
+
|
|
39
|
+
### 3. JSON + Table Output (TTY Auto-Detection)
|
|
40
|
+
|
|
41
|
+
Output format strategy:
|
|
42
|
+
|
|
43
|
+
- **TTY Detected**: Show colored table output for human readability
|
|
44
|
+
- **Pipe/Redirect**: Auto-switch to JSON for scripting and AI integration
|
|
45
|
+
- **Explicit Flag**: `--json` flag forces JSON output regardless of TTY
|
|
46
|
+
|
|
47
|
+
## Multi-Environment Support
|
|
48
|
+
|
|
49
|
+
Barista CLI supports four environments:
|
|
50
|
+
|
|
51
|
+
| Environment | Liberica URL Pattern | Arabica URL |
|
|
52
|
+
|-------------|---------------------|-------------|
|
|
53
|
+
| dev | `{tenant}-dev.newpeaksh.com` | arabica-dev.newpeaksh.com |
|
|
54
|
+
| test | `{tenant}-test.newpeaksh.com` | arabica-test.newpeaksh.com |
|
|
55
|
+
| prod-cn | `{tenant}.newpeaksh.com` | www.newpeaksh.com |
|
|
56
|
+
| prod-jp | `{tenant}.newpeakjp.com` | members.newpeakjp.com |
|
|
57
|
+
|
|
58
|
+
## Multi-Tenant Support
|
|
59
|
+
|
|
60
|
+
Liberica is a multi-tenant system where each customer (tenant) has isolated data. The CLI supports:
|
|
61
|
+
|
|
62
|
+
- **Tenant Switching**: `barista context use-tenant <name>`
|
|
63
|
+
- **Per-Tenant Tokens**: Each tenant+environment combination has its own token
|
|
64
|
+
- **URL Templating**: Tenant ID is injected into API URLs
|
|
65
|
+
|
|
66
|
+
## System Architecture
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ Barista CLI │
|
|
71
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
72
|
+
│ bin/barista (entry point) │
|
|
73
|
+
│ │ │
|
|
74
|
+
│ ▼ │
|
|
75
|
+
│ src/index.ts (main) │
|
|
76
|
+
│ │ │
|
|
77
|
+
│ ├── configManager ──────────────────► ~/.barista/config.yaml
|
|
78
|
+
│ │ │
|
|
79
|
+
│ ├── tokenManager ───────────────────► System Keychain │
|
|
80
|
+
│ │ │
|
|
81
|
+
│ ├── createContextCommand() │
|
|
82
|
+
│ ├── createAuthCommand() │
|
|
83
|
+
│ ├── createLibericaCommand() ────────► Liberica API │
|
|
84
|
+
│ │ (Axios HTTP) │
|
|
85
|
+
│ └── createArabicaCommand() ────────► Arabica API │
|
|
86
|
+
│ (Axios HTTP) │
|
|
87
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Module Structure
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
src/
|
|
94
|
+
├── index.ts # Main entry, Commander setup
|
|
95
|
+
├── commands/
|
|
96
|
+
│ ├── context.ts # Context management commands
|
|
97
|
+
│ ├── auth.ts # Authentication commands
|
|
98
|
+
│ ├── liberica/
|
|
99
|
+
│ │ └── index.ts # Liberica service commands
|
|
100
|
+
│ └── arabica/
|
|
101
|
+
│ └── index.ts # Arabica service commands
|
|
102
|
+
├── core/
|
|
103
|
+
│ ├── config/
|
|
104
|
+
│ │ └── manager.ts # Config loading/saving
|
|
105
|
+
│ └── auth/
|
|
106
|
+
│ └── token-manager.ts # Keychain token storage
|
|
107
|
+
├── types/
|
|
108
|
+
│ └── index.ts # TypeScript interfaces
|
|
109
|
+
└── utils/
|
|
110
|
+
└── (formatters, validators, etc.)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Constraints
|
|
114
|
+
|
|
115
|
+
**Critical Constraint**: Cannot modify backend code
|
|
116
|
+
|
|
117
|
+
- Liberica and Arabica backend systems are external and immutable
|
|
118
|
+
- CLI must work within existing API contracts
|
|
119
|
+
- No backend changes can be requested to accommodate CLI needs
|
|
120
|
+
- API responses must be handled as-is
|
|
121
|
+
|
|
122
|
+
## Configuration File
|
|
123
|
+
|
|
124
|
+
Config is stored at `~/.barista/config.yaml`:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
defaults:
|
|
128
|
+
env: dev
|
|
129
|
+
tenant: default
|
|
130
|
+
service: liberica
|
|
131
|
+
|
|
132
|
+
environments:
|
|
133
|
+
dev:
|
|
134
|
+
liberica:
|
|
135
|
+
baseUrl: "https://{tenant}-dev.newpeaksh.com"
|
|
136
|
+
timeout: 30000
|
|
137
|
+
arabica:
|
|
138
|
+
baseUrl: "https://arabica-dev.newpeaksh.com"
|
|
139
|
+
timeout: 30000
|
|
140
|
+
|
|
141
|
+
output:
|
|
142
|
+
format: table
|
|
143
|
+
color: true
|
|
144
|
+
timestamp: true
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Security Considerations
|
|
148
|
+
|
|
149
|
+
1. **Token Storage**: All tokens stored in system keychain, never in files
|
|
150
|
+
2. **Environment Isolation**: Each environment+service+tenant combination has separate token
|
|
151
|
+
3. **No Credential Logging**: Tokens never appear in logs or error messages
|
|
152
|
+
4. **Secure Defaults**: Config created with minimal defaults on first run
|
|
153
|
+
|
|
154
|
+
## Error Handling
|
|
155
|
+
|
|
156
|
+
All API errors follow a standard response structure:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface APIResponse<T> {
|
|
160
|
+
success: boolean;
|
|
161
|
+
data?: T;
|
|
162
|
+
error?: {
|
|
163
|
+
code: string;
|
|
164
|
+
message: string;
|
|
165
|
+
details?: unknown;
|
|
166
|
+
};
|
|
167
|
+
meta?: {
|
|
168
|
+
requestId?: string;
|
|
169
|
+
timestamp?: string;
|
|
170
|
+
pagination?: PaginationInfo;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Detailed Specifications
|
|
176
|
+
|
|
177
|
+
For detailed command specifications, API schemas, and implementation details, refer to the plans in `.sisyphus/plans/`.
|
|
178
|
+
|
|
179
|
+
## Future Considerations
|
|
180
|
+
|
|
181
|
+
- [ ] Add integration tests with mock servers
|
|
182
|
+
- [ ] Support for config file templates
|
|
183
|
+
- [ ] Interactive wizard for initial setup
|
|
184
|
+
- [ ] Completion scripts for bash/zsh
|
package/docs/COMMANDS.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Command Development Guide
|
|
2
|
+
|
|
3
|
+
## 完整命令参考
|
|
4
|
+
|
|
5
|
+
所有命令的完整清单(包括已实现和待开发)请查看 [命令参考](./commands/REFERENCE.md)。
|
|
6
|
+
|
|
7
|
+
## Command Naming Convention
|
|
8
|
+
|
|
9
|
+
All Barista CLI commands follow a consistent naming pattern:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
barista <service> <resource> <action> [options]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Examples:**
|
|
16
|
+
- `barista liberica orders list`
|
|
17
|
+
- `barista liberica products search --keyword "coffee"`
|
|
18
|
+
- `barista arabica members get 12345`
|
|
19
|
+
- `barista context show`
|
|
20
|
+
|
|
21
|
+
## Command Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
barista
|
|
25
|
+
├── context # Context management
|
|
26
|
+
│ ├── show
|
|
27
|
+
│ ├── use-env
|
|
28
|
+
│ └── use-tenant
|
|
29
|
+
├── auth # Authentication
|
|
30
|
+
│ ├── login
|
|
31
|
+
│ ├── status
|
|
32
|
+
│ └── logout
|
|
33
|
+
├── liberica # Liberica service
|
|
34
|
+
│ ├── orders
|
|
35
|
+
│ ├── products
|
|
36
|
+
│ └── production
|
|
37
|
+
└── arabica # Arabica service
|
|
38
|
+
├── members
|
|
39
|
+
├── enterprises
|
|
40
|
+
├── products
|
|
41
|
+
├── subscriptions
|
|
42
|
+
└── access
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Creating a New Command
|
|
46
|
+
|
|
47
|
+
### Step 1: Create Command File
|
|
48
|
+
|
|
49
|
+
Create a new file in `src/commands/{service}/`:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// src/commands/liberica/orders.ts
|
|
53
|
+
import { Command } from 'commander';
|
|
54
|
+
import chalk from 'chalk';
|
|
55
|
+
import { configManager } from '../../core/config/manager.js';
|
|
56
|
+
import { tokenManager } from '../../core/auth/token-manager.js';
|
|
57
|
+
import axios from 'axios';
|
|
58
|
+
|
|
59
|
+
export function createOrdersCommand(): Command {
|
|
60
|
+
const ordersCommand = new Command('orders');
|
|
61
|
+
ordersCommand.description('Manage Liberica orders');
|
|
62
|
+
|
|
63
|
+
// List subcommand
|
|
64
|
+
ordersCommand
|
|
65
|
+
.command('list')
|
|
66
|
+
.description('List all orders')
|
|
67
|
+
.option('--page <number>', 'Page number', '1')
|
|
68
|
+
.option('--size <number>', 'Page size', '20')
|
|
69
|
+
.option('--json', 'Output as JSON')
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
const context = configManager.getCurrentContext();
|
|
72
|
+
const token = await tokenManager.getToken({
|
|
73
|
+
service: 'liberica',
|
|
74
|
+
environment: context.environment,
|
|
75
|
+
tenant: context.tenant,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!token) {
|
|
79
|
+
console.error(chalk.red('✗ Not logged in. Run "barista auth login" first.'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const baseUrl = configManager.getEnvironmentUrl('liberica', context.environment);
|
|
85
|
+
const response = await axios.get(`${baseUrl}/api/v1/orders`, {
|
|
86
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
87
|
+
params: { page: options.page, size: options.size },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (options.json) {
|
|
91
|
+
console.log(JSON.stringify(response.data, null, 2));
|
|
92
|
+
} else {
|
|
93
|
+
// Render table output
|
|
94
|
+
console.log(chalk.bold('\n📋 Orders\n'));
|
|
95
|
+
// ... table rendering logic
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(chalk.red('✗ Failed to fetch orders:'), error.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return ordersCommand;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 2: Register in Parent index.ts
|
|
108
|
+
|
|
109
|
+
Update `src/commands/liberica/index.ts`:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Command } from 'commander';
|
|
113
|
+
import { createOrdersCommand } from './orders.js';
|
|
114
|
+
|
|
115
|
+
export function createLibericaCommand(): Command {
|
|
116
|
+
const libericaCommand = new Command('liberica');
|
|
117
|
+
libericaCommand.description('Liberica production management operations');
|
|
118
|
+
|
|
119
|
+
libericaCommand.addCommand(createOrdersCommand());
|
|
120
|
+
// Add more subcommands here...
|
|
121
|
+
|
|
122
|
+
return libericaCommand;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Step 3: Write Tests
|
|
127
|
+
|
|
128
|
+
Create `tests/unit/commands/liberica/orders.test.ts`:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
132
|
+
import { createOrdersCommand } from '../../../src/commands/liberica/orders.js';
|
|
133
|
+
|
|
134
|
+
describe('liberica orders', () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
vi.clearAllMocks();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should create orders command', () => {
|
|
140
|
+
const command = createOrdersCommand();
|
|
141
|
+
expect(command.name()).toBe('orders');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Step 4: Verify
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm run build
|
|
150
|
+
npm test
|
|
151
|
+
node ./bin/barista liberica orders --help
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Output Formatting Standards
|
|
155
|
+
|
|
156
|
+
### JSON Output
|
|
157
|
+
|
|
158
|
+
Use `--json` flag for machine-readable output:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
barista liberica orders list --json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Output:
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"success": true,
|
|
168
|
+
"data": {
|
|
169
|
+
"items": [...],
|
|
170
|
+
"pagination": { "page": 1, "size": 20, "total": 100 }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Table Output (TTY)
|
|
176
|
+
|
|
177
|
+
For human-readable output when stdout is a terminal:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import chalk from 'chalk';
|
|
181
|
+
import Table from 'cli-table3';
|
|
182
|
+
|
|
183
|
+
function formatOrdersTable(orders: Order[]): void {
|
|
184
|
+
const table = new Table({
|
|
185
|
+
head: ['ID', 'Product', 'Quantity', 'Status', 'Created'],
|
|
186
|
+
colWidths: [10, 30, 10, 15, 20],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
for (const order of orders) {
|
|
190
|
+
table.push([
|
|
191
|
+
order.id,
|
|
192
|
+
order.productName,
|
|
193
|
+
order.quantity,
|
|
194
|
+
formatStatus(order.status),
|
|
195
|
+
order.createdAt,
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(table.toString());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function formatStatus(status: string): string {
|
|
203
|
+
const colors: Record<string, chalk.Chalk> = {
|
|
204
|
+
pending: chalk.yellow,
|
|
205
|
+
in_production: chalk.blue,
|
|
206
|
+
completed: chalk.green,
|
|
207
|
+
cancelled: chalk.red,
|
|
208
|
+
};
|
|
209
|
+
return colors[status] ? colors[status](status) : status;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Auto-Detection
|
|
214
|
+
|
|
215
|
+
Detect output format automatically:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
function shouldUseJson(): boolean {
|
|
219
|
+
return process.stdout.isTTY === false || process.argv.includes('--json');
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Error Handling Patterns
|
|
224
|
+
|
|
225
|
+
### API Errors
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { APIResponse } from '../../types/index.js';
|
|
229
|
+
|
|
230
|
+
async function fetchOrders(): Promise<Order[]> {
|
|
231
|
+
try {
|
|
232
|
+
const response = await axios.get<APIResponse<Order[]>>('/api/orders');
|
|
233
|
+
|
|
234
|
+
if (!response.data.success) {
|
|
235
|
+
throw new Error(response.data.error?.message || 'Unknown error');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return response.data.data || [];
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (axios.isAxiosError(error)) {
|
|
241
|
+
if (error.response) {
|
|
242
|
+
// Server responded with error
|
|
243
|
+
const status = error.response.status;
|
|
244
|
+
if (status === 401) {
|
|
245
|
+
throw new Error('Authentication failed. Run "barista auth login"');
|
|
246
|
+
} else if (status === 403) {
|
|
247
|
+
throw new Error('Access denied');
|
|
248
|
+
}
|
|
249
|
+
} else if (error.request) {
|
|
250
|
+
// No response received
|
|
251
|
+
throw new Error('Network error. Check your connection.');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### User Errors
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
function validateOrderId(id: string): number {
|
|
263
|
+
const numericId = parseInt(id, 10);
|
|
264
|
+
if (isNaN(numericId) || numericId <= 0) {
|
|
265
|
+
console.error(chalk.red(`✗ Invalid order ID: ${id}`));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
return numericId;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Dry-Run Mode Implementation
|
|
273
|
+
|
|
274
|
+
For destructive operations, implement dry-run:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
ordersCommand
|
|
278
|
+
.command('cancel <order-id>')
|
|
279
|
+
.description('Cancel an order')
|
|
280
|
+
.option('--dry-run', 'Preview without executing')
|
|
281
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
282
|
+
.action(async (orderId: string, options) => {
|
|
283
|
+
const order = await fetchOrder(orderId);
|
|
284
|
+
|
|
285
|
+
console.log(chalk.bold('\n🔍 Cancel Order Preview\n'));
|
|
286
|
+
console.log(` ${chalk.gray('Order ID:')} ${orderId}`);
|
|
287
|
+
console.log(` ${chalk.gray('Product:')} ${order.productName}`);
|
|
288
|
+
console.log(` ${chalk.gray('Quantity:')} ${order.quantity}\n`);
|
|
289
|
+
|
|
290
|
+
if (options.dryRun) {
|
|
291
|
+
console.log(chalk.yellow('⚠ Dry-run mode: No changes made\n'));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!options.confirm) {
|
|
296
|
+
const { confirmed } = await inquirer.prompt([
|
|
297
|
+
{ type: 'confirm', name: 'confirmed', message: 'Confirm cancellation?' },
|
|
298
|
+
]);
|
|
299
|
+
if (!confirmed) {
|
|
300
|
+
console.log(chalk.gray('Cancelled.'));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
await cancelOrder(orderId);
|
|
306
|
+
console.log(chalk.green('✓ Order cancelled\n'));
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Testing Commands
|
|
311
|
+
|
|
312
|
+
### Mocking Dependencies
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
316
|
+
import axios from 'axios';
|
|
317
|
+
|
|
318
|
+
vi.mock('axios');
|
|
319
|
+
const mockedAxios = vi.mocked(axios);
|
|
320
|
+
|
|
321
|
+
describe('orders list', () => {
|
|
322
|
+
it('should list orders successfully', async () => {
|
|
323
|
+
mockedAxios.get.mockResolvedValue({
|
|
324
|
+
data: {
|
|
325
|
+
success: true,
|
|
326
|
+
data: [
|
|
327
|
+
{ id: '1', productName: 'Coffee Machine', quantity: 10 },
|
|
328
|
+
],
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Test implementation...
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Full Command Reference
|
|
338
|
+
|
|
339
|
+
### 已实现命令
|
|
340
|
+
|
|
341
|
+
查看 [命令参考](./commands/REFERENCE.md) 获取完整的命令清单和状态。
|
|
342
|
+
|
|
343
|
+
### 命令设计规范
|
|
344
|
+
|
|
345
|
+
**新增命令必须先编写设计文档**,参考 `COMMAND_DESIGN_SPEC.md` 第2节模板,包含:
|
|
346
|
+
- 后端接口引用(Controller、DTO位置)
|
|
347
|
+
- CLI参数设计
|
|
348
|
+
- 字段映射表
|
|
349
|
+
- 错误码引用
|
|
350
|
+
- 开发流程检查清单
|
|
351
|
+
|
|
352
|
+
所有待开发命令的状态和优先级请查看 [命令参考](./commands/REFERENCE.md)。
|