@orderful/droid 0.48.0 → 0.51.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/CHANGELOG.md +34 -0
- package/bun.lock +137 -3
- package/dist/bin/droid.js +355 -90
- package/dist/commands/pack.d.ts +5 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/lib/pack.d.ts +31 -0
- package/dist/lib/pack.d.ts.map +1 -0
- package/dist/lib/types.d.ts +17 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/.claude-plugin/plugin.json +1 -1
- package/dist/tools/brain/TOOL.yaml +3 -1
- package/dist/tools/brain/skills/brain/SKILL.md +4 -0
- package/dist/tools/brain/skills/brain/references/workflows.md +21 -7
- package/dist/tools/coach/TOOL.yaml +4 -0
- package/dist/tools/code-review/.claude-plugin/plugin.json +3 -2
- package/dist/tools/code-review/TOOL.yaml +4 -1
- package/dist/tools/code-review/agents/codex-context-researcher.md +99 -0
- package/dist/tools/code-review/skills/code-review/SKILL.md +20 -1
- package/dist/tools/codex/.claude-plugin/plugin.json +1 -1
- package/dist/tools/codex/TOOL.yaml +3 -1
- package/dist/tools/codex/skills/codex/SKILL.md +5 -1
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts +61 -0
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.d.ts.map +1 -0
- package/dist/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
- package/dist/tools/comments/TOOL.yaml +2 -0
- package/dist/tools/droid/.claude-plugin/plugin.json +1 -1
- package/dist/tools/droid/TOOL.yaml +3 -1
- package/dist/tools/droid/skills/droid/SKILL.md +48 -2
- package/dist/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
- package/dist/tools/edi-schema/TOOL.yaml +2 -0
- package/dist/tools/excalidraw/TOOL.yaml +2 -0
- package/dist/tools/meeting/TOOL.yaml +2 -0
- package/dist/tools/pii/TOOL.yaml +2 -0
- package/dist/tools/plan/.claude-plugin/plugin.json +1 -1
- package/dist/tools/plan/TOOL.yaml +5 -1
- package/dist/tools/plan/commands/plan.md +3 -2
- package/dist/tools/plan/skills/plan/SKILL.md +31 -10
- package/dist/tools/plan/skills/plan/references/workflows.md +44 -14
- package/dist/tools/project/.claude-plugin/plugin.json +1 -1
- package/dist/tools/project/TOOL.yaml +7 -1
- package/dist/tools/project/skills/project/SKILL.md +32 -1
- package/dist/tools/project/skills/project/references/loading.md +1 -0
- package/dist/tools/project/skills/project/references/pulling.md +57 -0
- package/dist/tools/project/skills/project/references/pushing.md +79 -0
- package/dist/tools/release/TOOL.yaml +2 -0
- package/dist/tools/share/TOOL.yaml +2 -0
- package/dist/tools/status-update/TOOL.yaml +4 -0
- package/dist/tools/tech-design/TOOL.yaml +2 -0
- package/dist/tools/wrapup/TOOL.yaml +2 -0
- package/package.json +3 -1
- package/scripts/build.ts +3 -2
- package/src/bin/droid.ts +9 -0
- package/src/commands/pack.ts +77 -0
- package/src/lib/pack.test.ts +85 -0
- package/src/lib/pack.ts +293 -0
- package/src/lib/types.ts +19 -0
- package/src/tools/brain/.claude-plugin/plugin.json +1 -1
- package/src/tools/brain/TOOL.yaml +3 -1
- package/src/tools/brain/skills/brain/SKILL.md +4 -0
- package/src/tools/brain/skills/brain/references/workflows.md +21 -7
- package/src/tools/coach/TOOL.yaml +4 -0
- package/src/tools/code-review/.claude-plugin/plugin.json +3 -2
- package/src/tools/code-review/TOOL.yaml +4 -1
- package/src/tools/code-review/agents/codex-context-researcher.md +99 -0
- package/src/tools/code-review/skills/code-review/SKILL.md +20 -1
- package/src/tools/codex/.claude-plugin/plugin.json +1 -1
- package/src/tools/codex/TOOL.yaml +3 -1
- package/src/tools/codex/skills/codex/SKILL.md +5 -1
- package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.test.ts +331 -0
- package/src/tools/codex/skills/codex/scripts/normalize-frontmatter.ts +402 -0
- package/src/tools/comments/TOOL.yaml +2 -0
- package/src/tools/droid/.claude-plugin/plugin.json +1 -1
- package/src/tools/droid/TOOL.yaml +3 -1
- package/src/tools/droid/skills/droid/SKILL.md +48 -2
- package/src/tools/droid/skills/droid/references/new-tool-workflow.md +234 -0
- package/src/tools/edi-schema/TOOL.yaml +2 -0
- package/src/tools/excalidraw/TOOL.yaml +2 -0
- package/src/tools/meeting/TOOL.yaml +2 -0
- package/src/tools/pii/TOOL.yaml +2 -0
- package/src/tools/plan/.claude-plugin/plugin.json +1 -1
- package/src/tools/plan/TOOL.yaml +5 -1
- package/src/tools/plan/commands/plan.md +3 -2
- package/src/tools/plan/skills/plan/SKILL.md +31 -10
- package/src/tools/plan/skills/plan/references/workflows.md +44 -14
- package/src/tools/project/.claude-plugin/plugin.json +1 -1
- package/src/tools/project/TOOL.yaml +7 -1
- package/src/tools/project/skills/project/SKILL.md +32 -1
- package/src/tools/project/skills/project/references/loading.md +1 -0
- package/src/tools/project/skills/project/references/pulling.md +57 -0
- package/src/tools/project/skills/project/references/pushing.md +79 -0
- package/src/tools/release/TOOL.yaml +2 -0
- package/src/tools/share/TOOL.yaml +2 -0
- package/src/tools/status-update/TOOL.yaml +4 -0
- package/src/tools/tech-design/TOOL.yaml +2 -0
- package/src/tools/wrapup/TOOL.yaml +2 -0
- package/dist/tools/codex/skills/codex/scripts/git-scripts.test.ts +0 -364
- package/dist/tools/pii/skills/pii/scripts/presidio.test.ts +0 -444
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Pushing to Codex
|
|
2
|
+
|
|
3
|
+
**Trigger:** `/project push [codex:{name}]`
|
|
4
|
+
|
|
5
|
+
Push team-relevant content from a local project back to the shared codex.
|
|
6
|
+
|
|
7
|
+
## What Gets Shared
|
|
8
|
+
|
|
9
|
+
Not everything in a project is codex-worthy. Extract only team-relevant content:
|
|
10
|
+
|
|
11
|
+
| Share | Don't Share |
|
|
12
|
+
|-------|-------------|
|
|
13
|
+
| Architecture decisions with rationale | Personal work notes |
|
|
14
|
+
| New patterns discovered during implementation | In-progress tasks |
|
|
15
|
+
| Updated technical constraints | Session logs or worklogs |
|
|
16
|
+
| Status changes (shipped, blocked, pivoted) | Brain docs or research drafts |
|
|
17
|
+
| Key file paths or API changes | Local configuration |
|
|
18
|
+
|
|
19
|
+
## Procedure
|
|
20
|
+
|
|
21
|
+
1. **Resolve codex project name:**
|
|
22
|
+
- If `codex:{name}` provided → use it, save to PROJECT.md frontmatter as `codex_project: {name}`
|
|
23
|
+
- If not provided → read `codex_project` from PROJECT.md frontmatter
|
|
24
|
+
- If neither → ask user which codex project to update, save to frontmatter
|
|
25
|
+
- Codex project must already exist. If it doesn't, suggest `/codex new project {name}` first.
|
|
26
|
+
|
|
27
|
+
2. **Identify the local project:**
|
|
28
|
+
- If in a loaded project session → use that project
|
|
29
|
+
- If no project loaded → search and load first
|
|
30
|
+
|
|
31
|
+
3. **Read PROJECT.md** and companion docs (DECISIONS.md, WORKLOG.md if they exist)
|
|
32
|
+
|
|
33
|
+
4. **Load codex entry:**
|
|
34
|
+
- Run `droid config --get tools.codex` and get `codex_repo` from the JSON output
|
|
35
|
+
- Sync: `git -C {codex_repo} pull --rebase --quiet`
|
|
36
|
+
- Read existing codex project docs
|
|
37
|
+
|
|
38
|
+
5. **Extract and categorise shareable content:**
|
|
39
|
+
- **Decisions:** New entries from DECISIONS.md or inline decisions in PROJECT.md
|
|
40
|
+
- **Architecture:** Changes to technical approach, new patterns, updated diagrams
|
|
41
|
+
- **Status:** Phase changes, milestones hit, scope changes
|
|
42
|
+
- **Constraints:** Newly discovered technical constraints or gotchas
|
|
43
|
+
|
|
44
|
+
6. **Propose codex updates** (never auto-apply):
|
|
45
|
+
- For each item, show:
|
|
46
|
+
- What will be added/changed in codex
|
|
47
|
+
- Which codex file it targets (DECISIONS.md, TECH-DESIGN.md, PRD.md)
|
|
48
|
+
- A diff preview
|
|
49
|
+
- Let user accept, reject, or edit each item
|
|
50
|
+
|
|
51
|
+
7. **After approval:**
|
|
52
|
+
- Use codex git scripts for the write operation:
|
|
53
|
+
```bash
|
|
54
|
+
# Start write
|
|
55
|
+
droid config --get tools.codex | droid exec codex git-start-write --config - \
|
|
56
|
+
--branch codex/sync-{project-name}
|
|
57
|
+
|
|
58
|
+
# Make changes to codex files
|
|
59
|
+
|
|
60
|
+
# Normalize frontmatter
|
|
61
|
+
droid config --get tools.codex | droid exec codex normalize-frontmatter --config -
|
|
62
|
+
|
|
63
|
+
# Finish write (commit + PR)
|
|
64
|
+
droid config --get tools.codex | droid exec codex git-finish-write --config - \
|
|
65
|
+
--message "sync({project}): {summary}" \
|
|
66
|
+
--pr-title "sync({project}): Update from local project" \
|
|
67
|
+
--pr-body "{description of changes}"
|
|
68
|
+
```
|
|
69
|
+
- Update local PROJECT.md sync marker:
|
|
70
|
+
```
|
|
71
|
+
**Last pushed:** {date} to codex:{name}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Key Principles
|
|
75
|
+
|
|
76
|
+
- **Curated, not automated** — user explicitly reviews what gets shared
|
|
77
|
+
- **PR-based** — codex changes go through PR review, not direct commits
|
|
78
|
+
- **Additive** — adds to codex content, doesn't overwrite others' contributions
|
|
79
|
+
- **Idempotent** — running `/project push` twice proposes only new/changed content
|
|
@@ -2,6 +2,10 @@ name: status-update
|
|
|
2
2
|
description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
|
|
3
3
|
version: 0.2.2
|
|
4
4
|
status: beta
|
|
5
|
+
audience:
|
|
6
|
+
- engineering
|
|
7
|
+
- product
|
|
8
|
+
- design
|
|
5
9
|
|
|
6
10
|
includes:
|
|
7
11
|
skills:
|
|
@@ -2,6 +2,8 @@ name: tech-design
|
|
|
2
2
|
description: "Technical design authoring tool for engineers. Create structured tech design docs with /tech-design start, iterate in brain, publish to codex. Three-document approach: research doc (codebase discoveries) + thought doc (design workspace) + roll-up (clean summary for review)."
|
|
3
3
|
version: 0.3.1
|
|
4
4
|
status: beta
|
|
5
|
+
audience:
|
|
6
|
+
- engineering
|
|
5
7
|
|
|
6
8
|
includes:
|
|
7
9
|
skills:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orderful/droid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@slack/web-api": "^7.13.0",
|
|
49
|
+
"archiver": "^7.0.1",
|
|
49
50
|
"chalk": "^5.3.0",
|
|
50
51
|
"commander": "^12.1.0",
|
|
51
52
|
"ink": "^6.5.1",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"@changesets/changelog-github": "^0.5.2",
|
|
61
62
|
"@changesets/cli": "^2.29.8",
|
|
62
63
|
"@playwright/test": "^1.57.0",
|
|
64
|
+
"@types/archiver": "^7.0.0",
|
|
63
65
|
"@types/bun": "latest",
|
|
64
66
|
"@types/ink-text-input": "^2.0.5",
|
|
65
67
|
"@types/inquirer": "^9.0.7",
|
package/scripts/build.ts
CHANGED
|
@@ -65,9 +65,10 @@ async function main() {
|
|
|
65
65
|
packages: 'external',
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
// Copy tools directory
|
|
68
|
+
// Copy tools directory (exclude test files — they don't belong in installed skills)
|
|
69
69
|
console.log('Copying tools...');
|
|
70
|
-
|
|
70
|
+
const skipTestFiles = (src: string) => !/\.test\.ts$/.test(src);
|
|
71
|
+
cpSync('src/tools', 'dist/tools', { recursive: true, filter: skipTestFiles });
|
|
71
72
|
|
|
72
73
|
// Copy integrations directory (references, etc.)
|
|
73
74
|
console.log('Copying integrations...');
|
package/src/bin/droid.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { uninstallCommand } from '../commands/uninstall';
|
|
|
10
10
|
import { updateCommand } from '../commands/update';
|
|
11
11
|
import { tuiCommand } from '../commands/tui';
|
|
12
12
|
import { execCommand } from '../commands/exec';
|
|
13
|
+
import { packCommand } from '../commands/pack';
|
|
13
14
|
import {
|
|
14
15
|
reposListCommand,
|
|
15
16
|
reposAddCommand,
|
|
@@ -103,6 +104,14 @@ program
|
|
|
103
104
|
.description('Launch interactive TUI dashboard')
|
|
104
105
|
.action(tuiCommand);
|
|
105
106
|
|
|
107
|
+
program
|
|
108
|
+
.command('pack')
|
|
109
|
+
.description('Create audience-filtered zip packs for distribution')
|
|
110
|
+
.argument('[audience]', 'Target audience (e.g., engineering, customer-support)')
|
|
111
|
+
.option('-l, --list', 'List available audiences and tool counts')
|
|
112
|
+
.option('-o, --output <dir>', 'Output directory (default: cwd)')
|
|
113
|
+
.action(packCommand);
|
|
114
|
+
|
|
106
115
|
program
|
|
107
116
|
.command('exec <tool> <script>')
|
|
108
117
|
.description('Execute a tool script')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ToolAudience } from '../lib/types';
|
|
3
|
+
import { getAudienceInfo, buildPack } from '../lib/pack';
|
|
4
|
+
|
|
5
|
+
function isValidAudience(value: string): value is ToolAudience {
|
|
6
|
+
return Object.values(ToolAudience).includes(value as ToolAudience);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function packCommand(
|
|
10
|
+
audience: string | undefined,
|
|
11
|
+
options: { list?: boolean; output?: string },
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
// --list mode: show audiences and tool counts
|
|
14
|
+
if (options.list) {
|
|
15
|
+
const infos = getAudienceInfo();
|
|
16
|
+
|
|
17
|
+
if (infos.length === 0) {
|
|
18
|
+
console.log(chalk.yellow('No tools with audience metadata found.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.bold('\nAvailable audiences:\n'));
|
|
23
|
+
for (const info of infos) {
|
|
24
|
+
console.log(
|
|
25
|
+
` ${chalk.cyan(info.audience.padEnd(24))} ${chalk.gray(`${info.toolCount} tools`)} ${chalk.gray(info.toolNames.join(', '))}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
console.log('');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Require audience argument
|
|
33
|
+
if (!audience) {
|
|
34
|
+
console.error(chalk.red('\nError: audience argument required'));
|
|
35
|
+
console.log(chalk.gray('Usage: droid pack <audience>'));
|
|
36
|
+
console.log(chalk.gray(' droid pack --list'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate audience
|
|
41
|
+
if (!isValidAudience(audience)) {
|
|
42
|
+
console.error(chalk.red(`\nError: unknown audience '${audience}'`));
|
|
43
|
+
console.log(chalk.gray('\nValid audiences:'));
|
|
44
|
+
for (const a of Object.values(ToolAudience)) {
|
|
45
|
+
if (a !== ToolAudience.All) {
|
|
46
|
+
console.log(chalk.gray(` - ${a}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const outputDir = options.output || process.cwd();
|
|
53
|
+
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.bold(`\nPacking tools for ${chalk.cyan(audience)}...`),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const result = await buildPack({ audience, outputDir });
|
|
59
|
+
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
console.error(chalk.red(`\n${result.message}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(chalk.green(`\n${result.message}`));
|
|
66
|
+
console.log(chalk.gray(` ${result.toolCount} tools included`));
|
|
67
|
+
console.log(chalk.gray(` Output: ${result.outputPath}`));
|
|
68
|
+
|
|
69
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
70
|
+
console.log(chalk.yellow('\nWarnings:'));
|
|
71
|
+
for (const warning of result.warnings) {
|
|
72
|
+
console.log(chalk.yellow(` - ${warning}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { ToolAudience } from './types';
|
|
3
|
+
import { getToolsForAudience, getAudienceInfo } from './pack';
|
|
4
|
+
|
|
5
|
+
// Uses real bundled tools for integration-style tests
|
|
6
|
+
|
|
7
|
+
describe('getToolsForAudience', () => {
|
|
8
|
+
it('should include "all" audience tools in any audience pack', () => {
|
|
9
|
+
const tools = getToolsForAudience(ToolAudience.Engineering);
|
|
10
|
+
const toolNames = tools.map((t) => t.name);
|
|
11
|
+
// brain is audience: [all]
|
|
12
|
+
expect(toolNames).toContain('brain');
|
|
13
|
+
// comments is audience: [all]
|
|
14
|
+
expect(toolNames).toContain('comments');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should include audience-specific tools', () => {
|
|
18
|
+
const tools = getToolsForAudience(ToolAudience.Engineering);
|
|
19
|
+
const toolNames = tools.map((t) => t.name);
|
|
20
|
+
// code-review is audience: [engineering]
|
|
21
|
+
expect(toolNames).toContain('code-review');
|
|
22
|
+
// release is audience: [engineering]
|
|
23
|
+
expect(toolNames).toContain('release');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should include tools with prerequisites (codex)', () => {
|
|
27
|
+
const tools = getToolsForAudience(ToolAudience.Engineering);
|
|
28
|
+
const toolNames = tools.map((t) => t.name);
|
|
29
|
+
expect(toolNames).toContain('codex');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should not include tools from other audiences', () => {
|
|
33
|
+
// edi-schema is engineering-only
|
|
34
|
+
const tools = getToolsForAudience(ToolAudience.Product);
|
|
35
|
+
const toolNames = tools.map((t) => t.name);
|
|
36
|
+
expect(toolNames).not.toContain('edi-schema');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return tools for audiences with only "all" tools', () => {
|
|
40
|
+
const tools = getToolsForAudience(ToolAudience.CustomerSupport);
|
|
41
|
+
// customer-support has no specific tools but gets all 'all' audience tools
|
|
42
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
43
|
+
// Every tool should have audience 'all'
|
|
44
|
+
for (const tool of tools) {
|
|
45
|
+
expect(tool.audience).toContain(ToolAudience.All);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should skip tools without audience metadata', () => {
|
|
50
|
+
const tools = getToolsForAudience(ToolAudience.Engineering);
|
|
51
|
+
for (const tool of tools) {
|
|
52
|
+
expect(tool.audience).toBeDefined();
|
|
53
|
+
expect(tool.audience!.length).toBeGreaterThan(0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('getAudienceInfo', () => {
|
|
59
|
+
it('should return info for audiences that have tools', () => {
|
|
60
|
+
const infos = getAudienceInfo();
|
|
61
|
+
expect(infos.length).toBeGreaterThan(0);
|
|
62
|
+
|
|
63
|
+
// Engineering should be present (has edi-schema, code-review, etc.)
|
|
64
|
+
const engineering = infos.find(
|
|
65
|
+
(i) => i.audience === ToolAudience.Engineering,
|
|
66
|
+
);
|
|
67
|
+
expect(engineering).toBeDefined();
|
|
68
|
+
expect(engineering!.toolCount).toBeGreaterThan(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not include the "all" audience itself', () => {
|
|
72
|
+
const infos = getAudienceInfo();
|
|
73
|
+
const all = infos.find((i) => i.audience === ToolAudience.All);
|
|
74
|
+
expect(all).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should count "all" audience tools in each audience', () => {
|
|
78
|
+
const infos = getAudienceInfo();
|
|
79
|
+
const engineering = infos.find(
|
|
80
|
+
(i) => i.audience === ToolAudience.Engineering,
|
|
81
|
+
);
|
|
82
|
+
// Engineering should include 'all' tools like brain, comments
|
|
83
|
+
expect(engineering!.toolNames).toContain('brain');
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/lib/pack.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, createWriteStream } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import archiver from 'archiver';
|
|
4
|
+
import { getBundledTools, getBundledToolsDir } from './tools';
|
|
5
|
+
import { ToolAudience, type ToolManifest } from './types';
|
|
6
|
+
|
|
7
|
+
export interface AudienceInfo {
|
|
8
|
+
audience: ToolAudience;
|
|
9
|
+
toolCount: number;
|
|
10
|
+
toolNames: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PackOptions {
|
|
14
|
+
audience: ToolAudience;
|
|
15
|
+
outputDir: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PackResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
message: string;
|
|
21
|
+
outputPath?: string;
|
|
22
|
+
toolCount?: number;
|
|
23
|
+
warnings?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get tools filtered by audience
|
|
28
|
+
* Tools with audience 'all' are always included
|
|
29
|
+
*/
|
|
30
|
+
export function getToolsForAudience(
|
|
31
|
+
audience: ToolAudience,
|
|
32
|
+
): ToolManifest[] {
|
|
33
|
+
const allTools = getBundledTools();
|
|
34
|
+
|
|
35
|
+
return allTools.filter((tool) => {
|
|
36
|
+
// Skip tools without audience metadata
|
|
37
|
+
if (!tool.audience || tool.audience.length === 0) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Must match the requested audience OR be audience 'all'
|
|
42
|
+
return (
|
|
43
|
+
tool.audience.includes(audience) ||
|
|
44
|
+
tool.audience.includes(ToolAudience.All)
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get audience info for all audiences that have tools
|
|
51
|
+
*/
|
|
52
|
+
export function getAudienceInfo(): AudienceInfo[] {
|
|
53
|
+
const allTools = getBundledTools();
|
|
54
|
+
const audiences = Object.values(ToolAudience).filter(
|
|
55
|
+
(a) => a !== ToolAudience.All,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return audiences
|
|
59
|
+
.map((audience) => {
|
|
60
|
+
const tools = allTools.filter(
|
|
61
|
+
(tool) =>
|
|
62
|
+
tool.audience &&
|
|
63
|
+
(tool.audience.includes(audience) ||
|
|
64
|
+
tool.audience.includes(ToolAudience.All)),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
audience,
|
|
69
|
+
toolCount: tools.length,
|
|
70
|
+
toolNames: tools.map((t) => t.name),
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
.filter((info) => info.toolCount > 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Collect all artifact paths for a tool (skills, commands, agents)
|
|
78
|
+
* Returns paths relative to the zip root (installed format)
|
|
79
|
+
*/
|
|
80
|
+
function collectToolArtifacts(
|
|
81
|
+
tool: ToolManifest,
|
|
82
|
+
): Array<{ sourcePath: string; zipPath: string }> {
|
|
83
|
+
const artifacts: Array<{ sourcePath: string; zipPath: string }> = [];
|
|
84
|
+
const toolDir = join(getBundledToolsDir(), tool.name);
|
|
85
|
+
|
|
86
|
+
// Skills
|
|
87
|
+
for (const skill of tool.includes.skills) {
|
|
88
|
+
const skillDir = join(toolDir, 'skills', skill.name);
|
|
89
|
+
if (!existsSync(skillDir)) continue;
|
|
90
|
+
|
|
91
|
+
// SKILL.md
|
|
92
|
+
const skillMd = join(skillDir, 'SKILL.md');
|
|
93
|
+
if (existsSync(skillMd)) {
|
|
94
|
+
artifacts.push({
|
|
95
|
+
sourcePath: skillMd,
|
|
96
|
+
zipPath: `skills/${skill.name}/SKILL.md`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// references/
|
|
101
|
+
const refsDir = join(skillDir, 'references');
|
|
102
|
+
if (existsSync(refsDir)) {
|
|
103
|
+
const refFiles = readdirSync(refsDir).filter((f) => f.endsWith('.md'));
|
|
104
|
+
for (const file of refFiles) {
|
|
105
|
+
artifacts.push({
|
|
106
|
+
sourcePath: join(refsDir, file),
|
|
107
|
+
zipPath: `skills/${skill.name}/references/${file}`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Commands
|
|
114
|
+
const commandsDir = join(toolDir, 'commands');
|
|
115
|
+
if (existsSync(commandsDir)) {
|
|
116
|
+
const commandFiles = readdirSync(commandsDir).filter(
|
|
117
|
+
(f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
|
|
118
|
+
);
|
|
119
|
+
for (const file of commandFiles) {
|
|
120
|
+
artifacts.push({
|
|
121
|
+
sourcePath: join(commandsDir, file),
|
|
122
|
+
zipPath: `commands/${file}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Agents
|
|
128
|
+
const agentsDir = join(toolDir, 'agents');
|
|
129
|
+
if (existsSync(agentsDir)) {
|
|
130
|
+
const agentFiles = readdirSync(agentsDir).filter(
|
|
131
|
+
(f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
|
|
132
|
+
);
|
|
133
|
+
for (const file of agentFiles) {
|
|
134
|
+
artifacts.push({
|
|
135
|
+
sourcePath: join(agentsDir, file),
|
|
136
|
+
zipPath: `agents/${file}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return artifacts;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate CLAUDE.md content with skill registration links
|
|
146
|
+
*/
|
|
147
|
+
function generateClaudeMd(tools: ToolManifest[]): string {
|
|
148
|
+
const skillNames: string[] = [];
|
|
149
|
+
for (const tool of tools) {
|
|
150
|
+
for (const skill of tool.includes.skills) {
|
|
151
|
+
skillNames.push(skill.name);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const lines = [
|
|
156
|
+
'# Droid Skills',
|
|
157
|
+
'',
|
|
158
|
+
'Skills installed from a Droid pack. Each skill teaches your AI new capabilities.',
|
|
159
|
+
'',
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
for (const name of skillNames) {
|
|
163
|
+
lines.push(`- [${name}](skills/${name}/SKILL.md)`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
lines.push('');
|
|
167
|
+
return lines.join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate README.md with install instructions
|
|
172
|
+
*/
|
|
173
|
+
function generateReadme(audience: ToolAudience, tools: ToolManifest[]): string {
|
|
174
|
+
const lines = [
|
|
175
|
+
`# Droid Pack: ${audience}`,
|
|
176
|
+
'',
|
|
177
|
+
'This pack contains AI skills, commands, and agents for Claude Desktop.',
|
|
178
|
+
'',
|
|
179
|
+
'## Installation',
|
|
180
|
+
'',
|
|
181
|
+
'1. Unzip this archive',
|
|
182
|
+
'2. Copy the contents into your `~/.claude/` directory:',
|
|
183
|
+
'',
|
|
184
|
+
'```bash',
|
|
185
|
+
'# From the directory where you unzipped:',
|
|
186
|
+
'cp -r skills/ ~/.claude/skills/',
|
|
187
|
+
'cp -r commands/ ~/.claude/commands/',
|
|
188
|
+
'cp -r agents/ ~/.claude/agents/',
|
|
189
|
+
'```',
|
|
190
|
+
'',
|
|
191
|
+
'3. Append the contents of `CLAUDE.md` to your `~/.claude/CLAUDE.md`:',
|
|
192
|
+
'',
|
|
193
|
+
'```bash',
|
|
194
|
+
'cat CLAUDE.md >> ~/.claude/CLAUDE.md',
|
|
195
|
+
'```',
|
|
196
|
+
'',
|
|
197
|
+
'## Included Tools',
|
|
198
|
+
'',
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
for (const tool of tools) {
|
|
202
|
+
lines.push(`- **${tool.name}** (v${tool.version}) — ${tool.description}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lines.push('');
|
|
206
|
+
return lines.join('\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check for missing dependencies and return warnings
|
|
211
|
+
*/
|
|
212
|
+
function checkDependencies(tools: ToolManifest[]): string[] {
|
|
213
|
+
const warnings: string[] = [];
|
|
214
|
+
const toolNames = new Set(tools.map((t) => t.name));
|
|
215
|
+
|
|
216
|
+
for (const tool of tools) {
|
|
217
|
+
if (!tool.dependencies) continue;
|
|
218
|
+
for (const dep of tool.dependencies) {
|
|
219
|
+
if (!toolNames.has(dep)) {
|
|
220
|
+
warnings.push(
|
|
221
|
+
`Tool '${tool.name}' depends on '${dep}' which is not included in this pack`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return warnings;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Build a zip pack for the given audience
|
|
232
|
+
*/
|
|
233
|
+
export async function buildPack(options: PackOptions): Promise<PackResult> {
|
|
234
|
+
const { audience, outputDir } = options;
|
|
235
|
+
|
|
236
|
+
// Get filtered tools
|
|
237
|
+
const tools = getToolsForAudience(audience);
|
|
238
|
+
|
|
239
|
+
if (tools.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
message: `No tools found for audience '${audience}'`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check dependencies
|
|
247
|
+
const warnings = checkDependencies(tools);
|
|
248
|
+
|
|
249
|
+
// Build the zip
|
|
250
|
+
const filename = `droid-${audience}-pack.zip`;
|
|
251
|
+
const outputPath = join(outputDir, filename);
|
|
252
|
+
|
|
253
|
+
return new Promise((resolve) => {
|
|
254
|
+
const output = createWriteStream(outputPath);
|
|
255
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
256
|
+
|
|
257
|
+
output.on('close', () => {
|
|
258
|
+
resolve({
|
|
259
|
+
success: true,
|
|
260
|
+
message: `Pack created: ${filename}`,
|
|
261
|
+
outputPath,
|
|
262
|
+
toolCount: tools.length,
|
|
263
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
archive.on('error', (err: Error) => {
|
|
268
|
+
resolve({
|
|
269
|
+
success: false,
|
|
270
|
+
message: `Failed to create pack: ${err.message}`,
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
archive.pipe(output);
|
|
275
|
+
|
|
276
|
+
// Add all tool artifacts
|
|
277
|
+
for (const tool of tools) {
|
|
278
|
+
const artifacts = collectToolArtifacts(tool);
|
|
279
|
+
for (const artifact of artifacts) {
|
|
280
|
+
const content = readFileSync(artifact.sourcePath, 'utf-8');
|
|
281
|
+
archive.append(content, { name: artifact.zipPath });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Add generated CLAUDE.md
|
|
286
|
+
archive.append(generateClaudeMd(tools), { name: 'CLAUDE.md' });
|
|
287
|
+
|
|
288
|
+
// Add generated README.md
|
|
289
|
+
archive.append(generateReadme(audience, tools), { name: 'README.md' });
|
|
290
|
+
|
|
291
|
+
archive.finalize();
|
|
292
|
+
});
|
|
293
|
+
}
|
package/src/lib/types.ts
CHANGED
|
@@ -219,12 +219,31 @@ export interface ToolIncludes {
|
|
|
219
219
|
agents: string[];
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
export enum ToolAudience {
|
|
223
|
+
All = 'all',
|
|
224
|
+
Engineering = 'engineering',
|
|
225
|
+
Product = 'product',
|
|
226
|
+
Design = 'design',
|
|
227
|
+
IntegrationDevelopers = 'integration-developers',
|
|
228
|
+
CustomerSupport = 'customer-support',
|
|
229
|
+
NetworkOperations = 'network-operations',
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface ToolPrerequisite {
|
|
233
|
+
name: string;
|
|
234
|
+
description: string;
|
|
235
|
+
check: string;
|
|
236
|
+
install_hint: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
222
239
|
export interface ToolManifest {
|
|
223
240
|
name: string;
|
|
224
241
|
description: string;
|
|
225
242
|
version: string;
|
|
226
243
|
status?: SkillStatus;
|
|
244
|
+
audience?: ToolAudience[];
|
|
227
245
|
includes: ToolIncludes;
|
|
228
246
|
dependencies?: string[];
|
|
247
|
+
prerequisites?: ToolPrerequisite[];
|
|
229
248
|
config_schema?: Record<string, ConfigOption>;
|
|
230
249
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "droid-brain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Your scratchpad (or brain) - a collaborative space for planning and research. Create docs with /brain plan, /brain research, or /brain review. Use @mentions for async discussion. Docs persist across sessions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Orderful",
|