@opensassi/opencode 0.1.4 → 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 (28) hide show
  1. package/AGENTS.md +4 -2
  2. package/package.json +2 -1
  3. package/skills/demo-video/SKILL.md +264 -0
  4. package/skills/demo-video/scripts/assemble.cjs +152 -0
  5. package/skills/demo-video/scripts/capture-browser.sh +64 -0
  6. package/skills/demo-video/scripts/capture-html.sh +48 -0
  7. package/skills/demo-video/scripts/generate-subs.cjs +75 -0
  8. package/skills/demo-video/scripts/generate-tts.sh +28 -0
  9. package/skills/demo-video/scripts/render-slide.cjs +61 -0
  10. package/skills/demo-video/scripts/render-terminal.cjs +138 -0
  11. package/skills/demo-video/scripts/setup.sh +44 -0
  12. package/skills/demo-video/test/assemble.test.js +100 -0
  13. package/skills/demo-video/test/capture-browser.test.js +71 -0
  14. package/skills/demo-video/test/capture-html.test.js +72 -0
  15. package/skills/demo-video/test/e2e-test.sh +302 -0
  16. package/skills/demo-video/test/fixtures/demo-scenes.json +36 -0
  17. package/skills/demo-video/test/fixtures/hello.output +2 -0
  18. package/skills/demo-video/test/fixtures/hello.timing +13 -0
  19. package/skills/demo-video/test/generate-subs.test.js +67 -0
  20. package/skills/demo-video/test/generate-tts.test.js +58 -0
  21. package/skills/demo-video/test/helpers/run-script.js +33 -0
  22. package/skills/demo-video/test/integration.test.js +110 -0
  23. package/skills/demo-video/test/jest.config.cjs +6 -0
  24. package/skills/demo-video/test/render-slide.test.js +79 -0
  25. package/skills/demo-video/test/render-terminal.test.js +87 -0
  26. package/skills/demo-video/test/setup.test.js +55 -0
  27. package/skills/opensassi/SKILL.md +14 -6
  28. package/skills-index.json +5 -0
@@ -0,0 +1,79 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+ const { runScript } = require('./helpers/run-script')
4
+
5
+ const SLIDE_SCRIPT = 'scripts/render-slide.cjs'
6
+
7
+ describe('render-slide.js — narration HTML slides', () => {
8
+ const outDir = '/tmp/demo-test-render-slide'
9
+
10
+ beforeEach(() => {
11
+ fs.mkdirSync(outDir, { recursive: true })
12
+ })
13
+
14
+ afterEach(() => {
15
+ fs.rmSync(outDir, { recursive: true, force: true })
16
+ })
17
+
18
+ test('RS01: basic bullets produce valid HTML', () => {
19
+ const outFile = path.join(outDir, 'slide.html')
20
+ const result = runScript(SLIDE_SCRIPT, [
21
+ '--title', 'Demo',
22
+ '--bullets', '["Point A","Point B"]',
23
+ '--output', outFile,
24
+ ])
25
+ expect(result.exitCode).toBe(0)
26
+ expect(fs.existsSync(outFile)).toBe(true)
27
+ const html = fs.readFileSync(outFile, 'utf-8')
28
+ expect(html).toContain('<h1>Demo</h1>')
29
+ expect(html).toContain('Point A')
30
+ expect(html).toContain('Point B')
31
+ })
32
+
33
+ test('RS02: empty bullets array', () => {
34
+ const outFile = path.join(outDir, 'slide.html')
35
+ const result = runScript(SLIDE_SCRIPT, [
36
+ '--title', 'Empty',
37
+ '--bullets', '[]',
38
+ '--output', outFile,
39
+ ])
40
+ expect(result.exitCode).toBe(0)
41
+ const html = fs.readFileSync(outFile, 'utf-8')
42
+ expect(html).toContain('<ul></ul>')
43
+ })
44
+
45
+ test('RS03: custom colors appear in CSS', () => {
46
+ const outFile = path.join(outDir, 'slide.html')
47
+ runScript(SLIDE_SCRIPT, [
48
+ '--title', 'Colors',
49
+ '--bullets', '["A"]',
50
+ '--background', '#000000',
51
+ '--foreground', '#ffffff',
52
+ '--output', outFile,
53
+ ])
54
+ const html = fs.readFileSync(outFile, 'utf-8')
55
+ expect(html).toContain('background:#000000')
56
+ expect(html).toContain('color:#ffffff')
57
+ })
58
+
59
+ test('RS04: missing title exits with usage', () => {
60
+ const outFile = path.join(outDir, 'slide.html')
61
+ const result = runScript(SLIDE_SCRIPT, [
62
+ '--bullets', '["A"]',
63
+ '--output', outFile,
64
+ ])
65
+ expect(result.exitCode).toBe(1)
66
+ })
67
+
68
+ test('RS05: special characters are HTML-escaped', () => {
69
+ const outFile = path.join(outDir, 'slide.html')
70
+ runScript(SLIDE_SCRIPT, [
71
+ '--title', 'Test',
72
+ '--bullets', '["A & B < C > D"]',
73
+ '--output', outFile,
74
+ ])
75
+ const html = fs.readFileSync(outFile, 'utf-8')
76
+ expect(html).toContain('&amp;')
77
+ expect(html).not.toContain('< C >')
78
+ })
79
+ })
@@ -0,0 +1,87 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+ const { runScript } = require('./helpers/run-script')
4
+
5
+ const RENDER_SCRIPT = 'scripts/render-terminal.cjs'
6
+ const FIXTURES = path.resolve(__dirname, 'fixtures')
7
+
8
+ describe('render-terminal.cjs — script timing → HTML terminal', () => {
9
+ const outDir = '/tmp/demo-test-render-terminal'
10
+
11
+ beforeEach(() => {
12
+ fs.mkdirSync(outDir, { recursive: true })
13
+ })
14
+
15
+ afterEach(() => {
16
+ fs.rmSync(outDir, { recursive: true, force: true })
17
+ })
18
+
19
+ test('RT01: valid timing and output files produce valid HTML', () => {
20
+ const outFile = path.join(outDir, 'out.html')
21
+ const result = runScript(RENDER_SCRIPT, [
22
+ '--timing', path.join(FIXTURES, 'hello.timing'),
23
+ '--output', path.join(FIXTURES, 'hello.output'),
24
+ '--command', 'echo hi',
25
+ '--speed', '3',
26
+ '--html', outFile,
27
+ ])
28
+ expect(result.exitCode).toBe(0)
29
+ expect(result.stdout).toContain('Wrote')
30
+ expect(fs.existsSync(outFile)).toBe(true)
31
+ const html = fs.readFileSync(outFile, 'utf-8')
32
+ expect(html).toContain('<!DOCTYPE html>')
33
+ expect(html).toContain('class="terminal"')
34
+ expect(html).toContain('echo hi')
35
+ })
36
+
37
+ test('RT02: missing timing file exits with error', () => {
38
+ const outFile = path.join(outDir, 'out.html')
39
+ const result = runScript(RENDER_SCRIPT, [
40
+ '--timing', '/nonexistent.timing',
41
+ '--output', path.join(FIXTURES, 'hello.output'),
42
+ '--html', outFile,
43
+ ])
44
+ expect(result.exitCode).toBe(1)
45
+ expect(result.stdout + result.stderr).toMatch(/not found|Error|ENOENT/)
46
+ })
47
+
48
+ test('RT03: missing output file exits with error', () => {
49
+ const outFile = path.join(outDir, 'out.html')
50
+ const result = runScript(RENDER_SCRIPT, [
51
+ '--timing', path.join(FIXTURES, 'hello.timing'),
52
+ '--output', '/nonexistent.output',
53
+ '--html', outFile,
54
+ ])
55
+ expect(result.exitCode).toBe(1)
56
+ expect(result.stdout + result.stderr).toMatch(/not found|Error|ENOENT/)
57
+ })
58
+
59
+ test('RT04: HTML renders without errors', () => {
60
+ const outFile = path.join(outDir, 'out.html')
61
+ const result = runScript(RENDER_SCRIPT, [
62
+ '--timing', path.join(FIXTURES, 'hello.timing'),
63
+ '--output', path.join(FIXTURES, 'hello.output'),
64
+ '--command', 'echo hi',
65
+ '--speed', '3',
66
+ '--html', outFile,
67
+ ])
68
+ expect(result.exitCode).toBe(0)
69
+ const html = fs.readFileSync(outFile, 'utf-8')
70
+ expect(html).toContain('<script>')
71
+ })
72
+
73
+ test('RT05: HTML includes segments from output file content', () => {
74
+ const outFile = path.join(outDir, 'out.html')
75
+ runScript(RENDER_SCRIPT, [
76
+ '--timing', path.join(FIXTURES, 'hello.timing'),
77
+ '--output', path.join(FIXTURES, 'hello.output'),
78
+ '--command', 'echo hi',
79
+ '--speed', '3',
80
+ '--html', outFile,
81
+ ])
82
+ const html = fs.readFileSync(outFile, 'utf-8')
83
+ expect(html).toContain('segs')
84
+ expect(html).toContain('function next()')
85
+ expect(html).not.toMatch(/fromCharCode/)
86
+ })
87
+ })
@@ -0,0 +1,55 @@
1
+ const path = require('path')
2
+ const { execSync } = require('child_process')
3
+ const { runScript } = require('./helpers/run-script')
4
+
5
+ const SETUP_SCRIPT = 'scripts/setup.sh'
6
+
7
+ function haveTool(name) {
8
+ try {
9
+ execSync(`command -v ${name}`, { stdio: 'ignore' })
10
+ return true
11
+ } catch { return false }
12
+ }
13
+
14
+ function allDepsPresent() {
15
+ try {
16
+ execSync('ffmpeg -version', { stdio: 'ignore', timeout: 3000 })
17
+ execSync('npx playwright --version', { stdio: 'ignore', timeout: 5000 })
18
+ execSync('python3 -m edge_tts --version', { stdio: 'ignore', timeout: 5000 })
19
+ return true
20
+ } catch { return false }
21
+ }
22
+
23
+ describe('setup.sh — dependency check', () => {
24
+ test('SD01: runs and checks ffmpeg', () => {
25
+ const result = runScript(SETUP_SCRIPT)
26
+ if (allDepsPresent()) {
27
+ expect(result.exitCode).toBe(0)
28
+ expect(result.stdout).toContain('[OK]')
29
+ expect(result.stdout).toContain('ffmpeg')
30
+ } else {
31
+ expect(result.exitCode).toBe(1)
32
+ expect(result.stdout).toContain('[MISSING]')
33
+ expect(result.stdout).toContain('ffmpeg')
34
+ }
35
+ })
36
+
37
+ test('SD02: output format includes OK or MISSING per tool', () => {
38
+ const result = runScript(SETUP_SCRIPT)
39
+ expect(result.stdout).toMatch(/\[OK\]|\[MISSING\]/)
40
+ expect(result.stdout).toContain('ffmpeg')
41
+ })
42
+
43
+ test('SD03: status line at end of output', () => {
44
+ const result = runScript(SETUP_SCRIPT)
45
+ const lines = result.stdout.trim().split('\n')
46
+ const lastLine = lines[lines.length - 1]
47
+ expect(lastLine).toMatch(/ready|missing|install|satisfied|dependencies/)
48
+ })
49
+
50
+ test('SD04: prints at least one tool line', () => {
51
+ const result = runScript(SETUP_SCRIPT)
52
+ const toolLines = result.stdout.split('\n').filter(l => l.startsWith('[OK]') || l.startsWith('[MISSING]'))
53
+ expect(toolLines.length).toBeGreaterThanOrEqual(1)
54
+ })
55
+ })
@@ -5,13 +5,16 @@ description: Root skill ecosystem — loads system-design + spec tree, routes su
5
5
 
6
6
  # Skill: opensassi — Root Skill Ecosystem
7
7
 
8
+ > **Invocation note:** Within this project, use `npm run opencode -- <cmd>`.
9
+ > Consumers of the published `@opensassi/opencode` package use `npx @opensassi/opencode <cmd>` instead.
10
+
8
11
  ## Entry Point
9
12
 
10
13
  | Input | Action |
11
14
  |-------|--------|
12
15
  | `/opensassi` | Load `skill system-design`, read `technical-specification.md` + spec tree depth 2 (root + facade specs). Report ready. |
13
16
  | `/opensassi init` | Run `env-check.sh`. Parse JSON result: if node+git+FlameGraph+deps all present → "Already bootstrapped". Otherwise run full bootstrap sequence (env-check → install → flamegraph → npm-deps → gitignore). |
14
- | `/opensassi <skill> <command> [args]` | Load `<skill>` from npm via `npx @opensassi/opencode <skill>`, then run `<command>` with `[args]`. Return result. |
17
+ | `/opensassi <skill> <command> [args]` | Load `<skill>` from npm via `npm run opencode -- <skill>`, then run `<command>` with `[args]`. Return result. |
15
18
 
16
19
  ### Spec tree depth
17
20
 
@@ -23,7 +26,7 @@ Depth is controlled by `--depth` flag on `load spec`:
23
26
 
24
27
  ## Init
25
28
 
26
- Single shell command: `npx @opensassi/opencode run --skill opensassi env-check.sh`
29
+ Single shell command: `npm run opencode -- run --skill opensassi env-check.sh`
27
30
 
28
31
  Returns JSON: `{"os": ..., "distro": ..., "node_version": ..., "git_version": ..., ...}`
29
32
 
@@ -37,11 +40,11 @@ bootstrapped = (node_version != "" && git_version != ""
37
40
  If bootstrapped → report "Environment ready." + show node/git versions.
38
41
  If not → run full bootstrap:
39
42
 
40
- 1. `npx @opensassi/opencode run --skill opensassi env-check.sh` — install git + Node.js LTS if missing, write `.nvmrc`
43
+ 1. `npm run opencode -- run --skill opensassi env-check.sh` — install git + Node.js LTS if missing, write `.nvmrc`
41
44
  2. `init install` — run platform-specific installer (cmake, nasm, gdb, ripgrep, perf, htop, etc.) or report none found
42
45
  3. `init flamegraph` — clone FlameGraph v1.0 to `scripts/FlameGraph/`
43
- 4. `npx @opensassi/opencode run --skill opensassi install-npm-deps.sh` — `npm install`
44
- 5. `npx @opensassi/opencode run --skill opensassi ensure-gitignore.sh` — append common patterns
46
+ 4. `npm run opencode -- run --skill opensassi install-npm-deps.sh` — `npm install`
47
+ 5. `npm run opencode -- run --skill opensassi ensure-gitignore.sh` — append common patterns
45
48
 
46
49
  ## Lexicon
47
50
 
@@ -112,6 +115,9 @@ If not → run full bootstrap:
112
115
  | | `commit` | — | Stage + commit all skill changes |
113
116
  | | `audit skills` | — | Validate all skill files for consistency |
114
117
  | **system-design-review** | *(no commands defined)* | — | Seven-expert panel audit of technical specs |
118
+ | **demo-video** | `plan` | — | Generate scene file from project outline |
119
+ | | `record` | — | Capture terminal + browser scenes as video clips |
120
+ | | `produce` | — | TTS audio, subtitles, ffmpeg assembly → final MP4 |
115
121
  | **daily-evaluation** | *(no commands defined)* | — | Aggregate session evaluations into dashboards |
116
122
  | **npx** | `npx <target> <cmd>` | `<target> <cmd>` | Run npx command in target directory |
117
123
  | | `npx . <cmd>` | `<cmd>` | Run npx command in current directory |
@@ -135,6 +141,7 @@ Common requests map to skill compositions. Load order: permanent base (tail) at
135
141
  | "optimize a hot function" | asm-optimizer → system-design+spec | `assess <entry>` → `iterative-optimize <entry>` |
136
142
  | "create a debugging todo" | todo → asm-optimizer → system-design+spec | `extract` → `propose-todo` → `save-todo` |
137
143
  | "save a note" | todo → system-design+spec | (treat free text as note → `extract` → `propose-todo` → `save-todo`) |
144
+ | "create a demo video" | demo-video → system-design+spec | `plan` → `record` → `produce` |
138
145
 
139
146
  ## Interpretation
140
147
 
@@ -151,7 +158,8 @@ Parse user text into skill compositions:
151
158
  - "todo", "note", "deferred", "remaining" → `todo` skill
152
159
  - "spec", "diagram", "design" → `system-design` skill
153
160
  - "session eval", "report card" → `session-evaluation` skill
154
- - "skill", "manage skills" → `skill-manager` skill
161
+ - "skill", "manage skills" → `skill-manager` skill
162
+ - "demo", "video", "record", "narration" → `demo-video` skill
155
163
 
156
164
  3. **Pattern matching** — Match multi-keyword phrases against Composition Patterns. If no direct match, compose by chaining relevant skills.
157
165
 
package/skills-index.json CHANGED
@@ -23,6 +23,11 @@
23
23
  "description": "Aggregate session evaluations into dashboards",
24
24
  "commands": []
25
25
  },
26
+ {
27
+ "name": "demo-video",
28
+ "description": "Produce narrated demo videos with multi-language subtitles — captures terminal TUI and browser, assembles via ffmpeg",
29
+ "commands": ["plan", "record", "produce"]
30
+ },
26
31
  {
27
32
  "name": "git",
28
33
  "description": "Rebase-based single-commit-per-session workflow",