ai-summon 0.0.1
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/.claude/commands/speckit.analyze.md +184 -0
- package/.claude/commands/speckit.checklist.md +294 -0
- package/.claude/commands/speckit.clarify.md +177 -0
- package/.claude/commands/speckit.constitution.md +78 -0
- package/.claude/commands/speckit.implement.md +121 -0
- package/.claude/commands/speckit.plan.md +81 -0
- package/.claude/commands/speckit.specify.md +204 -0
- package/.claude/commands/speckit.tasks.md +108 -0
- package/.claude/settings.local.json +23 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +10 -0
- package/.specify/memory/constitution.md +72 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +113 -0
- package/.specify/scripts/bash/create-new-feature.sh +97 -0
- package/.specify/scripts/bash/setup-plan.sh +60 -0
- package/.specify/scripts/bash/update-agent-context.sh +738 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/plan-template.md +111 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +250 -0
- package/CLAUDE.md +199 -0
- package/PRD.md +268 -0
- package/README.md +171 -0
- package/dist/ai-summon.d.ts +2 -0
- package/dist/ai-summon.js +73 -0
- package/dist/commands/ide/index.d.ts +3 -0
- package/dist/commands/ide/index.js +253 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +55 -0
- package/dist/commands/url.d.ts +4 -0
- package/dist/commands/url.js +223 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.js +1 -0
- package/dist/util.d.ts +16 -0
- package/dist/util.js +109 -0
- package/eslint.config.js +47 -0
- package/package.json +47 -0
- package/specs/001-cloud-login-feature/contracts/cloud-command.ts +82 -0
- package/specs/001-cloud-login-feature/contracts/config-service.ts +170 -0
- package/specs/001-cloud-login-feature/data-model.md +269 -0
- package/specs/001-cloud-login-feature/plan.md +91 -0
- package/specs/001-cloud-login-feature/quickstart.md +366 -0
- package/specs/001-cloud-login-feature/research.md +290 -0
- package/specs/001-cloud-login-feature/spec.md +195 -0
- package/specs/001-cloud-login-feature/tasks.md +235 -0
- package/specs/001-cloud-scp-command/contracts/cloud-scp-api.ts +402 -0
- package/specs/001-cloud-scp-command/data-model.md +424 -0
- package/specs/001-cloud-scp-command/plan.md +124 -0
- package/specs/001-cloud-scp-command/quickstart.md +536 -0
- package/specs/001-cloud-scp-command/research.md +345 -0
- package/specs/001-cloud-scp-command/spec.md +248 -0
- package/specs/001-cloud-scp-command/tasks.md +434 -0
- package/src/ai-summon.ts +88 -0
- package/src/commands/ide/index.ts +322 -0
- package/src/commands/init.ts +64 -0
- package/src/commands/url.ts +262 -0
- package/src/types/index.ts +49 -0
- package/src/util.ts +146 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Data Model: Cloud SCP Command
|
|
2
|
+
|
|
3
|
+
**Feature**: 001-cloud-scp-command
|
|
4
|
+
**Date**: 2025-10-11
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This document defines the data structures, types, and state management for the `hsh cloud scp` command. Since this feature extends the existing cloud infrastructure, it primarily reuses existing types and adds minimal new structures.
|
|
9
|
+
|
|
10
|
+
## Type Definitions
|
|
11
|
+
|
|
12
|
+
### Existing Types (Reused from types/index.ts)
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// Environment type - already defined in cloud login
|
|
16
|
+
export type Environment = 'dev' | 'staging' | 'prod';
|
|
17
|
+
|
|
18
|
+
// Cloud configuration interface - already defined
|
|
19
|
+
export interface CloudConfig {
|
|
20
|
+
ip: string;
|
|
21
|
+
privateKeyFile: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Service configuration - already defined
|
|
25
|
+
export interface ServiceConfig {
|
|
26
|
+
[key: string]: CloudConfig;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Full configuration structure - already defined
|
|
30
|
+
export interface Config {
|
|
31
|
+
repos?: {
|
|
32
|
+
[category: string]: {
|
|
33
|
+
[project: string]: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
yiren: {
|
|
37
|
+
[service: string]: {
|
|
38
|
+
dev?: CloudConfig;
|
|
39
|
+
staging?: CloudConfig;
|
|
40
|
+
prod?: CloudConfig;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### New Types (SCP-Specific)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// SCP command options interface
|
|
50
|
+
export interface ScpOptions {
|
|
51
|
+
env?: Environment;
|
|
52
|
+
service?: string;
|
|
53
|
+
recursive?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Path validation result
|
|
57
|
+
export interface PathValidationResult {
|
|
58
|
+
exists: boolean;
|
|
59
|
+
isDirectory: boolean;
|
|
60
|
+
isFile: boolean;
|
|
61
|
+
path: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// SCP operation metadata (for logging/debugging)
|
|
65
|
+
export interface ScpOperation {
|
|
66
|
+
localPath: string;
|
|
67
|
+
remotePath: string;
|
|
68
|
+
service: string;
|
|
69
|
+
environment: Environment;
|
|
70
|
+
isDirectory: boolean;
|
|
71
|
+
timestamp: Date;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Entities
|
|
76
|
+
|
|
77
|
+
### Entity 1: SCP Command Parameters
|
|
78
|
+
|
|
79
|
+
**Purpose**: Encapsulates all parameters required to execute an SCP operation
|
|
80
|
+
|
|
81
|
+
**Fields**:
|
|
82
|
+
|
|
83
|
+
- `localPath: string` - Source file or directory path on local filesystem
|
|
84
|
+
- `remotePath: string` - Destination path on remote server
|
|
85
|
+
- `options: ScpOptions` - Command options including environment, service, and recursive flag
|
|
86
|
+
|
|
87
|
+
**Validation Rules**:
|
|
88
|
+
|
|
89
|
+
- `localPath`: Must exist on local filesystem (validated before execution)
|
|
90
|
+
- `remotePath`: Must be non-empty string (format validation delegated to SSH/SCP)
|
|
91
|
+
- `options.recursive`: Required if localPath is a directory
|
|
92
|
+
- `options.env`: Must be one of 'dev' | 'staging' | 'prod' (if provided)
|
|
93
|
+
- `options.service`: Must exist in configuration (if provided)
|
|
94
|
+
|
|
95
|
+
**Usage**:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
interface ScpCommandParams {
|
|
99
|
+
localPath: string;
|
|
100
|
+
remotePath: string;
|
|
101
|
+
options: ScpOptions;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Entity 2: Path Validation State
|
|
106
|
+
|
|
107
|
+
**Purpose**: Represents the result of local path validation
|
|
108
|
+
|
|
109
|
+
**Fields**:
|
|
110
|
+
|
|
111
|
+
- `exists: boolean` - Whether the path exists on filesystem
|
|
112
|
+
- `isDirectory: boolean` - Whether the path is a directory
|
|
113
|
+
- `isFile: boolean` - Whether the path is a regular file
|
|
114
|
+
- `path: string` - The validated path (resolved/absolute)
|
|
115
|
+
|
|
116
|
+
**State Transitions**:
|
|
117
|
+
|
|
118
|
+
1. **Initial**: Path provided by user → validation pending
|
|
119
|
+
2. **Validated**: Path checked via fs.stat() → exists/type determined
|
|
120
|
+
3. **Error**: Path doesn't exist or permission denied → error state
|
|
121
|
+
|
|
122
|
+
**Validation Flow**:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
async function validatePath(path: string): Promise<PathValidationResult> {
|
|
126
|
+
try {
|
|
127
|
+
const stats = await stat(path);
|
|
128
|
+
return {
|
|
129
|
+
exists: true,
|
|
130
|
+
isDirectory: stats.isDirectory(),
|
|
131
|
+
isFile: stats.isFile(),
|
|
132
|
+
path: path,
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error.code === 'ENOENT') {
|
|
136
|
+
return {
|
|
137
|
+
exists: false,
|
|
138
|
+
isDirectory: false,
|
|
139
|
+
isFile: false,
|
|
140
|
+
path: path,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
throw error; // Permission errors, etc.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Entity 3: SCP Execution Context
|
|
149
|
+
|
|
150
|
+
**Purpose**: Complete context required to execute SCP command
|
|
151
|
+
|
|
152
|
+
**Fields**:
|
|
153
|
+
|
|
154
|
+
- `service: string` - Selected service name
|
|
155
|
+
- `environment: Environment` - Selected environment
|
|
156
|
+
- `cloudConfig: CloudConfig` - IP and private key from configuration
|
|
157
|
+
- `localPath: string` - Validated local source path
|
|
158
|
+
- `remotePath: string` - Remote destination path
|
|
159
|
+
- `isRecursive: boolean` - Whether to use -r flag
|
|
160
|
+
|
|
161
|
+
**Derivation**:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface ScpExecutionContext {
|
|
165
|
+
service: string;
|
|
166
|
+
environment: Environment;
|
|
167
|
+
cloudConfig: CloudConfig;
|
|
168
|
+
localPath: string;
|
|
169
|
+
remotePath: string;
|
|
170
|
+
isRecursive: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Derived from user input + configuration + validation
|
|
174
|
+
function buildExecutionContext(
|
|
175
|
+
params: ScpCommandParams,
|
|
176
|
+
config: Config,
|
|
177
|
+
pathInfo: PathValidationResult
|
|
178
|
+
): ScpExecutionContext {
|
|
179
|
+
return {
|
|
180
|
+
service: params.options.service || await promptForService(...),
|
|
181
|
+
environment: params.options.env || await promptForEnvironment(...),
|
|
182
|
+
cloudConfig: config.yiren[service][environment],
|
|
183
|
+
localPath: pathInfo.path,
|
|
184
|
+
remotePath: params.remotePath,
|
|
185
|
+
isRecursive: pathInfo.isDirectory
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration Data Model
|
|
191
|
+
|
|
192
|
+
### Configuration File Structure
|
|
193
|
+
|
|
194
|
+
The SCP command uses the existing `~/.ai/config.json` structure:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"yiren": {
|
|
199
|
+
"service-name": {
|
|
200
|
+
"dev": {
|
|
201
|
+
"ip": "192.168.1.10",
|
|
202
|
+
"privateKeyFile": "/path/to/dev-key.pem"
|
|
203
|
+
},
|
|
204
|
+
"staging": {
|
|
205
|
+
"ip": "192.168.1.20",
|
|
206
|
+
"privateKeyFile": "/path/to/staging-key.pem"
|
|
207
|
+
},
|
|
208
|
+
"prod": {
|
|
209
|
+
"ip": "192.168.1.30",
|
|
210
|
+
"privateKeyFile": "/path/to/prod-key.pem"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**No Schema Changes Required**: SCP command fully reuses cloud login configuration
|
|
218
|
+
|
|
219
|
+
## Data Flow
|
|
220
|
+
|
|
221
|
+
### 1. Command Input → Validation
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
User Input:
|
|
225
|
+
hsh cloud scp -r ./dist /var/www/html --env prod --service my-app
|
|
226
|
+
|
|
227
|
+
↓
|
|
228
|
+
|
|
229
|
+
Parse Parameters:
|
|
230
|
+
localPath: "./dist"
|
|
231
|
+
remotePath: "/var/www/html"
|
|
232
|
+
options: { env: "prod", service: "my-app", recursive: true }
|
|
233
|
+
|
|
234
|
+
↓
|
|
235
|
+
|
|
236
|
+
Validate Local Path:
|
|
237
|
+
await stat("./dist")
|
|
238
|
+
→ { exists: true, isDirectory: true, isFile: false }
|
|
239
|
+
|
|
240
|
+
↓
|
|
241
|
+
|
|
242
|
+
Validate Recursive Flag:
|
|
243
|
+
isDirectory: true
|
|
244
|
+
recursive: true
|
|
245
|
+
→ ✅ Valid
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 2. Configuration Resolution
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
Configuration Lookup:
|
|
252
|
+
config.yiren["my-app"]["prod"]
|
|
253
|
+
|
|
254
|
+
↓
|
|
255
|
+
|
|
256
|
+
Extract CloudConfig:
|
|
257
|
+
{
|
|
258
|
+
ip: "192.168.1.30",
|
|
259
|
+
privateKeyFile: "/path/to/prod-key.pem"
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
↓
|
|
263
|
+
|
|
264
|
+
Validate Private Key:
|
|
265
|
+
await validatePrivateKey("/path/to/prod-key.pem")
|
|
266
|
+
→ Check existence, permissions
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 3. Interactive Prompts (if options missing)
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
If options.service === undefined:
|
|
273
|
+
services = Object.keys(config.yiren)
|
|
274
|
+
selectedService = await promptForService(services)
|
|
275
|
+
|
|
276
|
+
If options.env === undefined:
|
|
277
|
+
environments = Object.keys(config.yiren[service])
|
|
278
|
+
selectedEnv = await promptForEnvironment(environments)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 4. Execution Context → SCP Command
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
Build Context:
|
|
285
|
+
{
|
|
286
|
+
service: "my-app",
|
|
287
|
+
environment: "prod",
|
|
288
|
+
cloudConfig: { ip: "...", privateKeyFile: "..." },
|
|
289
|
+
localPath: "./dist",
|
|
290
|
+
remotePath: "/var/www/html",
|
|
291
|
+
isRecursive: true
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
↓
|
|
295
|
+
|
|
296
|
+
Construct SCP Command:
|
|
297
|
+
scp -i /path/to/prod-key.pem scp -r ./dist root@192.168.1.30:/var/www/html
|
|
298
|
+
|
|
299
|
+
↓
|
|
300
|
+
|
|
301
|
+
Execute via zx:
|
|
302
|
+
await $`scp -i ${privateKey} scp ${flags} ${localPath} root@${ip}:${remotePath}`
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Error States
|
|
306
|
+
|
|
307
|
+
### Path Validation Errors
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// Error State 1: Path doesn't exist
|
|
311
|
+
{
|
|
312
|
+
exists: false,
|
|
313
|
+
isDirectory: false,
|
|
314
|
+
isFile: false,
|
|
315
|
+
path: "./nonexistent"
|
|
316
|
+
}
|
|
317
|
+
→ Error: "❌ Local path not found: ./nonexistent"
|
|
318
|
+
|
|
319
|
+
// Error State 2: Directory without -r flag
|
|
320
|
+
{
|
|
321
|
+
exists: true,
|
|
322
|
+
isDirectory: true,
|
|
323
|
+
isFile: false,
|
|
324
|
+
path: "./dist"
|
|
325
|
+
}
|
|
326
|
+
+ options.recursive: false
|
|
327
|
+
→ Error: "❌ Cannot copy directory without -r flag"
|
|
328
|
+
|
|
329
|
+
// Error State 3: Permission denied
|
|
330
|
+
throw { code: 'EACCES', path: './restricted' }
|
|
331
|
+
→ Error: "❌ Permission denied: ./restricted"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Configuration Errors
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Error State 4: Service not found
|
|
338
|
+
config.yiren["unknown-service"]
|
|
339
|
+
→ undefined
|
|
340
|
+
→ Error: "❌ Service 'unknown-service' not found in configuration"
|
|
341
|
+
|
|
342
|
+
// Error State 5: Environment not configured
|
|
343
|
+
config.yiren["my-app"]["unknown-env"]
|
|
344
|
+
→ undefined
|
|
345
|
+
→ Error: "❌ Environment 'unknown-env' not configured for service 'my-app'"
|
|
346
|
+
|
|
347
|
+
// Error State 6: No services configured
|
|
348
|
+
config.yiren
|
|
349
|
+
→ {} (empty)
|
|
350
|
+
→ Error: "❌ No cloud services configured in yiren section"
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### SSH/SCP Errors
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// Error State 7: Connection timeout
|
|
357
|
+
→ Handled by handleSSHError()
|
|
358
|
+
→ "❌ Connection timeout - check network connectivity and IP address"
|
|
359
|
+
|
|
360
|
+
// Error State 8: Authentication failure
|
|
361
|
+
→ Handled by handleSSHError()
|
|
362
|
+
→ "❌ Authentication failed - check private key file permissions and path"
|
|
363
|
+
|
|
364
|
+
// Error State 9: Host unreachable
|
|
365
|
+
→ Handled by handleSSHError()
|
|
366
|
+
→ "❌ Host unreachable - verify IP address and network access"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## State Management
|
|
370
|
+
|
|
371
|
+
This feature is stateless - all state is ephemeral and exists only during command execution:
|
|
372
|
+
|
|
373
|
+
1. **Input State**: Command-line arguments and options (ephemeral)
|
|
374
|
+
2. **Configuration State**: Read from `~/.ai/config.json` (persistent, read-only)
|
|
375
|
+
3. **Validation State**: Path validation results (ephemeral, derived)
|
|
376
|
+
4. **Execution State**: SCP execution context (ephemeral, derived)
|
|
377
|
+
|
|
378
|
+
**No Persistent State**: No databases, no session storage, no state files
|
|
379
|
+
|
|
380
|
+
## Type Safety Guarantees
|
|
381
|
+
|
|
382
|
+
### TypeScript Strict Mode Enforcement
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// All functions have explicit return types
|
|
386
|
+
async function cloudScp(localPath: string, remotePath: string, options: ScpOptions): Promise<void>;
|
|
387
|
+
|
|
388
|
+
// All parameters are typed
|
|
389
|
+
async function validateLocalPath(path: string): Promise<PathValidationResult>;
|
|
390
|
+
|
|
391
|
+
// Error handling preserves types
|
|
392
|
+
try {
|
|
393
|
+
await cloudScp(localPath, remotePath, options);
|
|
394
|
+
} catch (error: any) {
|
|
395
|
+
handleSSHError(error);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Interface Validation
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// Configuration structure is validated at runtime
|
|
403
|
+
function isCloudConfig(obj: any): obj is CloudConfig {
|
|
404
|
+
return obj && typeof obj.ip === 'string' && typeof obj.privateKeyFile === 'string';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Environment validation
|
|
408
|
+
function isEnvironment(value: any): value is Environment {
|
|
409
|
+
return value === 'dev' || value === 'staging' || value === 'prod';
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Summary
|
|
414
|
+
|
|
415
|
+
The data model for `hsh cloud scp` is intentionally minimal:
|
|
416
|
+
|
|
417
|
+
- **Reuses 100%** of existing cloud login types (`Environment`, `CloudConfig`, `ServiceConfig`, `Config`)
|
|
418
|
+
- **Adds 3 new types** for SCP-specific needs (`ScpOptions`, `PathValidationResult`, `ScpOperation`)
|
|
419
|
+
- **No schema changes** to configuration file structure
|
|
420
|
+
- **Stateless design** with ephemeral state during execution
|
|
421
|
+
- **Type-safe** with strict TypeScript enforcement
|
|
422
|
+
- **Validated** at each stage: path → configuration → execution
|
|
423
|
+
|
|
424
|
+
This aligns with the constitution's TypeScript-First principle and the modular architecture requirement by maintaining clear separation between types, validation, and execution logic.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Implementation Plan: Cloud SCP Command
|
|
2
|
+
|
|
3
|
+
**Branch**: `001-cloud-scp-command` | **Date**: 2025-10-11 | **Spec**: [spec.md](./spec.md)
|
|
4
|
+
**Input**: Feature specification from `/specs/001-cloud-scp-command/spec.md`
|
|
5
|
+
|
|
6
|
+
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
|
7
|
+
|
|
8
|
+
## Summary
|
|
9
|
+
|
|
10
|
+
Add `hsh cloud scp` command to enable secure file/directory copying to cloud instances. The command extends existing cloud infrastructure management by reusing the same configuration structure and helper functions from `cloud login`. It will support interactive service/environment selection, automatic file vs directory detection, and production safety confirmations.
|
|
11
|
+
|
|
12
|
+
## Technical Context
|
|
13
|
+
|
|
14
|
+
**Language/Version**: TypeScript 5.0+ with ES2020 target and ESNext modules
|
|
15
|
+
**Primary Dependencies**:
|
|
16
|
+
|
|
17
|
+
- commander (CLI framework for command/option parsing)
|
|
18
|
+
- inquirer (interactive prompts with validation)
|
|
19
|
+
- zx (shell command execution)
|
|
20
|
+
- chalk (terminal colors for user feedback)
|
|
21
|
+
- fs/promises (file system operations for path validation)
|
|
22
|
+
|
|
23
|
+
**Storage**: Configuration from `~/.ai/config.json` (existing yiren section)
|
|
24
|
+
**Testing**: Manual CLI testing (consistent with project patterns)
|
|
25
|
+
**Target Platform**: Node.js CLI (global installation via yarn)
|
|
26
|
+
**Project Type**: Single TypeScript CLI project
|
|
27
|
+
**Performance Goals**: Command initialization <2s (excluding actual file transfer time)
|
|
28
|
+
**Constraints**:
|
|
29
|
+
|
|
30
|
+
- Reuse existing cloud.ts helper functions (validatePrivateKey, promptForService, promptForEnvironment, handleSSHError)
|
|
31
|
+
- Maintain backward compatibility with cloud login configuration
|
|
32
|
+
- Follow SCP standard conventions for flags and argument order
|
|
33
|
+
|
|
34
|
+
**Scale/Scope**:
|
|
35
|
+
|
|
36
|
+
- Single new subcommand addition to existing cloud command group
|
|
37
|
+
- Reuses 4 existing helper functions from cloud.ts
|
|
38
|
+
- Adds 1 new function (cloudScp) to src/commands/cloud.ts
|
|
39
|
+
- Updates hsh.ts with new subcommand definition (~10 lines)
|
|
40
|
+
|
|
41
|
+
## Constitution Check
|
|
42
|
+
|
|
43
|
+
_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
|
|
44
|
+
|
|
45
|
+
✅ **TypeScript-First**: All code written in TypeScript with strict mode, fully typed
|
|
46
|
+
|
|
47
|
+
- New cloudScp function will be fully typed with CloudConfig, Environment interfaces
|
|
48
|
+
- Path validation uses typed fs/promises API
|
|
49
|
+
- All parameters and return types explicitly defined
|
|
50
|
+
|
|
51
|
+
✅ **Shell Integration**: Uses zx library for all shell operations with async/await
|
|
52
|
+
|
|
53
|
+
- SCP command execution via `$` template literals from zx
|
|
54
|
+
- All shell operations are async/await with proper error handling
|
|
55
|
+
- No direct child_process usage
|
|
56
|
+
|
|
57
|
+
✅ **Interactive CLI**: Uses inquirer prompts with validation and chalk/ora for feedback
|
|
58
|
+
|
|
59
|
+
- Reuses promptForService and promptForEnvironment from cloud.ts
|
|
60
|
+
- Chalk for colored success/error messages
|
|
61
|
+
- Input validation before SCP execution
|
|
62
|
+
|
|
63
|
+
✅ **Modular Architecture**: Commands organized by domain in separate modules
|
|
64
|
+
|
|
65
|
+
- cloudScp function added to src/commands/cloud.ts (cloud domain)
|
|
66
|
+
- Command registration in hsh.ts delegates to cloud module
|
|
67
|
+
- Follows existing cloud login pattern
|
|
68
|
+
|
|
69
|
+
✅ **Yarn Package Management**: Uses Yarn with standard script patterns
|
|
70
|
+
|
|
71
|
+
- No new dependencies required
|
|
72
|
+
- Follows existing build, dev, start patterns
|
|
73
|
+
- Global installation via existing bin configuration
|
|
74
|
+
|
|
75
|
+
## Project Structure
|
|
76
|
+
|
|
77
|
+
### Documentation (this feature)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
specs/001-cloud-scp-command/
|
|
81
|
+
├── spec.md # Feature specification
|
|
82
|
+
├── plan.md # This file (/speckit.plan command output)
|
|
83
|
+
├── research.md # Phase 0 output (/speckit.plan command)
|
|
84
|
+
├── data-model.md # Phase 1 output (/speckit.plan command)
|
|
85
|
+
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
|
86
|
+
├── contracts/ # Phase 1 output (/speckit.plan command)
|
|
87
|
+
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Source Code (repository root)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
src/
|
|
94
|
+
├── commands/
|
|
95
|
+
│ ├── cloud.ts # MODIFIED: Add cloudScp function and SCP-related helpers
|
|
96
|
+
│ ├── git.ts # Unchanged
|
|
97
|
+
│ ├── mono.ts # Unchanged
|
|
98
|
+
│ └── ide.ts # Unchanged
|
|
99
|
+
├── types/
|
|
100
|
+
│ └── index.ts # May add SCP-specific types if needed
|
|
101
|
+
├── util.ts # Unchanged
|
|
102
|
+
└── hsh.ts # MODIFIED: Add cloud scp subcommand registration
|
|
103
|
+
|
|
104
|
+
dist/ # Compiled TypeScript output
|
|
105
|
+
└── [mirrors src structure]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Structure Decision**: TypeScript CLI Project (existing structure)
|
|
109
|
+
|
|
110
|
+
- **Modified Files**:
|
|
111
|
+
- `src/commands/cloud.ts`: Add cloudScp function with path validation and SCP execution
|
|
112
|
+
- `src/hsh.ts`: Register new `cloud scp` subcommand with options/arguments
|
|
113
|
+
- **Unchanged Files**: git.ts, mono.ts, ide.ts, util.ts remain unchanged
|
|
114
|
+
- **No New Files**: All functionality fits within existing cloud.ts module
|
|
115
|
+
- **Type Reuse**: Uses existing CloudConfig, Environment types from types/index.ts
|
|
116
|
+
|
|
117
|
+
## Complexity Tracking
|
|
118
|
+
|
|
119
|
+
_Fill ONLY if Constitution Check has violations that must be justified_
|
|
120
|
+
|
|
121
|
+
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
122
|
+
| -------------------------- | ------------------ | ------------------------------------ |
|
|
123
|
+
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
|
124
|
+
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|