@sundial-ai/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.
Files changed (97) hide show
  1. package/PLAN.md +497 -0
  2. package/README.md +30 -0
  3. package/dist/commands/add.d.ts +13 -0
  4. package/dist/commands/add.d.ts.map +1 -0
  5. package/dist/commands/add.js +112 -0
  6. package/dist/commands/add.js.map +1 -0
  7. package/dist/commands/config.d.ts +5 -0
  8. package/dist/commands/config.d.ts.map +1 -0
  9. package/dist/commands/config.js +42 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/list.d.ts +5 -0
  12. package/dist/commands/list.d.ts.map +1 -0
  13. package/dist/commands/list.js +53 -0
  14. package/dist/commands/list.js.map +1 -0
  15. package/dist/commands/remove.d.ts +13 -0
  16. package/dist/commands/remove.d.ts.map +1 -0
  17. package/dist/commands/remove.js +127 -0
  18. package/dist/commands/remove.js.map +1 -0
  19. package/dist/commands/show.d.ts +5 -0
  20. package/dist/commands/show.d.ts.map +1 -0
  21. package/dist/commands/show.js +81 -0
  22. package/dist/commands/show.js.map +1 -0
  23. package/dist/core/agent-detect.d.ts +9 -0
  24. package/dist/core/agent-detect.d.ts.map +1 -0
  25. package/dist/core/agent-detect.js +44 -0
  26. package/dist/core/agent-detect.js.map +1 -0
  27. package/dist/core/agents.d.ts +8 -0
  28. package/dist/core/agents.d.ts.map +1 -0
  29. package/dist/core/agents.js +34 -0
  30. package/dist/core/agents.js.map +1 -0
  31. package/dist/core/config-manager.d.ts +9 -0
  32. package/dist/core/config-manager.d.ts.map +1 -0
  33. package/dist/core/config-manager.js +47 -0
  34. package/dist/core/config-manager.js.map +1 -0
  35. package/dist/core/skill-hash.d.ts +12 -0
  36. package/dist/core/skill-hash.d.ts.map +1 -0
  37. package/dist/core/skill-hash.js +53 -0
  38. package/dist/core/skill-hash.js.map +1 -0
  39. package/dist/core/skill-info.d.ts +35 -0
  40. package/dist/core/skill-info.d.ts.map +1 -0
  41. package/dist/core/skill-info.js +211 -0
  42. package/dist/core/skill-info.js.map +1 -0
  43. package/dist/core/skill-install.d.ts +24 -0
  44. package/dist/core/skill-install.d.ts.map +1 -0
  45. package/dist/core/skill-install.js +123 -0
  46. package/dist/core/skill-install.js.map +1 -0
  47. package/dist/core/skill-source.d.ts +29 -0
  48. package/dist/core/skill-source.d.ts.map +1 -0
  49. package/dist/core/skill-source.js +105 -0
  50. package/dist/core/skill-source.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +104 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/types/index.d.ts +57 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +2 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/dist/utils/fuzzy-match.d.ts +16 -0
  60. package/dist/utils/fuzzy-match.d.ts.map +1 -0
  61. package/dist/utils/fuzzy-match.js +37 -0
  62. package/dist/utils/fuzzy-match.js.map +1 -0
  63. package/dist/utils/prompts.d.ts +16 -0
  64. package/dist/utils/prompts.d.ts.map +1 -0
  65. package/dist/utils/prompts.js +80 -0
  66. package/dist/utils/prompts.js.map +1 -0
  67. package/dist/utils/registry.d.ts +6 -0
  68. package/dist/utils/registry.d.ts.map +1 -0
  69. package/dist/utils/registry.js +14 -0
  70. package/dist/utils/registry.js.map +1 -0
  71. package/package.json +43 -0
  72. package/publish.sh +2 -0
  73. package/src/commands/add.ts +137 -0
  74. package/src/commands/config.ts +49 -0
  75. package/src/commands/list.ts +68 -0
  76. package/src/commands/remove.ts +152 -0
  77. package/src/commands/show.ts +93 -0
  78. package/src/core/agent-detect.ts +53 -0
  79. package/src/core/agents.ts +40 -0
  80. package/src/core/config-manager.ts +55 -0
  81. package/src/core/skill-hash.ts +61 -0
  82. package/src/core/skill-info.ts +246 -0
  83. package/src/core/skill-install.ts +165 -0
  84. package/src/core/skill-source.ts +118 -0
  85. package/src/index.ts +116 -0
  86. package/src/types/index.ts +64 -0
  87. package/src/utils/fuzzy-match.ts +48 -0
  88. package/src/utils/prompts.ts +92 -0
  89. package/src/utils/registry.ts +16 -0
  90. package/test/agents.test.ts +86 -0
  91. package/test/fuzzy-match.test.ts +58 -0
  92. package/test/registry.test.ts +48 -0
  93. package/test/skill-hash.test.ts +77 -0
  94. package/test/skill-info.test.ts +195 -0
  95. package/test/skill-source.test.ts +89 -0
  96. package/tsconfig.json +20 -0
  97. package/vitest.config.ts +15 -0
package/PLAN.md ADDED
@@ -0,0 +1,497 @@
1
+ # PLAN.md
2
+
3
+ ## Commands Overview
4
+
5
+ ### `sun add <skill> [skill2] [skill3] ...`
6
+
7
+ **What it does:** Adds skill(s) to agent configuration(s)
8
+
9
+ **Sources:**
10
+ 1. Shortcut registry (e.g., `sun add tinker` → downloads from predefined GitHub URL)
11
+ 2. GitHub repo (contains `github.com`, e.g., `sun add github.com/user/my-skill`)
12
+ 3. Local folder path (e.g., `sun add ./my-local-skill` or `sun add ~/skills/custom-skill`)
13
+
14
+ **Flags:**
15
+ - `--global` - Install to global agent config (`~/.claude/`, `~/.codex/`, etc.)
16
+ - `--claude` - Install to Claude Code (can combine: `--claude --codex`)
17
+ - `--codex` - Install to Codex
18
+ - `--gemini` - Install to Gemini
19
+
20
+ **Behavior:**
21
+ - **First time adding ANY skill:** Detect installed agents (`.claude/`, `.codex/`, `.gemini/` in current dir or global), show dialog: "We've detected these agents installed, which ones do you want to add to by default?" ALL detected agents selected by default, user confirms.
22
+ - **After first time:** Uses default agent(s) from config
23
+ - **Can specify multiple agents:** `sun add tinker --claude --codex` installs to both
24
+ - **Local installs:** Show message "(use --global to install globally)"
25
+ - **Confirmation message:** "Added web-search, file-manager to .claude/ and .codex/ (use --global to install globally)"
26
+ - **Multiple skills:** Process all, report success/failure for each
27
+
28
+ ---
29
+
30
+ ### `sun remove <skill> [skill2] [skill3] ...`
31
+
32
+ **What it does:** Removes skill(s) from agent configuration(s)
33
+
34
+ **Flags:**
35
+ - `--global` - Remove from global config
36
+ - `--claude` - Remove from Claude Code (can combine: `--claude --gemini`)
37
+ - `--codex` - Remove from Codex
38
+ - `--gemini` - Remove from Gemini
39
+
40
+ **Behavior:**
41
+ - Uses same default agents as `add`
42
+ - Can specify multiple agents to remove from
43
+ - Confirmation message: "Removed web-search from .claude/ and .codex/"
44
+ - If skill not found, show error but continue with other skills
45
+
46
+ ---
47
+
48
+ ### `sun list`
49
+
50
+ **What it does:** Lists all installed skills for each agent
51
+
52
+ **Output format:**
53
+ ```
54
+ Claude Code (.claude/):
55
+ - web-search
56
+ - file-manager
57
+
58
+ Codex (.codex/):
59
+ - web-search
60
+
61
+ Gemini (global ~/.gemini/):
62
+ - code-analyzer
63
+
64
+ Currently supported agents: Claude Code, Codex, Gemini
65
+ You can add skills with "sun add <skill>" or remove with "sun remove <skill>"
66
+ ```
67
+
68
+ ---
69
+
70
+ ### `sun show <skill>`
71
+
72
+ **What it does:** Shows skill details and installation locations
73
+
74
+ **Output format:**
75
+ ```
76
+ Skill: web-search
77
+ Description: Searches the web using multiple providers
78
+ Version: 1.2.0
79
+ Author: Sundial Team
80
+
81
+ Installed in:
82
+ - .claude/ (local)
83
+ - ~/.codex/ (global)
84
+ ```
85
+
86
+ **Special case:** If same skill name exists at different paths with different contents:
87
+ ```
88
+ Skill: custom-tool
89
+ Warning: Multiple versions detected
90
+
91
+ Version 1 (.claude/):
92
+ Source: github.com/user/custom-tool
93
+ Author: User Name
94
+ Content hash: abc123def456
95
+
96
+ Version 2 (~/.gemini/):
97
+ Source: /home/user/my-tools/custom-tool
98
+ Author: Local Dev
99
+ Content hash: 789xyz012abc
100
+ ```
101
+
102
+ **Note on hash:** Content hash is computed by hashing the skill folder contents (all files) to detect if two skills with same name have different implementations.
103
+
104
+ ---
105
+
106
+ ### `sun config`
107
+
108
+ **What it does:** Re-opens agent selection dialog
109
+
110
+ **Behavior:**
111
+ - Shows current default agents
112
+ - Shows currently supported agents (from constants)
113
+ - Allows user to select/deselect agents
114
+ - Saves to `~/.sun/config.json`
115
+
116
+ **Output includes:**
117
+ ```
118
+ Currently supported agents: Claude Code, Codex, Gemini
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Skill Registry
124
+
125
+ **Initial shortcuts (hardcoded in code):**
126
+ ```typescript
127
+ const SKILL_SHORTCUTS: Record<string, string> = {
128
+ tinker: 'https://github.com/sundial-org/skills/tree/main/skills/tinker'
129
+ };
130
+ ```
131
+
132
+ When resolving skill source:
133
+ 1. Check if it's a shortcut → use registry URL
134
+ 2. Check if contains "github.com" → treat as GitHub URL
135
+ 3. Check if valid path → treat as local
136
+ 4. Otherwise → error "Skill not found"
137
+
138
+ ---
139
+
140
+ ## Agent Configuration
141
+
142
+ **Constants file (`src/core/agents.ts`):**
143
+ ```typescript
144
+ export interface AgentConfig {
145
+ name: string; // Display name
146
+ flag: string; // CLI flag name
147
+ folderName: string; // Folder name (.claude, .codex, etc.)
148
+ }
149
+
150
+ export const SUPPORTED_AGENTS: AgentConfig[] = [
151
+ {
152
+ name: 'Claude Code',
153
+ flag: 'claude',
154
+ folderName: '.claude'
155
+ },
156
+ {
157
+ name: 'Codex',
158
+ flag: 'codex',
159
+ folderName: '.codex'
160
+ },
161
+ {
162
+ name: 'Gemini',
163
+ flag: 'gemini',
164
+ folderName: '.gemini'
165
+ }
166
+ ];
167
+
168
+ export function getAgentByFlag(flag: string): AgentConfig | undefined {
169
+ return SUPPORTED_AGENTS.find(agent => agent.flag === flag);
170
+ }
171
+
172
+ export function getAgentByFolder(folderName: string): AgentConfig | undefined {
173
+ return SUPPORTED_AGENTS.find(agent => agent.folderName === folderName);
174
+ }
175
+
176
+ export function getSupportedAgentsMessage(): string {
177
+ const agentNames = SUPPORTED_AGENTS.map(a => a.name).join(', ');
178
+ return `Currently supported agents: ${agentNames}`;
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Implementation Plan
185
+
186
+ ### 1. Project Structure
187
+ ```
188
+ sundial-cli/
189
+ ├── src/
190
+ │ ├── index.ts # CLI entry point
191
+ │ ├── commands/
192
+ │ │ ├── add.ts # Add command
193
+ │ │ ├── remove.ts # Remove command
194
+ │ │ ├── list.ts # List command
195
+ │ │ ├── show.ts # Show command
196
+ │ │ └── config.ts # Config command
197
+ │ ├── core/
198
+ │ │ ├── agents.ts # Agent configuration constants
199
+ │ │ ├── agent-detect.ts # Detect installed agents
200
+ │ │ ├── config-manager.ts # Config file management
201
+ │ │ ├── skill-source.ts # Determine skill source
202
+ │ │ ├── skill-install.ts # Install skill to agent folder
203
+ │ │ ├── skill-info.ts # Read skill metadata
204
+ │ │ └── skill-hash.ts # Compute content hash
205
+ │ ├── utils/
206
+ │ │ ├── fuzzy-match.ts # Command suggestion
207
+ │ │ ├── prompts.ts # Interactive prompts
208
+ │ │ └── registry.ts # Skill shortcuts registry
209
+ │ └── types/
210
+ │ └── index.ts # TypeScript types
211
+ ├── tests/
212
+ │ ├── add.test.ts
213
+ │ ├── remove.test.ts
214
+ │ ├── list.test.ts
215
+ │ ├── show.test.ts
216
+ │ ├── config.test.ts
217
+ │ ├── agent-detect.test.ts
218
+ │ ├── skill-source.test.ts
219
+ │ └── skill-hash.test.ts
220
+ ├── package.json
221
+ ├── tsconfig.json
222
+ └── README.md
223
+ ```
224
+
225
+ ### 2. TypeScript Types
226
+ ```typescript
227
+ // src/types/index.ts
228
+
229
+ export type AgentType = 'claude' | 'codex' | 'gemini';
230
+
231
+ export interface SunConfig {
232
+ defaultAgents: AgentType[];
233
+ firstRunComplete: boolean;
234
+ skillRegistryUrl?: string;
235
+ }
236
+
237
+ export interface SkillMetadata {
238
+ name: string;
239
+ description: string;
240
+ version: string;
241
+ author: string;
242
+ source?: string;
243
+ }
244
+
245
+ export type SkillSourceType = 'shortcut' | 'github' | 'local';
246
+
247
+ export interface SkillSource {
248
+ type: SkillSourceType;
249
+ location: string;
250
+ }
251
+
252
+ export interface SkillInstallation {
253
+ agent: AgentType;
254
+ path: string;
255
+ isGlobal: boolean;
256
+ metadata: SkillMetadata;
257
+ contentHash: string;
258
+ }
259
+
260
+ export interface CommandFlags {
261
+ global?: boolean;
262
+ claude?: boolean;
263
+ codex?: boolean;
264
+ gemini?: boolean;
265
+ }
266
+ ```
267
+
268
+ ### 3. Config File (`~/.sun/config.json`)
269
+ ```json
270
+ {
271
+ "defaultAgents": ["claude", "codex", "gemini"],
272
+ "firstRunComplete": true
273
+ }
274
+ ```
275
+
276
+ **Note:** `defaultAgents` is set to ALL detected agents on first run.
277
+
278
+ ### 4. Skill Metadata (in each skill folder)
279
+ ```json
280
+ // skill.json
281
+ {
282
+ "name": "web-search",
283
+ "description": "Searches the web using multiple providers",
284
+ "version": "1.2.0",
285
+ "author": "Sundial Team",
286
+ "source": "github.com/sundial-org/skills/web-search"
287
+ }
288
+ ```
289
+
290
+ ### 5. Implementation Steps
291
+
292
+ #### Step 1: Core Infrastructure
293
+ - [ ] Setup TypeScript project with tsconfig
294
+ - [ ] Create `agents.ts` - agent configuration constants and helper functions
295
+ - [ ] Create `config-manager.ts` - read/write `~/.sun/config.json`
296
+ - [ ] Create `agent-detect.ts` - detect agent folders in cwd and global, returns ALL detected agents on first run
297
+ - [ ] Create `prompts.ts` - interactive agent selection dialog (use `inquirer` or `prompts`)
298
+ - [ ] Create `fuzzy-match.ts` - command suggestion using `fuzzysort` or custom implementation
299
+ - [ ] Create `registry.ts` - skill shortcuts registry
300
+
301
+ #### Step 2: Skill Source & Hashing
302
+ - [ ] Create `skill-source.ts`:
303
+ - `isShortcut(skill: string): boolean` - check registry
304
+ - `isGithubUrl(skill: string): boolean` - check if contains "github.com"
305
+ - `isLocalPath(skill: string): boolean` - check if valid file path
306
+ - `resolveSkillSource(skill: string): SkillSource` → returns source type and location
307
+ - [ ] Create `skill-hash.ts`:
308
+ - `computeContentHash(skillPath: string): Promise<string>` - hash all files in skill folder using crypto module
309
+ - Use SHA-256, hash file contents in sorted order for consistency
310
+
311
+ #### Step 3: Skill Installation & Info
312
+ - [ ] Create `skill-install.ts`:
313
+ - `installFromShortcut(skill: string, agent: AgentType, isGlobal: boolean): Promise<void>`
314
+ - `installFromGithub(repoUrl: string, agent: AgentType, isGlobal: boolean): Promise<void>` (use `degit` or `simple-git`)
315
+ - `installFromLocal(path: string, agent: AgentType, isGlobal: boolean): Promise<void>` (copy folder)
316
+ - Each function copies to `{agentFolder}/skills/{skillName}/` using SUPPORTED_AGENTS constants
317
+ - [ ] Create `skill-info.ts`:
318
+ - `readSkillMetadata(skillPath: string): Promise<SkillMetadata>` - parse skill.json
319
+ - `findSkillInstallations(skillName: string): Promise<SkillInstallation[]>` - find all installations
320
+
321
+ #### Step 4: Commands
322
+ - [ ] `add.ts`:
323
+ - Parse arguments (skills, flags)
324
+ - Check if first run → detect ALL agents → show agent selection dialog with all detected agents selected
325
+ - Determine target agents from flags or config
326
+ - For each skill: resolve source, install to target agents
327
+ - Print confirmation messages
328
+ - [ ] `remove.ts`:
329
+ - Parse arguments (skills, flags)
330
+ - Determine target agents from flags or config
331
+ - Remove from agents
332
+ - Print confirmation
333
+ - [ ] `list.ts`:
334
+ - Scan all agent folders (local + global) using SUPPORTED_AGENTS constants
335
+ - Group by agent, print formatted output
336
+ - Add footer message with `getSupportedAgentsMessage()`
337
+ - [ ] `show.ts`:
338
+ - Find skill in all agent folders
339
+ - Read `skill.json` metadata
340
+ - Compute content hash for each installation
341
+ - Detect duplicates by comparing hashes
342
+ - Print formatted output with author
343
+ - [ ] `config.ts`:
344
+ - Show current config
345
+ - Show `getSupportedAgentsMessage()`
346
+ - Re-run agent selection dialog
347
+ - Save updated config
348
+
349
+ #### Step 5: CLI Entry Point
350
+ - [ ] Create `index.ts`:
351
+ - Use `commander` or `yargs` for argument parsing
352
+ - Register flags dynamically from SUPPORTED_AGENTS constants
353
+ - Command routing
354
+ - Fuzzy matching for unknown commands
355
+ - Error handling
356
+ - Setup as bin in package.json
357
+
358
+ ### 6. Unit Tests
359
+
360
+ #### `agents.test.ts`
361
+ - [ ] Test getAgentByFlag returns correct agent
362
+ - [ ] Test getAgentByFolder returns correct agent
363
+ - [ ] Test getSupportedAgentsMessage format
364
+ - [ ] Test invalid flag/folder returns undefined
365
+
366
+ #### `agent-detect.test.ts`
367
+ - [ ] Test detecting agents in current directory
368
+ - [ ] Test detecting global agents
369
+ - [ ] Test when no agents found
370
+ - [ ] Test multiple agents in same directory
371
+ - [ ] Test first run returns ALL detected agents
372
+
373
+ #### `config-manager.test.ts`
374
+ - [ ] Test creating new config with all detected agents
375
+ - [ ] Test reading existing config
376
+ - [ ] Test updating config
377
+ - [ ] Test config doesn't exist
378
+
379
+ #### `skill-source.test.ts`
380
+ - [ ] Test shortcut detection (tinker)
381
+ - [ ] Test GitHub URL detection
382
+ - [ ] Test local path detection (relative, absolute)
383
+ - [ ] Test invalid inputs
384
+
385
+ #### `skill-hash.test.ts`
386
+ - [ ] Test hashing same content produces same hash
387
+ - [ ] Test hashing different content produces different hash
388
+ - [ ] Test hashing empty folder
389
+ - [ ] Test hashing with subdirectories
390
+
391
+ #### `add.test.ts`
392
+ - [ ] Test adding single skill
393
+ - [ ] Test adding multiple skills
394
+ - [ ] Test adding with --global
395
+ - [ ] Test adding with multiple agent flags (--claude --codex)
396
+ - [ ] Test first-run dialog trigger with all agents selected
397
+ - [ ] Test error handling (skill not found, no agents)
398
+
399
+ #### `remove.test.ts`
400
+ - [ ] Test removing single skill
401
+ - [ ] Test removing multiple skills
402
+ - [ ] Test removing with multiple agent flags
403
+ - [ ] Test removing non-existent skill
404
+
405
+ #### `list.test.ts`
406
+ - [ ] Test listing when skills exist
407
+ - [ ] Test listing when no skills
408
+ - [ ] Test output format includes supported agents message
409
+
410
+ #### `show.test.ts`
411
+ - [ ] Test showing existing skill
412
+ - [ ] Test showing skill with multiple installations
413
+ - [ ] Test showing non-existent skill
414
+ - [ ] Test duplicate detection with different hashes
415
+ - [ ] Test author field display
416
+
417
+ #### `fuzzy-match.test.ts`
418
+ - [ ] Test close matches (typos)
419
+ - [ ] Test no matches
420
+ - [ ] Test exact matches
421
+
422
+ ### 7. Dependencies
423
+ ```json
424
+ {
425
+ "dependencies": {
426
+ "commander": "^11.0.0",
427
+ "chalk": "^5.3.0",
428
+ "inquirer": "^9.2.0",
429
+ "ora": "^7.0.0",
430
+ "degit": "^2.8.4",
431
+ "fs-extra": "^11.1.0",
432
+ "fuzzysort": "^2.0.4"
433
+ },
434
+ "devDependencies": {
435
+ "@types/node": "^20.0.0",
436
+ "@types/inquirer": "^9.0.0",
437
+ "@types/fs-extra": "^11.0.0",
438
+ "typescript": "^5.0.0",
439
+ "vitest": "^1.0.0",
440
+ "tsx": "^4.0.0"
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### 8. Package.json bin configuration
446
+ ```json
447
+ {
448
+ "name": "sundial-cli",
449
+ "version": "0.1.0",
450
+ "bin": {
451
+ "sun": "./dist/index.js"
452
+ },
453
+ "scripts": {
454
+ "build": "tsc",
455
+ "dev": "tsx src/index.ts",
456
+ "test": "vitest"
457
+ }
458
+ }
459
+ ```
460
+
461
+ ### 9. First Release Checklist
462
+ - [ ] All commands implemented
463
+ - [ ] Unit tests passing (>80% coverage)
464
+ - [ ] README.md complete
465
+ - [ ] First-run experience tested with all detected agents selected by default
466
+ - [ ] Fuzzy command matching works
467
+ - [ ] Error messages are helpful
468
+ - [ ] Config persistence works
469
+ - [ ] Multi-agent support tested with multiple flags
470
+ - [ ] Agent configuration constants properly used throughout
471
+ - [ ] Skill shortcuts registry works (tinker)
472
+ - [ ] Content hashing for duplicate detection works
473
+ - [ ] Author field displays in `show` command
474
+ - [ ] `list` and `config` show supported agents message
475
+
476
+ ### 10. Future Enhancements (v2)
477
+ - More skill shortcuts in registry
478
+ - Skill versioning
479
+ - Skill updates (`sun upgrade`)
480
+ - Skill search (`sun search <query>`)
481
+ - Bundles (multiple skills together)
482
+ - MCP server integration
483
+ - System prompt management
484
+ - Dynamic agent registration (add new agents via config)
485
+
486
+ ---
487
+
488
+ ## Key Design Principles
489
+
490
+ 1. **Simple by default** - Most users just do `sun add <skill>` and it works
491
+ 2. **Power user friendly** - Multiple agent flags available when needed
492
+ 3. **Multi-agent aware** - Works across all supported agents seamlessly with `--claude --codex`
493
+ 4. **Helpful errors** - Fuzzy matching, clear messages
494
+ 5. **No surprises** - Always confirm what was installed where
495
+ 6. **Shortcuts for convenience** - `sun add tinker` just works
496
+ 7. **Centralized configuration** - Agent definitions live in constants for easy maintenance
497
+ 8. **Inclusive defaults** - First run selects ALL detected agents to maximize utility
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Sundial CLI
2
+
3
+ Manage skills for your AI coding agents.
4
+
5
+ ```bash
6
+ npm install -g @sundial-ai/cli
7
+ ```
8
+
9
+ ```bash
10
+ sun add tinker # Add from registry
11
+ sun add github.com/user/skill # Add from GitHub
12
+ sun add ./my-skill # Add from local path
13
+
14
+ sun list # See installed skills
15
+ sun remove tinker # Remove a skill
16
+ ```
17
+
18
+ Works with Claude Code, Codex, and Gemini.
19
+
20
+ ## Flags
21
+
22
+ ```bash
23
+ sun add tinker --global # Install globally (~/.claude/)
24
+ sun add tinker --claude --codex # Install to specific agents
25
+ ```
26
+
27
+ ## Links
28
+
29
+ - [Agent Skills Specification](https://agentskills.io/specification)
30
+ - [Issues](https://github.com/sundial-org/skills/issues)
@@ -0,0 +1,13 @@
1
+ import type { CommandFlags } from '../types/index.js';
2
+ export interface AddResult {
3
+ skill: string;
4
+ installedNames: string[];
5
+ agents: string[];
6
+ isGlobal: boolean;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Add skill(s) to agent configuration(s).
11
+ */
12
+ export declare function addCommand(skills: string[], flags: CommandFlags): Promise<void>;
13
+ //# sourceMappingURL=add.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAiEjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDrF"}
@@ -0,0 +1,112 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { getAgentByFlag, SUPPORTED_AGENTS } from '../core/agents.js';
4
+ import { isFirstRun, getDefaultAgents, setDefaultAgents } from '../core/config-manager.js';
5
+ import { detectAllAgents, detectLocalAgents } from '../core/agent-detect.js';
6
+ import { installSkill } from '../core/skill-install.js';
7
+ import { promptAgentSelection } from '../utils/prompts.js';
8
+ /**
9
+ * Determine which agents to install to and whether to install globally.
10
+ *
11
+ * Logic:
12
+ * 1. If --global flag is set, always install globally
13
+ * 2. If agent flags (--claude, --codex, etc.) are set, use those agents
14
+ * 3. If first run, detect agents and prompt for selection
15
+ * 4. Otherwise use saved default agents
16
+ *
17
+ * For local vs global:
18
+ * - If --global: always global
19
+ * - Otherwise: check if any configured agents have local folders
20
+ * - If yes: install locally to those
21
+ * - If no local folders exist for configured agents: install globally
22
+ */
23
+ async function resolveTargetAgents(flags) {
24
+ const forceGlobal = flags.global ?? false;
25
+ // Check if any agent flags were explicitly set
26
+ const explicitAgents = [];
27
+ for (const agent of SUPPORTED_AGENTS) {
28
+ if (flags[agent.flag]) {
29
+ explicitAgents.push(agent.flag);
30
+ }
31
+ }
32
+ let targetAgents;
33
+ // Determine which agents to target
34
+ if (explicitAgents.length > 0) {
35
+ targetAgents = explicitAgents;
36
+ }
37
+ else if (await isFirstRun()) {
38
+ // First run: detect agents and show selection dialog
39
+ const detectedAgents = await detectAllAgents();
40
+ const selectedAgents = await promptAgentSelection(detectedAgents);
41
+ await setDefaultAgents(selectedAgents);
42
+ targetAgents = selectedAgents;
43
+ }
44
+ else {
45
+ // Use saved defaults
46
+ const defaultAgents = await getDefaultAgents();
47
+ if (defaultAgents.length === 0) {
48
+ throw new Error('No default agents configured. Run "sun config" to set up your agents.');
49
+ }
50
+ targetAgents = defaultAgents;
51
+ }
52
+ // Determine if we should install globally or locally
53
+ if (forceGlobal) {
54
+ return { agents: targetAgents, isGlobal: true };
55
+ }
56
+ // Check if any of the target agents have local folders in current directory
57
+ const localAgents = await detectLocalAgents();
58
+ const localAgentFlags = new Set(localAgents.map(a => a.agent.flag));
59
+ const hasLocalFolders = targetAgents.some(agentFlag => localAgentFlags.has(agentFlag));
60
+ // If no local folders exist for any configured agent, install globally
61
+ const isGlobal = !hasLocalFolders;
62
+ return { agents: targetAgents, isGlobal };
63
+ }
64
+ /**
65
+ * Add skill(s) to agent configuration(s).
66
+ */
67
+ export async function addCommand(skills, flags) {
68
+ if (skills.length === 0) {
69
+ console.error(chalk.red('Error: No skills specified. Usage: sun add <skill> [skill2] ...'));
70
+ process.exit(1);
71
+ }
72
+ // Resolve target agents
73
+ const { agents, isGlobal } = await resolveTargetAgents(flags);
74
+ const results = [];
75
+ for (const skill of skills) {
76
+ const spinner = ora(`Adding ${skill}...`).start();
77
+ const result = {
78
+ skill,
79
+ installedNames: [],
80
+ agents: [],
81
+ isGlobal
82
+ };
83
+ try {
84
+ // Install to each target agent
85
+ for (const agentFlag of agents) {
86
+ const { skillNames } = await installSkill(skill, agentFlag, isGlobal);
87
+ result.installedNames = skillNames;
88
+ result.agents.push(getAgentByFlag(agentFlag).name);
89
+ }
90
+ spinner.succeed(`Added ${result.installedNames.join(', ')}`);
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ result.error = message;
95
+ spinner.fail(`Failed to add ${skill}: ${message}`);
96
+ }
97
+ results.push(result);
98
+ }
99
+ // Print summary
100
+ const successful = results.filter(r => !r.error);
101
+ if (successful.length > 0) {
102
+ const allSkills = [...new Set(successful.flatMap(r => r.installedNames))];
103
+ const agentFolders = [...new Set(successful.flatMap(r => r.agents))];
104
+ console.log();
105
+ const location = isGlobal ? '(global)' : '(local)';
106
+ console.log(chalk.green(`Added ${allSkills.join(', ')} to ${agentFolders.join(' and ')} ${chalk.gray(location)}`));
107
+ if (!isGlobal) {
108
+ console.log(chalk.gray('(use --global to install globally)'));
109
+ }
110
+ }
111
+ }
112
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-open agent selection dialog and update config.
3
+ */
4
+ export declare function configCommand(): Promise<void>;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAuCnD"}