@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.
Files changed (86) hide show
  1. package/.changeset/README.md +30 -0
  2. package/.changeset/config.json +14 -0
  3. package/.eslintrc.json +20 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +9 -0
  5. package/.github/workflows/ci.yml +47 -0
  6. package/.github/workflows/release.yml +45 -0
  7. package/CHANGELOG.md +11 -0
  8. package/README.md +153 -0
  9. package/bun.lock +571 -0
  10. package/dist/bin/droid.d.ts +3 -0
  11. package/dist/bin/droid.d.ts.map +1 -0
  12. package/dist/bin/droid.js +48 -0
  13. package/dist/bin/droid.js.map +1 -0
  14. package/dist/commands/config.d.ts +8 -0
  15. package/dist/commands/config.d.ts.map +1 -0
  16. package/dist/commands/config.js +67 -0
  17. package/dist/commands/config.js.map +1 -0
  18. package/dist/commands/install.d.ts +2 -0
  19. package/dist/commands/install.d.ts.map +1 -0
  20. package/dist/commands/install.js +42 -0
  21. package/dist/commands/install.js.map +1 -0
  22. package/dist/commands/setup.d.ts +2 -0
  23. package/dist/commands/setup.d.ts.map +1 -0
  24. package/dist/commands/setup.js +132 -0
  25. package/dist/commands/setup.js.map +1 -0
  26. package/dist/commands/skills.d.ts +2 -0
  27. package/dist/commands/skills.d.ts.map +1 -0
  28. package/dist/commands/skills.js +135 -0
  29. package/dist/commands/skills.js.map +1 -0
  30. package/dist/commands/uninstall.d.ts +2 -0
  31. package/dist/commands/uninstall.d.ts.map +1 -0
  32. package/dist/commands/uninstall.js +17 -0
  33. package/dist/commands/uninstall.js.map +1 -0
  34. package/dist/commands/update.d.ts +7 -0
  35. package/dist/commands/update.d.ts.map +1 -0
  36. package/dist/commands/update.js +45 -0
  37. package/dist/commands/update.js.map +1 -0
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +6 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/config.d.ts +46 -0
  43. package/dist/lib/config.d.ts.map +1 -0
  44. package/dist/lib/config.js +133 -0
  45. package/dist/lib/config.js.map +1 -0
  46. package/dist/lib/skill-config.d.ts +6 -0
  47. package/dist/lib/skill-config.d.ts.map +1 -0
  48. package/dist/lib/skill-config.js +80 -0
  49. package/dist/lib/skill-config.js.map +1 -0
  50. package/dist/lib/skills.d.ts +56 -0
  51. package/dist/lib/skills.d.ts.map +1 -0
  52. package/dist/lib/skills.js +245 -0
  53. package/dist/lib/skills.js.map +1 -0
  54. package/dist/lib/types.d.ts +54 -0
  55. package/dist/lib/types.d.ts.map +1 -0
  56. package/dist/lib/types.js +31 -0
  57. package/dist/lib/types.js.map +1 -0
  58. package/dist/lib/version.d.ts +10 -0
  59. package/dist/lib/version.d.ts.map +1 -0
  60. package/dist/lib/version.js +41 -0
  61. package/dist/lib/version.js.map +1 -0
  62. package/dist/skills/comments/SKILL.md +65 -0
  63. package/dist/skills/comments/SKILL.yaml +18 -0
  64. package/dist/skills/comments/commands/comments.md +48 -0
  65. package/package.json +58 -0
  66. package/src/bin/droid.ts +58 -0
  67. package/src/commands/config.ts +86 -0
  68. package/src/commands/install.ts +48 -0
  69. package/src/commands/setup.ts +149 -0
  70. package/src/commands/skills.ts +159 -0
  71. package/src/commands/uninstall.ts +18 -0
  72. package/src/commands/update.ts +58 -0
  73. package/src/index.ts +5 -0
  74. package/src/lib/config.test.ts +99 -0
  75. package/src/lib/config.ts +154 -0
  76. package/src/lib/skill-config.ts +93 -0
  77. package/src/lib/skills.test.ts +138 -0
  78. package/src/lib/skills.ts +285 -0
  79. package/src/lib/types.test.ts +65 -0
  80. package/src/lib/types.ts +68 -0
  81. package/src/lib/version.test.ts +23 -0
  82. package/src/lib/version.ts +47 -0
  83. package/src/skills/comments/SKILL.md +65 -0
  84. package/src/skills/comments/SKILL.yaml +18 -0
  85. package/src/skills/comments/commands/comments.md +48 -0
  86. 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
+ }
@@ -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
+ }