agentcraft 0.0.3 → 0.0.4

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcraft",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Assign sounds to AI coding agent lifecycle events. Works with Claude Code and OpenCode.",
5
5
  "author": { "name": "rohenaz" },
6
6
  "keywords": ["sounds", "hooks", "audio", "productivity", "opencode", "claude-code"]
package/README.md CHANGED
@@ -49,11 +49,12 @@ bun install -g agentcraft
49
49
  ```
50
50
 
51
51
  ```bash
52
- agentcraft pack install rohenaz/agentcraft-sounds # install a pack from GitHub
53
- agentcraft pack list # list installed packs
54
- agentcraft pack update rohenaz/agentcraft-sounds # update a pack (git pull)
55
- agentcraft pack update --all # update all packs
56
- agentcraft pack remove rohenaz/agentcraft-sounds # remove a pack
52
+ agentcraft init # first-time setup
53
+ agentcraft add rohenaz/agentcraft-sounds # install a pack from GitHub
54
+ agentcraft list # list installed packs
55
+ agentcraft update rohenaz/agentcraft-sounds # update a pack (git pull)
56
+ agentcraft update # update all packs
57
+ agentcraft remove rohenaz/agentcraft-sounds # remove a pack
57
58
  ```
58
59
 
59
60
  ## Sound Packs
package/bin/agentcraft.js CHANGED
@@ -2,20 +2,30 @@
2
2
  'use strict';
3
3
 
4
4
  const { execSync, spawnSync } = require('child_process');
5
- const { existsSync, readdirSync, rmSync, statSync, mkdirSync } = require('fs');
6
- const { join } = require('path');
5
+ const { existsSync, readdirSync, rmSync, statSync, mkdirSync, readFileSync, copyFileSync, realpathSync } = require('fs');
6
+ const { join, dirname } = require('path');
7
7
  const { homedir } = require('os');
8
8
 
9
9
  const PACKS_DIR = join(homedir(), '.agentcraft', 'packs');
10
+ const ASSIGNMENTS_PATH = join(homedir(), '.agentcraft', 'assignments.json');
10
11
  const [,, cmd, sub, ...rest] = process.argv;
11
12
 
13
+ // ANSI colors — no dependencies
14
+ const c = {
15
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
16
+ green: s => `\x1b[32m${s}\x1b[0m`,
17
+ red: s => `\x1b[31m${s}\x1b[0m`,
18
+ dim: s => `\x1b[2m${s}\x1b[0m`,
19
+ bold: s => `\x1b[1m${s}\x1b[0m`,
20
+ };
21
+
12
22
  function ensureDir(p) {
13
23
  mkdirSync(p, { recursive: true });
14
24
  }
15
25
 
16
26
  function parsePackId(arg) {
17
27
  if (!arg || !arg.includes('/')) {
18
- console.error(`Error: pack must be "publisher/name", got: ${arg ?? '(nothing)'}`);
28
+ console.error(c.red(`✗ pack must be "publisher/name", got: ${arg ?? '(nothing)'}`));
19
29
  process.exit(1);
20
30
  }
21
31
  const slash = arg.indexOf('/');
@@ -24,133 +34,194 @@ function parsePackId(arg) {
24
34
  return { publisher, name, id: arg, url: `https://github.com/${publisher}/${name}` };
25
35
  }
26
36
 
37
+ function readPackMeta(packPath) {
38
+ try { return JSON.parse(readFileSync(join(packPath, 'pack.json'), 'utf-8')); } catch { return {}; }
39
+ }
40
+
27
41
  function getInstalledPacks() {
28
42
  if (!existsSync(PACKS_DIR)) return [];
29
43
  const packs = [];
30
44
  for (const publisher of readdirSync(PACKS_DIR)) {
31
45
  const ppath = join(PACKS_DIR, publisher);
32
- try {
33
- if (!statSync(ppath).isDirectory()) continue;
34
- } catch { continue; }
46
+ try { if (!statSync(ppath).isDirectory()) continue; } catch { continue; }
35
47
  for (const name of readdirSync(ppath)) {
36
- try {
37
- if (statSync(join(ppath, name)).isDirectory()) packs.push(`${publisher}/${name}`);
38
- } catch { continue; }
48
+ try { if (statSync(join(ppath, name)).isDirectory()) packs.push(`${publisher}/${name}`); } catch { continue; }
39
49
  }
40
50
  }
41
51
  return packs;
42
52
  }
43
53
 
44
- function packInstall(packArg) {
54
+ function packAdd(packArg) {
45
55
  const { publisher, name, url } = parsePackId(packArg);
46
56
  const dest = join(PACKS_DIR, publisher, name);
47
57
  if (existsSync(dest)) {
48
- console.log(`Already installed: ${publisher}/${name}`);
58
+ console.log(c.dim(`Already installed: ${publisher}/${name}`));
49
59
  process.exit(0);
50
60
  }
51
61
  ensureDir(join(PACKS_DIR, publisher));
52
- console.log(`Installing ${publisher}/${name} from ${url} ...`);
62
+ console.log(`→ Installing ${c.cyan(`${publisher}/${name}`)} from ${c.dim(url)} ...`);
53
63
  const r = spawnSync('git', ['clone', url, dest], { stdio: 'inherit' });
54
64
  if (r.status !== 0) {
55
- console.error(`Failed to install ${publisher}/${name}`);
65
+ console.error(c.red(`✗ Failed to install ${publisher}/${name}`));
56
66
  process.exit(1);
57
67
  }
58
- console.log(`\nInstalled: ${publisher}/${name}`);
68
+ console.log(c.green(`✓ Installed: ${publisher}/${name}`));
59
69
  }
60
70
 
61
71
  function packRemove(packArg) {
62
72
  const { publisher, name } = parsePackId(packArg);
63
73
  const dest = join(PACKS_DIR, publisher, name);
64
74
  if (!existsSync(dest)) {
65
- console.error(`Not installed: ${publisher}/${name}`);
75
+ console.error(c.red(`✗ Not installed: ${publisher}/${name}`));
66
76
  process.exit(1);
67
77
  }
68
78
  rmSync(dest, { recursive: true, force: true });
69
- console.log(`Removed: ${publisher}/${name}`);
79
+ console.log(c.green(`✓ Removed: ${publisher}/${name}`));
70
80
  }
71
81
 
72
82
  function packUpdate(packArg) {
73
83
  const { publisher, name } = parsePackId(packArg);
74
84
  const dest = join(PACKS_DIR, publisher, name);
75
85
  if (!existsSync(dest)) {
76
- console.error(`Not installed: ${publisher}/${name}. Run: agentcraft pack install ${publisher}/${name}`);
86
+ console.error(c.red(`✗ Not installed: ${publisher}/${name}`));
87
+ console.error(c.dim(` Run: agentcraft add ${publisher}/${name}`));
77
88
  process.exit(1);
78
89
  }
79
- console.log(`Updating ${publisher}/${name} ...`);
90
+ console.log(`→ Updating ${c.cyan(`${publisher}/${name}`)} ...`);
80
91
  spawnSync('git', ['-C', dest, 'pull'], { stdio: 'inherit' });
81
92
  }
82
93
 
83
94
  function packList() {
84
95
  const packs = getInstalledPacks();
85
96
  if (!packs.length) {
86
- console.log('No packs installed.');
87
- console.log('\nInstall the official pack:');
88
- console.log(' agentcraft pack install rohenaz/agentcraft-sounds');
97
+ console.log(c.dim('No packs installed.'));
98
+ console.log('');
99
+ console.log('Install the official pack:');
100
+ console.log(` ${c.cyan('agentcraft add rohenaz/agentcraft-sounds')}`);
101
+ return;
102
+ }
103
+ console.log(c.bold('Installed packs:'));
104
+ for (const p of packs) {
105
+ const meta = readPackMeta(join(PACKS_DIR, ...p.split('/')));
106
+ const version = meta.version ? c.dim(` v${meta.version}`) : '';
107
+ const desc = meta.description ? c.dim(` ${meta.description}`) : '';
108
+ console.log(` ${c.cyan(p)}${version}${desc}`);
109
+ }
110
+ }
111
+
112
+ function packInit() {
113
+ console.log(c.bold('Initializing AgentCraft...'));
114
+ console.log('');
115
+
116
+ // 1. Install official pack if missing
117
+ const officialPack = join(PACKS_DIR, 'rohenaz', 'agentcraft-sounds');
118
+ if (!existsSync(officialPack)) {
119
+ packAdd('rohenaz/agentcraft-sounds');
89
120
  } else {
90
- console.log('Installed packs:');
91
- for (const p of packs) console.log(` ${p}`);
121
+ console.log(c.dim(`✓ Official pack already installed`));
122
+ }
123
+
124
+ // 2. Create assignments.json from pack defaults if missing
125
+ if (!existsSync(ASSIGNMENTS_PATH)) {
126
+ const defaultsPath = join(officialPack, 'defaults', 'assignments.json');
127
+ if (existsSync(defaultsPath)) {
128
+ ensureDir(join(homedir(), '.agentcraft'));
129
+ copyFileSync(defaultsPath, ASSIGNMENTS_PATH);
130
+ console.log(c.green('✓ Created ~/.agentcraft/assignments.json'));
131
+ }
132
+ } else {
133
+ console.log(c.dim('✓ assignments.json already exists'));
134
+ }
135
+
136
+ console.log('');
137
+ console.log(c.green('✓ AgentCraft ready!'));
138
+ console.log(` Dashboard: ${c.cyan('agentcraft start')}`);
139
+ console.log(` Browse packs: ${c.cyan('agentcraft list')}`);
140
+ }
141
+
142
+ function getWebDir() {
143
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
144
+ return join(process.env.CLAUDE_PLUGIN_ROOT, 'web');
145
+ }
146
+ // Global bun/npm install: bin/agentcraft.js → package root → web/
147
+ try {
148
+ return join(dirname(dirname(realpathSync(__filename))), 'web');
149
+ } catch {
150
+ console.error(c.red('✗ Cannot locate web directory. Set CLAUDE_PLUGIN_ROOT or reinstall.'));
151
+ process.exit(1);
92
152
  }
93
153
  }
94
154
 
95
155
  function showHelp() {
96
156
  console.log(`
97
- AgentCraft CLI — assign sounds to AI agent lifecycle events
157
+ ${c.bold('AgentCraft')} — assign sounds to AI agent lifecycle events
98
158
 
99
- Usage:
100
- agentcraft pack install <publisher/name> Install a sound pack from GitHub
101
- agentcraft pack remove <publisher/name> Remove an installed pack
102
- agentcraft pack update <publisher/name> Update a pack (git pull)
103
- agentcraft pack update --all Update all installed packs
104
- agentcraft pack list List installed packs
105
- agentcraft start Launch the dashboard (port 4040)
159
+ ${c.cyan('Usage:')}
160
+ agentcraft init Set up AgentCraft (install pack + config)
161
+ agentcraft add <publisher/name> Install a sound pack from GitHub
162
+ agentcraft remove <publisher/name> Remove an installed pack
163
+ agentcraft update [publisher/name] Update a pack, or all packs if no arg given
164
+ agentcraft list List installed packs
165
+ agentcraft start Launch the dashboard (port 4040)
106
166
 
107
- Examples:
108
- agentcraft pack install rohenaz/agentcraft-sounds
109
- agentcraft pack update --all
110
- agentcraft pack list
167
+ ${c.cyan('Examples:')}
168
+ agentcraft init
169
+ agentcraft add rohenaz/agentcraft-sounds
170
+ agentcraft add publisher/custom-pack
171
+ agentcraft update
172
+ agentcraft list
111
173
 
112
- Packs are stored at: ~/.agentcraft/packs/<publisher>/<name>/
113
- Any git repo cloned there is automatically discovered by the dashboard.
174
+ ${c.dim('Packs are stored at: ~/.agentcraft/packs/<publisher>/<name>/')}
175
+ ${c.dim('Any git repo cloned there is automatically discovered by the dashboard.')}
114
176
 
115
- Install the Claude Code plugin:
177
+ ${c.cyan('Install the Claude Code plugin:')}
116
178
  claude plugin install agentcraft@rohenaz
117
179
  `);
118
180
  }
119
181
 
120
- if (cmd === 'pack') {
121
- if (sub === 'install') {
122
- if (!rest[0]) { console.error('Usage: agentcraft pack install <publisher/name>'); process.exit(1); }
123
- packInstall(rest[0]);
124
- } else if (sub === 'remove') {
125
- if (!rest[0]) { console.error('Usage: agentcraft pack remove <publisher/name>'); process.exit(1); }
126
- packRemove(rest[0]);
127
- } else if (sub === 'update') {
128
- if (rest[0] === '--all') {
129
- const packs = getInstalledPacks();
130
- if (!packs.length) { console.log('No packs installed.'); process.exit(0); }
131
- for (const p of packs) packUpdate(p);
132
- } else {
133
- if (!rest[0]) { console.error('Usage: agentcraft pack update <publisher/name> | --all'); process.exit(1); }
134
- packUpdate(rest[0]);
135
- }
136
- } else if (sub === 'list') {
137
- packList();
182
+ // Route commands
183
+ if (cmd === 'init') {
184
+ packInit();
185
+ } else if (cmd === 'add') {
186
+ if (!sub) { console.error(c.red('Usage: agentcraft add <publisher/name>')); process.exit(1); }
187
+ packAdd(sub);
188
+ } else if (cmd === 'remove') {
189
+ if (!sub) { console.error(c.red('Usage: agentcraft remove <publisher/name>')); process.exit(1); }
190
+ packRemove(sub);
191
+ } else if (cmd === 'update') {
192
+ if (!sub) {
193
+ const packs = getInstalledPacks();
194
+ if (!packs.length) { console.log(c.dim('No packs installed.')); process.exit(0); }
195
+ for (const p of packs) packUpdate(p);
138
196
  } else {
139
- console.error(`Unknown pack subcommand: ${sub ?? '(none)'}`);
140
- console.error('Usage: agentcraft pack <install|remove|update|list>');
141
- process.exit(1);
197
+ packUpdate(sub);
142
198
  }
199
+ } else if (cmd === 'list') {
200
+ packList();
143
201
  } else if (cmd === 'start') {
144
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
145
- if (!pluginRoot) {
146
- console.error('CLAUDE_PLUGIN_ROOT not set. This command is intended to run from the Claude Code plugin context.');
202
+ const webDir = getWebDir();
203
+ if (!existsSync(webDir)) {
204
+ console.error(c.red(`✗ Web directory not found: ${webDir}`));
147
205
  process.exit(1);
148
206
  }
149
- execSync(`cd "${pluginRoot}/web" && bun dev --port 4040`, { stdio: 'inherit' });
207
+ console.log(`→ Starting AgentCraft at ${c.cyan('http://localhost:4040')} ...`);
208
+ execSync(`cd "${webDir}" && bun install --silent && bun dev --port 4040`, { stdio: 'inherit' });
150
209
  } else if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
151
210
  showHelp();
211
+ } else if (cmd === 'pack') {
212
+ // Legacy shim — print migration hint and route through
213
+ const newCmd = sub === 'install' ? 'add' : sub;
214
+ console.error(c.dim(`Note: "agentcraft pack ${sub}" → "agentcraft ${newCmd}"`));
215
+ if (sub === 'install') { if (!rest[0]) { console.error(c.red('Usage: agentcraft add <publisher/name>')); process.exit(1); } packAdd(rest[0]); }
216
+ else if (sub === 'remove') { if (!rest[0]) { console.error(c.red('Usage: agentcraft remove <publisher/name>')); process.exit(1); } packRemove(rest[0]); }
217
+ else if (sub === 'update') {
218
+ if (!rest[0] || rest[0] === '--all') { const packs = getInstalledPacks(); for (const p of packs) packUpdate(p); }
219
+ else packUpdate(rest[0]);
220
+ }
221
+ else if (sub === 'list') packList();
222
+ else { console.error(c.red(`Unknown: agentcraft pack ${sub}`)); process.exit(1); }
152
223
  } else {
153
- console.error(`Unknown command: ${cmd}`);
224
+ console.error(c.red(`✗ Unknown command: ${cmd}`));
154
225
  showHelp();
155
226
  process.exit(1);
156
227
  }
package/opencode.js CHANGED
@@ -3,7 +3,10 @@
3
3
  *
4
4
  * Plays sounds on OpenCode lifecycle events using the same assignments.json
5
5
  * config as the Claude Code plugin. Shared config lives at:
6
- * ~/.claude/sounds/assignments.json
6
+ * ~/.agentcraft/assignments.json
7
+ *
8
+ * Packs are stored at:
9
+ * ~/.agentcraft/packs/<publisher>/<name>/
7
10
  *
8
11
  * To install for OpenCode, symlink or copy this file to:
9
12
  * ~/.config/opencode/plugins/agentcraft.js (global)
@@ -15,24 +18,42 @@ import { execSync } from 'child_process';
15
18
  import { join } from 'path';
16
19
  import { homedir } from 'os';
17
20
 
18
- const CONFIG_PATH = join(homedir(), '.claude', 'sounds', 'assignments.json');
19
- const LIBRARY_PATH = join(homedir(), 'code', 'claude-sounds');
21
+ const ASSIGNMENTS_PATH = join(homedir(), '.agentcraft', 'assignments.json');
22
+ const PACKS_DIR = join(homedir(), '.agentcraft', 'packs');
20
23
 
21
24
  function getAssignments() {
22
25
  try {
23
- return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
26
+ return JSON.parse(readFileSync(ASSIGNMENTS_PATH, 'utf8'));
24
27
  } catch {
25
28
  return null;
26
29
  }
27
30
  }
28
31
 
32
+ function resolvePackPath(soundPath) {
33
+ if (!soundPath) return null;
34
+ if (soundPath.includes(':')) {
35
+ // "publisher/name:internal/path/to/sound.mp3"
36
+ const colon = soundPath.indexOf(':');
37
+ const packId = soundPath.slice(0, colon);
38
+ const internal = soundPath.slice(colon + 1);
39
+ const slash = packId.indexOf('/');
40
+ const publisher = packId.slice(0, slash);
41
+ const name = packId.slice(slash + 1);
42
+ return join(PACKS_DIR, publisher, name, internal);
43
+ }
44
+ // Legacy path — resolve against official pack
45
+ return join(PACKS_DIR, 'rohenaz', 'agentcraft-sounds', soundPath);
46
+ }
47
+
29
48
  function play(soundPath) {
30
- if (!soundPath) return;
31
- const full = join(LIBRARY_PATH, soundPath);
32
- if (!existsSync(full)) return;
49
+ const full = resolvePackPath(soundPath);
50
+ if (!full || !existsSync(full)) return;
33
51
  try {
34
52
  if (process.platform === 'darwin') execSync(`afplay "${full}" &`, { stdio: 'ignore' });
35
- else if (process.platform === 'linux') execSync(`paplay "${full}" &`, { stdio: 'ignore' });
53
+ else if (process.platform === 'linux') {
54
+ if (existsSync('/usr/bin/paplay')) execSync(`paplay "${full}" &`, { stdio: 'ignore' });
55
+ else execSync(`aplay "${full}" &`, { stdio: 'ignore' });
56
+ }
36
57
  } catch { /* non-blocking, ignore errors */ }
37
58
  }
38
59
 
@@ -62,7 +83,6 @@ export const AgentCraft = async () => {
62
83
  'tool.execute.before': async (input) => {
63
84
  const a = getAssignments();
64
85
  if (!a || a.settings?.enabled === false) return;
65
-
66
86
  if (input.tool === 'skill') {
67
87
  const key = input.args?.skill;
68
88
  if (!key) return;
@@ -75,7 +95,6 @@ export const AgentCraft = async () => {
75
95
  'tool.execute.after': async (input) => {
76
96
  const a = getAssignments();
77
97
  if (!a || a.settings?.enabled === false) return;
78
-
79
98
  if (input.tool === 'skill') {
80
99
  const key = input.args?.skill;
81
100
  if (!key) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcraft",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Assign sounds to AI coding agent lifecycle events. CLI for managing sound packs.",
5
5
  "license": "MIT",
6
6
  "author": "rohenaz",
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: agentcraft-packs
3
+ description: This skill should be used when the user asks to "install a sound pack", "add a pack", "find sound packs", "publish a pack", "create a pack", "share my sounds", "agentcraft pack install", "browse packs", "remove a pack", "update packs", or wants to know how the AgentCraft pack system works.
4
+ ---
5
+
6
+ # AgentCraft Sound Packs
7
+
8
+ Sound packs are git repos containing audio files. Any GitHub repo can be a pack — no approval, no registry required. Install by `publisher/name` slug, same as GitHub.
9
+
10
+ ## Installing Packs
11
+
12
+ ### From the CLI
13
+
14
+ ```bash
15
+ agentcraft init # first-time setup
16
+ agentcraft add rohenaz/agentcraft-sounds # official pack
17
+ agentcraft add publisher/repo-name # any GitHub repo
18
+ agentcraft list # show installed packs
19
+ agentcraft update rohenaz/agentcraft-sounds # git pull one pack
20
+ agentcraft update # update all packs
21
+ agentcraft remove publisher/repo-name # uninstall
22
+ ```
23
+
24
+ `agentcraft pack install publisher/repo-name` resolves to `https://github.com/publisher/repo-name` and clones into `~/.agentcraft/packs/publisher/repo-name/`.
25
+
26
+ Install the CLI globally:
27
+ ```bash
28
+ bun install -g agentcraft # or: npm install -g agentcraft
29
+ ```
30
+
31
+ ### From the Dashboard
32
+
33
+ Open the **PACKS** tab in the AgentCraft dashboard. Installed packs show UPDATE/REMOVE buttons. The **BROWSE PACKS** section fetches the community registry and shows packs not yet installed with an INSTALL button.
34
+
35
+ ### Manual Install (identical result)
36
+
37
+ ```bash
38
+ git clone https://github.com/publisher/repo-name ~/.agentcraft/packs/publisher/repo-name
39
+ ```
40
+
41
+ Manual clone and CLI install are exactly equivalent — no manifest or registration step.
42
+
43
+ ## Pack Storage
44
+
45
+ Packs live at `~/.agentcraft/packs/<publisher>/<name>/`. The dashboard auto-discovers everything at that path depth — any directory placed there works.
46
+
47
+ ```
48
+ ~/.agentcraft/packs/
49
+ rohenaz/
50
+ agentcraft-sounds/ ← official pack
51
+ publisher/
52
+ custom-pack/ ← any git repo cloned here
53
+ ```
54
+
55
+ ## Sound Assignment Paths
56
+
57
+ Assigned sounds are stored in `~/.agentcraft/assignments.json` with a pack-prefixed path:
58
+
59
+ ```
60
+ rohenaz/agentcraft-sounds:sc2/terran/session-start/scv-ready.mp3
61
+ ```
62
+
63
+ Format: `publisher/name:internal/path/to/sound.mp3`
64
+
65
+ The hook script resolves this to the absolute path at runtime:
66
+ ```
67
+ ~/.agentcraft/packs/rohenaz/agentcraft-sounds/sc2/terran/session-start/scv-ready.mp3
68
+ ```
69
+
70
+ ## Publishing a Pack
71
+
72
+ Any GitHub repo with audio files (`.mp3`, `.wav`, `.ogg`, `.m4a`) is a valid pack. No manifest required — directory structure is the organization.
73
+
74
+ ### Step 1: Organize the repo
75
+
76
+ Recommended structure — group sounds into directories by game, theme, or purpose:
77
+
78
+ ```
79
+ my-sounds/
80
+ sc2/
81
+ terran/
82
+ session-start/
83
+ ready.mp3
84
+ task-complete/
85
+ salute.mp3
86
+ halo/
87
+ unsc/
88
+ session-start/
89
+ wake-up.mp3
90
+ ui/ ← optional: UI theme sounds
91
+ sc2/
92
+ click.mp3
93
+ hover.mp3
94
+ ```
95
+
96
+ Any directory layout works. The dashboard groups sounds by their directory path.
97
+
98
+ ### Step 2: Add `pack.json` (optional but recommended)
99
+
100
+ ```json
101
+ {
102
+ "name": "my-sounds",
103
+ "publisher": "your-github-username",
104
+ "version": "1.0.0",
105
+ "description": "Short description of the pack",
106
+ "types": ["sounds", "ui"]
107
+ }
108
+ ```
109
+
110
+ `types` is informational. Use `"ui"` if the pack includes a `ui/` directory with dashboard theme sounds.
111
+
112
+ ### Step 3: Tag the repo on GitHub
113
+
114
+ Add the `agentcraft-pack` topic to the GitHub repo. This makes it discoverable in:
115
+ - The community registry at `https://rohenaz.github.io/agentcraft-registry/`
116
+ - GitHub search: `https://github.com/topics/agentcraft-pack`
117
+
118
+ To tag: GitHub repo → **Settings** → **Topics** → type `agentcraft-pack` → Save.
119
+
120
+ The registry GitHub Action runs every 6 hours and automatically picks up newly tagged repos.
121
+
122
+ ### Step 4: Share the install command
123
+
124
+ ```bash
125
+ agentcraft pack install your-username/your-repo-name
126
+ ```
127
+
128
+ That's the entire publish workflow — push to GitHub, tag it, done.
129
+
130
+ ## Pack Discovery
131
+
132
+ Find community packs three ways:
133
+
134
+ 1. **Dashboard** — PACKS tab → BROWSE PACKS section shows the registry
135
+ 2. **Registry** — `https://github.com/topics/agentcraft-pack`
136
+ 3. **Registry JSON** — `https://rohenaz.github.io/agentcraft-registry/index.json`
137
+
138
+ ## UI Theme Sounds
139
+
140
+ Packs can include a `ui/` directory with sounds that play as you use the AgentCraft dashboard (hover, click, page change, etc.). The dashboard's **UI SFX** dropdown lets users pick which pack's UI theme to use.
141
+
142
+ Structure the `ui/` directory by theme name:
143
+
144
+ ```
145
+ ui/
146
+ sc2/
147
+ click.mp3
148
+ hover.mp3
149
+ confirm.mp3
150
+ error.mp3
151
+ pageChange.mp3
152
+ wc3/
153
+ click.mp3
154
+ ...
155
+ ```
156
+
157
+ ## Additional Resources
158
+
159
+ - **`references/pack-format.md`** — Full audio file requirements, directory naming conventions, and pack.json schema
160
+ - **Registry source** — `https://github.com/rohenaz/agentcraft-registry`
@@ -0,0 +1,122 @@
1
+ # Pack Format Reference
2
+
3
+ ## Audio File Requirements
4
+
5
+ Supported formats: `.mp3`, `.wav`, `.ogg`, `.m4a`
6
+
7
+ Recommended: `.mp3` at 128–192kbps for compatibility and small file size.
8
+
9
+ File names become the display name in the sound browser (dashes/underscores become spaces, title-cased). Keep names descriptive:
10
+ - `scv-ready.mp3` → "Scv Ready"
11
+ - `marine-salute-00.mp3` → "Marine Salute 00"
12
+
13
+ ## Directory Structure
14
+
15
+ No required structure. The dashboard reads depth dynamically:
16
+
17
+ - Top-level directories → **Group tabs** (e.g. `sc2`, `halo`, `classic-os`)
18
+ - Second-level directories → **Sub-tabs** (e.g. `sc2/terran`, `halo/unsc`)
19
+ - Third-level directories → **Subcategories** within a sub-tab (e.g. `sc2/terran/session-start`)
20
+ - Files at any depth → Sound cards
21
+
22
+ Example layouts that all work:
23
+
24
+ **Game-organized:**
25
+ ```
26
+ sc2/terran/session-start/scv-ready.mp3
27
+ sc2/terran/task-complete/marine-salute.mp3
28
+ sc2/protoss/session-start/probe-ready.mp3
29
+ ```
30
+
31
+ **Flat:**
32
+ ```
33
+ sounds/click.mp3
34
+ sounds/beep.mp3
35
+ ```
36
+
37
+ **Mood-organized:**
38
+ ```
39
+ ambient/forest/birds.mp3
40
+ action/alert/warning.mp3
41
+ ```
42
+
43
+ ## pack.json Schema
44
+
45
+ All fields are optional. Used for display in the PACKS tab and the community registry.
46
+
47
+ ```json
48
+ {
49
+ "name": "string", // Display name (defaults to directory name)
50
+ "publisher": "string", // GitHub username (defaults to directory publisher)
51
+ "version": "string", // Semantic version, e.g. "1.0.0"
52
+ "description": "string", // One-line description shown in dashboard
53
+ "types": ["sounds", "ui"] // Content types: "sounds", "ui", or both
54
+ }
55
+ ```
56
+
57
+ ## UI Theme Sounds
58
+
59
+ Include a `ui/` directory at the pack root for dashboard UI sounds. Subdirectories are theme names. Each theme can define any subset of these event sounds:
60
+
61
+ | Filename | Trigger |
62
+ |----------|---------|
63
+ | `click.mp3` | Button click |
64
+ | `hover.mp3` | Element hover |
65
+ | `confirm.mp3` | Save / success action |
66
+ | `error.mp3` | Error / failure |
67
+ | `pageChange.mp3` | Tab / page switch |
68
+ | `drag.mp3` | Drag start |
69
+ | `drop.mp3` | Drop onto slot |
70
+ | `open.mp3` | Panel/modal open |
71
+ | `close.mp3` | Panel/modal close |
72
+
73
+ Missing files are silently skipped. Themes with no `ui/` directory are sound-only packs.
74
+
75
+ Example:
76
+ ```
77
+ ui/
78
+ sc2/
79
+ click.mp3
80
+ hover.mp3
81
+ confirm.mp3
82
+ error.mp3
83
+ wc3/
84
+ click.mp3
85
+ hover.mp3
86
+ ```
87
+
88
+ ## Assignment Path Format
89
+
90
+ When a sound is assigned to a hook, it's stored as:
91
+
92
+ ```
93
+ publisher/pack-name:path/to/sound.mp3
94
+ ```
95
+
96
+ The colon (`:`) separates pack identity from the internal path. The hook script resolves this to:
97
+
98
+ ```
99
+ ~/.agentcraft/packs/publisher/pack-name/path/to/sound.mp3
100
+ ```
101
+
102
+ Sounds from your pack will automatically use this format when assigned through the dashboard.
103
+
104
+ ## GitHub Topics
105
+
106
+ Tag your repo with `agentcraft-pack` to appear in the community registry. Additional optional tags:
107
+
108
+ | Topic | Meaning |
109
+ |-------|---------|
110
+ | `agentcraft-pack` | Required for discovery |
111
+ | `agentcraft-type-sounds` | Pack contains hook sounds |
112
+ | `agentcraft-type-ui` | Pack contains UI theme sounds |
113
+
114
+ The `agentcraft-type-*` topics are parsed by the registry Action and populate the `types` field in `index.json` automatically (as a fallback when no `pack.json` is present).
115
+
116
+ ## File Size Guidelines
117
+
118
+ - Individual sounds: ideally < 500KB, max 2MB
119
+ - Total pack size: ideally < 50MB
120
+ - Large packs slow down `agentcraft pack install` (git clone)
121
+
122
+ Consider using `.gitattributes` with Git LFS for packs with many large audio files.