@orderful/droid 0.2.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/.changeset/README.md +30 -0
- package/.changeset/config.json +14 -0
- package/.eslintrc.json +20 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +9 -0
- package/.github/workflows/ci.yml +47 -0
- package/.github/workflows/release.yml +45 -0
- package/CHANGELOG.md +11 -0
- package/README.md +153 -0
- package/bun.lock +571 -0
- package/dist/bin/droid.d.ts +3 -0
- package/dist/bin/droid.d.ts.map +1 -0
- package/dist/bin/droid.js +48 -0
- package/dist/bin/droid.js.map +1 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +67 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +42 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +132 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +135 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +17 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +45 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +46 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +133 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/skill-config.d.ts +6 -0
- package/dist/lib/skill-config.d.ts.map +1 -0
- package/dist/lib/skill-config.js +80 -0
- package/dist/lib/skill-config.js.map +1 -0
- package/dist/lib/skills.d.ts +56 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +245 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/types.d.ts +54 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +31 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/version.d.ts +10 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +41 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/skills/comments/SKILL.md +65 -0
- package/dist/skills/comments/SKILL.yaml +18 -0
- package/dist/skills/comments/commands/comments.md +48 -0
- package/package.json +58 -0
- package/src/bin/droid.ts +58 -0
- package/src/commands/config.ts +86 -0
- package/src/commands/install.ts +48 -0
- package/src/commands/setup.ts +149 -0
- package/src/commands/skills.ts +159 -0
- package/src/commands/uninstall.ts +18 -0
- package/src/commands/update.ts +58 -0
- package/src/index.ts +5 -0
- package/src/lib/config.test.ts +99 -0
- package/src/lib/config.ts +154 -0
- package/src/lib/skill-config.ts +93 -0
- package/src/lib/skills.test.ts +138 -0
- package/src/lib/skills.ts +285 -0
- package/src/lib/types.test.ts +65 -0
- package/src/lib/types.ts +68 -0
- package/src/lib/version.test.ts +23 -0
- package/src/lib/version.ts +47 -0
- package/src/skills/comments/SKILL.md +65 -0
- package/src/skills/comments/SKILL.yaml +18 -0
- package/src/skills/comments/commands/comments.md +48 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export var AITool;
|
|
2
|
+
(function (AITool) {
|
|
3
|
+
AITool["ClaudeCode"] = "claude-code";
|
|
4
|
+
AITool["OpenCode"] = "opencode";
|
|
5
|
+
})(AITool || (AITool = {}));
|
|
6
|
+
/**
|
|
7
|
+
* Get the AI's mention tag
|
|
8
|
+
* Always @droid - consistent branding regardless of underlying AI tool
|
|
9
|
+
*/
|
|
10
|
+
export function getAITag() {
|
|
11
|
+
return '@droid';
|
|
12
|
+
}
|
|
13
|
+
// Built-in output preferences (always available)
|
|
14
|
+
export var BuiltInOutput;
|
|
15
|
+
(function (BuiltInOutput) {
|
|
16
|
+
BuiltInOutput["Terminal"] = "terminal";
|
|
17
|
+
BuiltInOutput["Editor"] = "editor";
|
|
18
|
+
})(BuiltInOutput || (BuiltInOutput = {}));
|
|
19
|
+
export var SkillStatus;
|
|
20
|
+
(function (SkillStatus) {
|
|
21
|
+
SkillStatus["Stable"] = "stable";
|
|
22
|
+
SkillStatus["Beta"] = "beta";
|
|
23
|
+
SkillStatus["Alpha"] = "alpha";
|
|
24
|
+
})(SkillStatus || (SkillStatus = {}));
|
|
25
|
+
export var ConfigOptionType;
|
|
26
|
+
(function (ConfigOptionType) {
|
|
27
|
+
ConfigOptionType["String"] = "string";
|
|
28
|
+
ConfigOptionType["Boolean"] = "boolean";
|
|
29
|
+
ConfigOptionType["Select"] = "select";
|
|
30
|
+
})(ConfigOptionType || (ConfigOptionType = {}));
|
|
31
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,MAGX;AAHD,WAAY,MAAM;IAChB,oCAA0B,CAAA;IAC1B,+BAAqB,CAAA;AACvB,CAAC,EAHW,MAAM,KAAN,MAAM,QAGjB;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,iDAAiD;AACjD,MAAM,CAAN,IAAY,aAGX;AAHD,WAAY,aAAa;IACvB,sCAAqB,CAAA;IACrB,kCAAiB,CAAA;AACnB,CAAC,EAHW,aAAa,KAAb,aAAa,QAGxB;AAKD,MAAM,CAAN,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,gCAAiB,CAAA;IACjB,4BAAa,CAAA;IACb,8BAAe,CAAA;AACjB,CAAC,EAJW,WAAW,KAAX,WAAW,QAItB;AAED,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,qCAAiB,CAAA;IACjB,uCAAmB,CAAA;IACnB,qCAAiB,CAAA;AACnB,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the current version from package.json
|
|
3
|
+
*/
|
|
4
|
+
export declare function getVersion(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Check for updates (non-blocking)
|
|
7
|
+
* Shows a message if a new version is available
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkForUpdates(): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/lib/version.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAOnC;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBrD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageJsonPath = join(__dirname, '../../package.json');
|
|
8
|
+
/**
|
|
9
|
+
* Get the current version from package.json
|
|
10
|
+
*/
|
|
11
|
+
export function getVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
14
|
+
return pkg.version;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '0.0.0';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check for updates (non-blocking)
|
|
22
|
+
* Shows a message if a new version is available
|
|
23
|
+
*/
|
|
24
|
+
export async function checkForUpdates() {
|
|
25
|
+
try {
|
|
26
|
+
const currentVersion = getVersion();
|
|
27
|
+
// Use npm view to check latest version
|
|
28
|
+
const latestVersion = execSync('npm view @orderful/droid version 2>/dev/null', {
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
timeout: 3000,
|
|
31
|
+
}).trim();
|
|
32
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
33
|
+
console.log(chalk.yellow(`\nā ļø A new version of droid is available (${currentVersion} ā ${latestVersion})`));
|
|
34
|
+
console.log(chalk.yellow(` Run ${chalk.bold('droid update')} to upgrade\n`));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Silently fail - update check is non-critical
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/lib/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QAC/D,OAAO,GAAG,CAAC,OAAiB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,UAAU,EAAE,CAAC;QAEpC,uCAAuC;QACvC,MAAM,aAAa,GAAG,QAAQ,CAAC,8CAA8C,EAAE;YAC7E,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,IAAI,aAAa,KAAK,cAAc,EAAE,CAAC;YACtD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,8CAA8C,cAAc,MAAM,aAAa,GAAG,CACnF,CACF,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Comments Skill
|
|
2
|
+
|
|
3
|
+
Enable inline conversation in any file using `> @droid` and `> @{user}` markers.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
Leave comments for the AI using `> @droid`:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
> @droid What do you think of this approach?
|
|
11
|
+
|
|
12
|
+
> @droid Can you add error handling here?
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The AI will respond with `> @{user_mention}` (configured in droid setup, e.g., `@fry`):
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
> @fry I think this approach is solid. One consideration...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Tag Convention
|
|
22
|
+
|
|
23
|
+
| Who | Tag |
|
|
24
|
+
|-----|-----|
|
|
25
|
+
| AI | `@droid` (+ any configured aliases) |
|
|
26
|
+
| User | Configured (e.g., `@fry`) |
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
- `/comments check` - Scan for AI markers and address each one
|
|
31
|
+
- `/comments check {path}` - Scope to specific file or directory
|
|
32
|
+
- `/comments cleanup` - Find resolved comment threads and remove them
|
|
33
|
+
|
|
34
|
+
## Behavior
|
|
35
|
+
|
|
36
|
+
### On `/comments check`:
|
|
37
|
+
1. Search for `> @droid` (and any configured `ai_mentions`) in the specified scope
|
|
38
|
+
2. If there's a `git diff`, check those files first for relevant context
|
|
39
|
+
3. For each comment, determine intent:
|
|
40
|
+
- **Action request** ("do X", "add Y", "fix Z") ā Execute the action, remove the comment
|
|
41
|
+
- **Question** ("what do you think", "should we") ā Respond with `> @{user_mention}`, keep original comment
|
|
42
|
+
|
|
43
|
+
### On `/comments cleanup`:
|
|
44
|
+
1. Find AI tag and `> @{user_mention}` pairs where conversation appears resolved
|
|
45
|
+
2. Remove both markers
|
|
46
|
+
3. Output a summary of what was discussed/decided
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
Configured via `~/.droid/skills/comments/overrides.yaml` or inherits from global `~/.droid/config.yaml`:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
# Override the user mention (default: from global config)
|
|
54
|
+
user_mention: "@fry"
|
|
55
|
+
|
|
56
|
+
# Additional AI mentions to recognize (e.g., if you're used to @claude)
|
|
57
|
+
ai_mentions: "@claude"
|
|
58
|
+
|
|
59
|
+
# Keep comments after addressing them (default: false)
|
|
60
|
+
preserve_comments: false
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## File Type Support
|
|
64
|
+
|
|
65
|
+
Works in any text file. Respects `.gitignore` and skips common build directories (node_modules, dist, .git, etc.).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: comments
|
|
2
|
+
description: Inline conversation in any file via @droid/@user markers
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: beta
|
|
5
|
+
dependencies: []
|
|
6
|
+
provides_output: false
|
|
7
|
+
config_schema:
|
|
8
|
+
user_mention:
|
|
9
|
+
type: string
|
|
10
|
+
description: Override the global user mention for this skill
|
|
11
|
+
ai_mentions:
|
|
12
|
+
type: string
|
|
13
|
+
description: Additional AI mentions to recognize (comma-separated, e.g., "@claude,@ai")
|
|
14
|
+
default: ""
|
|
15
|
+
preserve_comments:
|
|
16
|
+
type: boolean
|
|
17
|
+
description: Keep original comments after addressing (vs removing them)
|
|
18
|
+
default: false
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# /comments - Check and respond to inline comments
|
|
2
|
+
|
|
3
|
+
Check for `> @droid` comments (and configured aliases like `@claude`) in files and address them.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/comments # Check all files in cwd
|
|
9
|
+
/comments check # Same as above
|
|
10
|
+
/comments check {path} # Check specific file or directory
|
|
11
|
+
/comments cleanup # Remove resolved comment threads
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Arguments
|
|
15
|
+
|
|
16
|
+
$ACTION - The action to perform: check (default) or cleanup
|
|
17
|
+
$PATH - Optional path to scope the search
|
|
18
|
+
|
|
19
|
+
## Behavior
|
|
20
|
+
|
|
21
|
+
When you find a `> @droid` comment (or configured alias):
|
|
22
|
+
|
|
23
|
+
1. **Read the context** - Understand what the comment is asking by reading surrounding code/text
|
|
24
|
+
2. **Determine intent**:
|
|
25
|
+
- If it's an **action request** (e.g., "add error handling", "refactor this"):
|
|
26
|
+
- Execute the requested action
|
|
27
|
+
- Remove the comment (unless preserve_comments is true)
|
|
28
|
+
- If it's a **question** (e.g., "what do you think?", "is this approach good?"):
|
|
29
|
+
- Respond with `> @{user_mention}` on a new line below
|
|
30
|
+
- Keep the original comment for context
|
|
31
|
+
3. **Check git diff first** - If there's uncommitted changes, prioritize checking those files
|
|
32
|
+
|
|
33
|
+
## Response Format
|
|
34
|
+
|
|
35
|
+
When responding to questions, use the configured user tag:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
> @droid Should we use async/await here?
|
|
39
|
+
|
|
40
|
+
> @fry Yes, async/await would be cleaner here because...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Check `~/.droid/skills/comments/overrides.yaml` for:
|
|
46
|
+
- `user_mention` - Your @mention for responses
|
|
47
|
+
- `ai_mentions` - Additional mentions to recognize (e.g., `@claude`)
|
|
48
|
+
- `preserve_comments` - Keep or remove addressed comments
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orderful/droid",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"droid": "./dist/bin/droid.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp -r src/skills dist/",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "bun dist/bin/droid.js",
|
|
14
|
+
"test": "bun test src/",
|
|
15
|
+
"test:watch": "bun test src/ --watch",
|
|
16
|
+
"lint": "eslint src --ext .ts",
|
|
17
|
+
"format": "prettier --write 'src/**/*.ts'",
|
|
18
|
+
"changeset": "changeset",
|
|
19
|
+
"version": "changeset version",
|
|
20
|
+
"release": "bun run build && changeset publish",
|
|
21
|
+
"prepublishOnly": "bun run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ai",
|
|
25
|
+
"claude",
|
|
26
|
+
"cli",
|
|
27
|
+
"developer-tools",
|
|
28
|
+
"workflow"
|
|
29
|
+
],
|
|
30
|
+
"author": "Orderful Engineering",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/orderful/droid.git"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"inquirer": "^9.2.15",
|
|
43
|
+
"ora": "^8.0.1",
|
|
44
|
+
"yaml": "^2.4.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@changesets/changelog-github": "^0.5.2",
|
|
48
|
+
"@changesets/cli": "^2.29.8",
|
|
49
|
+
"@types/bun": "latest",
|
|
50
|
+
"@types/inquirer": "^9.0.7",
|
|
51
|
+
"@types/node": "^20.11.24",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.49.0",
|
|
54
|
+
"eslint": "^8.57.0",
|
|
55
|
+
"prettier": "^3.2.5",
|
|
56
|
+
"typescript": "^5.4.2"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/bin/droid.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import { setupCommand } from '../commands/setup.js';
|
|
5
|
+
import { configCommand } from '../commands/config.js';
|
|
6
|
+
import { skillsCommand } from '../commands/skills.js';
|
|
7
|
+
import { installCommand } from '../commands/install.js';
|
|
8
|
+
import { uninstallCommand } from '../commands/uninstall.js';
|
|
9
|
+
import { updateCommand } from '../commands/update.js';
|
|
10
|
+
import { getVersion, checkForUpdates } from '../lib/version.js';
|
|
11
|
+
|
|
12
|
+
const version = getVersion();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name('droid')
|
|
16
|
+
.description('AI workflow toolkit - teaching your droid new tricks')
|
|
17
|
+
.version(version);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('setup')
|
|
21
|
+
.description('Interactive wizard for global configuration')
|
|
22
|
+
.action(setupCommand);
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.command('config')
|
|
26
|
+
.description('View or edit configuration')
|
|
27
|
+
.option('-e, --edit', 'Open config in editor')
|
|
28
|
+
.option('-g, --get <key>', 'Get a specific config value')
|
|
29
|
+
.option('-s, --set <key=value>', 'Set a config value')
|
|
30
|
+
.action(configCommand);
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('skills')
|
|
34
|
+
.description('Browse and manage available skills')
|
|
35
|
+
.action(skillsCommand);
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('install <skill>')
|
|
39
|
+
.description('Install a skill and run its setup wizard')
|
|
40
|
+
.action(installCommand);
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command('uninstall <skill>')
|
|
44
|
+
.description('Uninstall a skill')
|
|
45
|
+
.action(uninstallCommand);
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('update')
|
|
49
|
+
.description('Update droid and installed skills')
|
|
50
|
+
.option('--skills', 'Only update skills')
|
|
51
|
+
.option('--cli', 'Only update the CLI')
|
|
52
|
+
.argument('[skill]', 'Update a specific skill')
|
|
53
|
+
.action(updateCommand);
|
|
54
|
+
|
|
55
|
+
// Check for updates on any command (non-blocking)
|
|
56
|
+
checkForUpdates().catch(() => {});
|
|
57
|
+
|
|
58
|
+
program.parse();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import {
|
|
4
|
+
loadConfig,
|
|
5
|
+
getConfigPath,
|
|
6
|
+
getConfigValue,
|
|
7
|
+
setConfigValue,
|
|
8
|
+
configExists,
|
|
9
|
+
} from '../lib/config.js';
|
|
10
|
+
|
|
11
|
+
interface ConfigOptions {
|
|
12
|
+
edit?: boolean;
|
|
13
|
+
get?: string;
|
|
14
|
+
set?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function configCommand(options: ConfigOptions): Promise<void> {
|
|
18
|
+
// Handle --edit flag
|
|
19
|
+
if (options.edit) {
|
|
20
|
+
const configPath = getConfigPath();
|
|
21
|
+
const editor = process.env.EDITOR || 'vim';
|
|
22
|
+
|
|
23
|
+
if (!configExists()) {
|
|
24
|
+
console.log(chalk.yellow('No config file exists yet. Run `droid setup` first.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(chalk.gray(`Opening ${configPath} in ${editor}...`));
|
|
29
|
+
try {
|
|
30
|
+
execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
|
|
31
|
+
} catch {
|
|
32
|
+
console.error(chalk.red('Failed to open editor'));
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle --get flag
|
|
38
|
+
if (options.get) {
|
|
39
|
+
const value = getConfigValue(options.get);
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
console.log(chalk.yellow(`Config key '${options.get}' not found`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof value === 'object') {
|
|
46
|
+
console.log(JSON.stringify(value, null, 2));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(value);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle --set flag
|
|
54
|
+
if (options.set) {
|
|
55
|
+
const match = options.set.match(/^([^=]+)=(.*)$/);
|
|
56
|
+
if (!match) {
|
|
57
|
+
console.error(chalk.red('Invalid format. Use: --set key=value'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const [, key, rawValue] = match;
|
|
62
|
+
|
|
63
|
+
// Try to parse as JSON, otherwise use as string
|
|
64
|
+
let value: unknown;
|
|
65
|
+
try {
|
|
66
|
+
value = JSON.parse(rawValue);
|
|
67
|
+
} catch {
|
|
68
|
+
value = rawValue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setConfigValue(key, value);
|
|
72
|
+
console.log(chalk.green(`ā Set ${key} = ${JSON.stringify(value)}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default: show full config
|
|
77
|
+
if (!configExists()) {
|
|
78
|
+
console.log(chalk.yellow('No config file exists yet. Run `droid setup` first.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
console.log(chalk.bold('\nš Droid Config\n'));
|
|
84
|
+
console.log(chalk.gray(`Path: ${getConfigPath()}\n`));
|
|
85
|
+
console.log(JSON.stringify(config, null, 2));
|
|
86
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { installSkill, isSkillInstalled, getBundledSkills, loadSkillManifest, getBundledSkillsDir } from '../lib/skills.js';
|
|
4
|
+
import { promptForSkillConfig } from '../lib/skill-config.js';
|
|
5
|
+
|
|
6
|
+
export async function installCommand(skillName: string): Promise<void> {
|
|
7
|
+
// Check if skill exists
|
|
8
|
+
const skills = getBundledSkills();
|
|
9
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
10
|
+
|
|
11
|
+
if (!skill) {
|
|
12
|
+
console.error(chalk.red(`\nā Skill '${skillName}' not found`));
|
|
13
|
+
console.log(chalk.gray('\nAvailable skills:'));
|
|
14
|
+
for (const s of skills) {
|
|
15
|
+
console.log(chalk.gray(` - ${s.name}`));
|
|
16
|
+
}
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Check if already installed
|
|
21
|
+
if (isSkillInstalled(skillName)) {
|
|
22
|
+
console.log(chalk.yellow(`\nā Skill '${skillName}' is already installed`));
|
|
23
|
+
console.log(chalk.gray('Use `droid update` to update it, or uninstall first.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(chalk.bold(`\nš¤ Installing ${skillName}...\n`));
|
|
28
|
+
|
|
29
|
+
const result = installSkill(skillName);
|
|
30
|
+
|
|
31
|
+
if (result.success) {
|
|
32
|
+
console.log(chalk.green(`ā ${result.message}`));
|
|
33
|
+
|
|
34
|
+
// Check if skill has configurable options
|
|
35
|
+
const manifest = loadSkillManifest(join(getBundledSkillsDir(), skillName));
|
|
36
|
+
if (manifest?.config_schema && Object.keys(manifest.config_schema).length > 0) {
|
|
37
|
+
await promptForSkillConfig(skillName, manifest.config_schema, true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Show next steps
|
|
41
|
+
console.log(chalk.gray('\nNext steps:'));
|
|
42
|
+
console.log(chalk.gray(' - Run your AI tool to start using the skill'));
|
|
43
|
+
console.log(chalk.gray(` - Reconfigure anytime with \`droid skills\` ā Configure`));
|
|
44
|
+
} else {
|
|
45
|
+
console.error(chalk.red(`ā ${result.message}`));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { loadConfig, saveConfig, configExists } from '../lib/config.js';
|
|
5
|
+
import { getBundledSkills } from '../lib/skills.js';
|
|
6
|
+
import { AITool, BuiltInOutput, type DroidConfig, type OutputPreference } from '../lib/types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect which AI tool is installed
|
|
10
|
+
*/
|
|
11
|
+
function detectAITool(): AITool | null {
|
|
12
|
+
try {
|
|
13
|
+
execSync('claude --version', { stdio: 'ignore' });
|
|
14
|
+
return AITool.ClaudeCode;
|
|
15
|
+
} catch {
|
|
16
|
+
// Claude Code not found
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
execSync('opencode --version', { stdio: 'ignore' });
|
|
21
|
+
return AITool.OpenCode;
|
|
22
|
+
} catch {
|
|
23
|
+
// OpenCode not found
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Try to get git username
|
|
31
|
+
*/
|
|
32
|
+
function detectGitUsername(): string {
|
|
33
|
+
try {
|
|
34
|
+
return execSync('git config user.name', { encoding: 'utf-8' }).trim();
|
|
35
|
+
} catch {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get available output options (built-in + skills that provide output)
|
|
42
|
+
*/
|
|
43
|
+
function getOutputOptions(): Array<{ name: string; value: OutputPreference }> {
|
|
44
|
+
const options: Array<{ name: string; value: OutputPreference }> = [
|
|
45
|
+
{ name: 'Terminal (display in CLI)', value: BuiltInOutput.Terminal },
|
|
46
|
+
{ name: 'Editor ($EDITOR)', value: BuiltInOutput.Editor },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Add skills that provide output targets
|
|
50
|
+
const skills = getBundledSkills();
|
|
51
|
+
for (const skill of skills) {
|
|
52
|
+
if (skill.provides_output) {
|
|
53
|
+
options.push({
|
|
54
|
+
name: `${skill.name} (${skill.description})`,
|
|
55
|
+
value: skill.name,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function setupCommand(): Promise<void> {
|
|
64
|
+
console.log(chalk.bold('\nš¤ Droid Setup\n'));
|
|
65
|
+
|
|
66
|
+
const existingConfig = configExists();
|
|
67
|
+
if (existingConfig) {
|
|
68
|
+
const { overwrite } = await inquirer.prompt<{ overwrite: boolean }>([
|
|
69
|
+
{
|
|
70
|
+
type: 'confirm',
|
|
71
|
+
name: 'overwrite',
|
|
72
|
+
message: 'Config already exists. Overwrite?',
|
|
73
|
+
default: false,
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
if (!overwrite) {
|
|
78
|
+
console.log(chalk.gray('Setup cancelled.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Detect AI tool
|
|
84
|
+
const detectedTool = detectAITool();
|
|
85
|
+
if (detectedTool) {
|
|
86
|
+
console.log(chalk.green(`ā Detected ${detectedTool}\n`));
|
|
87
|
+
} else {
|
|
88
|
+
console.log(chalk.yellow('ā No AI tool detected (Claude Code or OpenCode)\n'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Detect git username
|
|
92
|
+
const detectedGitUsername = detectGitUsername();
|
|
93
|
+
|
|
94
|
+
// Get dynamic output options
|
|
95
|
+
const outputOptions = getOutputOptions();
|
|
96
|
+
|
|
97
|
+
const answers = await inquirer.prompt<{
|
|
98
|
+
ai_tool: AITool;
|
|
99
|
+
user_mention: string;
|
|
100
|
+
output_preference: OutputPreference;
|
|
101
|
+
git_username: string;
|
|
102
|
+
}>([
|
|
103
|
+
{
|
|
104
|
+
type: 'list',
|
|
105
|
+
name: 'ai_tool',
|
|
106
|
+
message: 'Which AI tool are you using?',
|
|
107
|
+
choices: [
|
|
108
|
+
{ name: 'Claude Code', value: AITool.ClaudeCode },
|
|
109
|
+
{ name: 'OpenCode', value: AITool.OpenCode },
|
|
110
|
+
],
|
|
111
|
+
default: detectedTool || AITool.ClaudeCode,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'user_mention',
|
|
116
|
+
message: 'What @mention should be used for you?',
|
|
117
|
+
default: '@user',
|
|
118
|
+
validate: (input: string) => {
|
|
119
|
+
if (!input.startsWith('@')) {
|
|
120
|
+
return 'Mention should start with @';
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: 'list',
|
|
127
|
+
name: 'output_preference',
|
|
128
|
+
message: 'Default output preference for skill results?',
|
|
129
|
+
choices: outputOptions,
|
|
130
|
+
default: BuiltInOutput.Terminal,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'input',
|
|
134
|
+
name: 'git_username',
|
|
135
|
+
message: 'Git username for attribution?',
|
|
136
|
+
default: detectedGitUsername || '',
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const config: DroidConfig = {
|
|
141
|
+
...loadConfig(),
|
|
142
|
+
...answers,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
saveConfig(config);
|
|
146
|
+
|
|
147
|
+
console.log(chalk.green('\nā Config saved to ~/.droid/config.yaml'));
|
|
148
|
+
console.log(chalk.gray('\nRun `droid skills` to browse and install skills.'));
|
|
149
|
+
}
|