@ericdisero/aurora-shared 0.1.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 (44) hide show
  1. package/README.md +9 -0
  2. package/dist/audio/ffmpeg.d.ts +21 -0
  3. package/dist/audio/ffmpeg.js +112 -0
  4. package/dist/audio/wav.d.ts +15 -0
  5. package/dist/audio/wav.js +159 -0
  6. package/dist/config.d.ts +14 -0
  7. package/dist/config.js +50 -0
  8. package/dist/db.d.ts +3 -0
  9. package/dist/db.js +121 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +8 -0
  12. package/dist/jobs.d.ts +45 -0
  13. package/dist/jobs.js +220 -0
  14. package/dist/operations/index.d.ts +12 -0
  15. package/dist/operations/index.js +848 -0
  16. package/dist/paths.d.ts +17 -0
  17. package/dist/paths.js +79 -0
  18. package/dist/providers/mvsep.d.ts +27 -0
  19. package/dist/providers/mvsep.js +112 -0
  20. package/dist/providers/suno.d.ts +89 -0
  21. package/dist/providers/suno.js +309 -0
  22. package/dist/sidecars.d.ts +20 -0
  23. package/dist/sidecars.js +109 -0
  24. package/dist/skills/content.d.ts +1 -0
  25. package/dist/skills/content.js +9 -0
  26. package/dist/split.d.ts +24 -0
  27. package/dist/split.js +162 -0
  28. package/dist/stack.d.ts +19 -0
  29. package/dist/stack.js +139 -0
  30. package/dist/storage/assets.d.ts +30 -0
  31. package/dist/storage/assets.js +103 -0
  32. package/dist/storage/projects.d.ts +12 -0
  33. package/dist/storage/projects.js +85 -0
  34. package/dist/storage/references.d.ts +10 -0
  35. package/dist/storage/references.js +54 -0
  36. package/dist/storage/stems.d.ts +13 -0
  37. package/dist/storage/stems.js +41 -0
  38. package/dist/types.d.ts +72 -0
  39. package/dist/types.js +5 -0
  40. package/package.json +51 -0
  41. package/skills/aurora-cost-discipline.md +31 -0
  42. package/skills/aurora-music-production.md +43 -0
  43. package/skills/aurora-split-and-stems.md +33 -0
  44. package/skills/aurora-suno-prompting.md +35 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @ericdisero/aurora-shared
2
+
3
+ Shared operations layer for the Aurora MCP server and CLI — the single tool surface (`ALL_OPERATIONS`), Aurora storage access (SQLite + project folders), Suno/MVSEP provider clients, the background-job model, split orchestration, and stack export.
4
+
5
+ Most users want **`@ericdisero/aurora-mcp-server`** (MCP) or **`@ericdisero/aurora-cli`** (`aurora` binary) instead — both depend on this package.
6
+
7
+ Full docs: [github.com/EricDisero/aurora-mcp](https://github.com/EricDisero/aurora-mcp).
8
+
9
+ MIT. Copyright Blueprint Online Learning Inc.
@@ -0,0 +1,21 @@
1
+ export declare function getFfmpegPath(): string;
2
+ export declare function runFfmpeg(args: string[]): Promise<void>;
3
+ /** Probe input duration in seconds via ffmpeg's `-i` banner (the installer
4
+ * ships no ffprobe). Returns null when no Duration line appears. */
5
+ export declare function probeDurationSeconds(inputPath: string): Promise<number | null>;
6
+ /** Standardize an arbitrary input to 44.1kHz stereo 32-bit float WAV — the
7
+ * single preprocessed format fed to MVSEP jobs and the `ee` phase-cancel
8
+ * reference (aurora's standardizeToWav, verbatim args). */
9
+ export declare function standardizeToWav(inputPath: string, outputPath: string): Promise<void>;
10
+ /** Whether the bundled ffmpeg build includes the rubberband filter. */
11
+ export declare function hasRubberband(): Promise<boolean>;
12
+ /** Split a tempo factor into a chain of atempo filters (each must be in [0.5, 2]). */
13
+ export declare function atempoChain(factor: number): string;
14
+ export type PitchEngine = 'varispeed' | 'rubberband' | 'asetrate+atempo';
15
+ export type AudioFormat = 'wav' | 'mp3';
16
+ /** Pitch-shift by `semitones` (+/-). Output locked to 44.1kHz.
17
+ * Default: varispeed (tempo shifts with pitch). preserveTempo: rubberband
18
+ * when available, else asetrate + atempo correction. */
19
+ export declare function pitchShift(inputPath: string, outputPath: string, semitones: number, preserveTempo: boolean, format: AudioFormat): Promise<PitchEngine>;
20
+ /** Convert to mp3 (320k CBR), keeping source sample rate / channel layout. */
21
+ export declare function convertToMp3(inputPath: string, outputPath: string): Promise<void>;
@@ -0,0 +1,112 @@
1
+ // ffmpeg operations — port of aurora tools/bridge/lib/ffmpeg-ops.ts +
2
+ // src/main/audio/ffmpeg.ts. Binary ships with this package via
3
+ // @ffmpeg-installer/ffmpeg (no Electron asar involved here).
4
+ import { spawn } from 'node:child_process';
5
+ import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
6
+ const SAMPLE_RATE = 44100;
7
+ export function getFfmpegPath() {
8
+ return ffmpegInstaller.path;
9
+ }
10
+ export function runFfmpeg(args) {
11
+ return new Promise((resolve, reject) => {
12
+ const proc = spawn(getFfmpegPath(), args, { windowsHide: true });
13
+ let stderr = '';
14
+ proc.stderr.on('data', (d) => {
15
+ stderr += d.toString();
16
+ });
17
+ proc.on('error', reject);
18
+ proc.on('close', (code) => {
19
+ if (code === 0)
20
+ resolve();
21
+ else
22
+ reject(new Error(`ffmpeg exited ${code}: ${stderr.slice(-2000)}`));
23
+ });
24
+ });
25
+ }
26
+ /** Probe input duration in seconds via ffmpeg's `-i` banner (the installer
27
+ * ships no ffprobe). Returns null when no Duration line appears. */
28
+ export function probeDurationSeconds(inputPath) {
29
+ return new Promise((resolve) => {
30
+ const proc = spawn(getFfmpegPath(), ['-hide_banner', '-i', inputPath], { windowsHide: true });
31
+ let stderr = '';
32
+ proc.stderr.on('data', (d) => {
33
+ stderr += d.toString();
34
+ });
35
+ proc.on('error', () => resolve(null));
36
+ proc.on('close', () => {
37
+ const m = /Duration:\s*(\d+):(\d{2}):(\d{2}(?:\.\d+)?)/.exec(stderr);
38
+ if (!m)
39
+ return resolve(null);
40
+ resolve(Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]));
41
+ });
42
+ });
43
+ }
44
+ /** Standardize an arbitrary input to 44.1kHz stereo 32-bit float WAV — the
45
+ * single preprocessed format fed to MVSEP jobs and the `ee` phase-cancel
46
+ * reference (aurora's standardizeToWav, verbatim args). */
47
+ export async function standardizeToWav(inputPath, outputPath) {
48
+ await runFfmpeg(['-y', '-i', inputPath, '-ac', '2', '-ar', '44100', '-c:a', 'pcm_f32le', outputPath]);
49
+ }
50
+ let rubberbandCache = null;
51
+ /** Whether the bundled ffmpeg build includes the rubberband filter. */
52
+ export async function hasRubberband() {
53
+ if (rubberbandCache !== null)
54
+ return rubberbandCache;
55
+ rubberbandCache = await new Promise((resolve) => {
56
+ const proc = spawn(getFfmpegPath(), ['-hide_banner', '-filters'], { windowsHide: true });
57
+ let out = '';
58
+ proc.stdout.on('data', (d) => {
59
+ out += d.toString();
60
+ });
61
+ proc.on('error', () => resolve(false));
62
+ proc.on('close', () => resolve(/\brubberband\b/.test(out)));
63
+ });
64
+ return rubberbandCache;
65
+ }
66
+ /** Split a tempo factor into a chain of atempo filters (each must be in [0.5, 2]). */
67
+ export function atempoChain(factor) {
68
+ const parts = [];
69
+ let f = factor;
70
+ while (f > 2.0) {
71
+ parts.push(2.0);
72
+ f /= 2.0;
73
+ }
74
+ while (f < 0.5) {
75
+ parts.push(0.5);
76
+ f /= 0.5;
77
+ }
78
+ parts.push(f);
79
+ return parts.map((p) => `atempo=${p.toFixed(6)}`).join(',');
80
+ }
81
+ function codecArgs(format) {
82
+ // wav mirrors the standardize format (44.1k float32); mp3 = 320k CBR.
83
+ return format === 'wav' ? ['-c:a', 'pcm_f32le'] : ['-c:a', 'libmp3lame', '-b:a', '320k'];
84
+ }
85
+ /** Pitch-shift by `semitones` (+/-). Output locked to 44.1kHz.
86
+ * Default: varispeed (tempo shifts with pitch). preserveTempo: rubberband
87
+ * when available, else asetrate + atempo correction. */
88
+ export async function pitchShift(inputPath, outputPath, semitones, preserveTempo, format) {
89
+ const ratio = 2 ** (semitones / 12);
90
+ const shiftedRate = Math.round(SAMPLE_RATE * ratio);
91
+ let engine;
92
+ let filter;
93
+ if (!preserveTempo) {
94
+ engine = 'varispeed';
95
+ filter = `aresample=${SAMPLE_RATE},asetrate=${shiftedRate},aresample=${SAMPLE_RATE}`;
96
+ }
97
+ else if (await hasRubberband()) {
98
+ engine = 'rubberband';
99
+ filter = `aresample=${SAMPLE_RATE},rubberband=pitch=${ratio.toFixed(8)}`;
100
+ }
101
+ else {
102
+ engine = 'asetrate+atempo';
103
+ filter = `aresample=${SAMPLE_RATE},asetrate=${shiftedRate},aresample=${SAMPLE_RATE},${atempoChain(1 / ratio)}`;
104
+ }
105
+ await runFfmpeg(['-y', '-i', inputPath, '-filter:a', filter, ...codecArgs(format), outputPath]);
106
+ return engine;
107
+ }
108
+ /** Convert to mp3 (320k CBR), keeping source sample rate / channel layout. */
109
+ export async function convertToMp3(inputPath, outputPath) {
110
+ await runFfmpeg(['-y', '-i', inputPath, ...codecArgs('mp3'), outputPath]);
111
+ }
112
+ //# sourceMappingURL=ffmpeg.js.map
@@ -0,0 +1,15 @@
1
+ export interface DecodedWav {
2
+ sampleRate: number;
3
+ /** Planar: one Float32Array per channel, each `frames` long. */
4
+ channels: Float32Array[];
5
+ frames: number;
6
+ }
7
+ export declare function decodeWavFile(path: string): Promise<DecodedWav>;
8
+ export declare function decodeWav(buf: Buffer): DecodedWav;
9
+ /** Encode planar Float32 channels to a 32-bit float WAV buffer. No clipping. */
10
+ export declare function encodeWavFloat32(channels: Float32Array[], sampleRate: number): Buffer;
11
+ export declare function encodeWavFloat32File(path: string, channels: Float32Array[], sampleRate: number): Promise<void>;
12
+ /** Element-wise A − (B + C + …). All inputs must share frame count + channel
13
+ * count (true by construction — same source, length-preserving models). Output
14
+ * keeps float headroom; no clipping/normalize. */
15
+ export declare function subtractWavs(minuend: DecodedWav, ...subtrahends: DecodedWav[]): DecodedWav;
@@ -0,0 +1,159 @@
1
+ // Minimal RIFF/WAVE reader + writer. No dependency — we only need enough to
2
+ // decode the WAVs MVSEP returns (PCM 16/24/32-bit int and 32-bit float) into
3
+ // planar Float32 channels, do sample-by-sample phase-cancellation, and re-encode
4
+ // 32-bit float WAV. Output may exceed ±1.0 — we keep full float headroom and do
5
+ // NOT clip or normalize (per mvsep-separation-contract.md Part B).
6
+ import { readFile, writeFile } from 'node:fs/promises';
7
+ const WAVE_FORMAT_PCM = 0x0001;
8
+ const WAVE_FORMAT_IEEE_FLOAT = 0x0003;
9
+ const WAVE_FORMAT_EXTENSIBLE = 0xfffe;
10
+ export async function decodeWavFile(path) {
11
+ const buf = await readFile(path);
12
+ return decodeWav(buf);
13
+ }
14
+ export function decodeWav(buf) {
15
+ if (buf.length < 12 || buf.toString('ascii', 0, 4) !== 'RIFF' || buf.toString('ascii', 8, 12) !== 'WAVE') {
16
+ throw new Error('Not a RIFF/WAVE file');
17
+ }
18
+ let fmtFound = false;
19
+ let audioFormat = WAVE_FORMAT_PCM;
20
+ let numChannels = 0;
21
+ let sampleRate = 0;
22
+ let bitsPerSample = 0;
23
+ let dataOffset = -1;
24
+ let dataLength = 0;
25
+ // Walk chunks starting after the 12-byte RIFF header.
26
+ let pos = 12;
27
+ while (pos + 8 <= buf.length) {
28
+ const chunkId = buf.toString('ascii', pos, pos + 4);
29
+ const chunkSize = buf.readUInt32LE(pos + 4);
30
+ const bodyStart = pos + 8;
31
+ if (chunkId === 'fmt ') {
32
+ audioFormat = buf.readUInt16LE(bodyStart);
33
+ numChannels = buf.readUInt16LE(bodyStart + 2);
34
+ sampleRate = buf.readUInt32LE(bodyStart + 4);
35
+ bitsPerSample = buf.readUInt16LE(bodyStart + 14);
36
+ // WAVE_FORMAT_EXTENSIBLE carries the real format in the subformat GUID's
37
+ // first 2 bytes (cbSize at +16, then valid bits/channel mask, then GUID).
38
+ if (audioFormat === WAVE_FORMAT_EXTENSIBLE && chunkSize >= 26) {
39
+ audioFormat = buf.readUInt16LE(bodyStart + 24);
40
+ }
41
+ fmtFound = true;
42
+ }
43
+ else if (chunkId === 'data') {
44
+ dataOffset = bodyStart;
45
+ // Clamp to the actual buffer (some encoders write a bogus 0xFFFFFFFF size).
46
+ dataLength = Math.min(chunkSize, buf.length - bodyStart);
47
+ }
48
+ // Chunks are word-aligned: an odd size is followed by a pad byte.
49
+ pos = bodyStart + chunkSize + (chunkSize % 2);
50
+ }
51
+ if (!fmtFound)
52
+ throw new Error('WAV missing fmt chunk');
53
+ if (dataOffset < 0)
54
+ throw new Error('WAV missing data chunk');
55
+ if (numChannels < 1)
56
+ throw new Error('WAV has no channels');
57
+ const bytesPerSample = bitsPerSample / 8;
58
+ const frameSize = bytesPerSample * numChannels;
59
+ const frames = Math.floor(dataLength / frameSize);
60
+ const channels = Array.from({ length: numChannels }, () => new Float32Array(frames));
61
+ const readSample = makeSampleReader(buf, audioFormat, bitsPerSample);
62
+ for (let f = 0; f < frames; f++) {
63
+ const frameBase = dataOffset + f * frameSize;
64
+ for (let ch = 0; ch < numChannels; ch++) {
65
+ channels[ch][f] = readSample(frameBase + ch * bytesPerSample);
66
+ }
67
+ }
68
+ return { sampleRate, channels, frames };
69
+ }
70
+ function makeSampleReader(buf, audioFormat, bitsPerSample) {
71
+ if (audioFormat === WAVE_FORMAT_IEEE_FLOAT) {
72
+ if (bitsPerSample === 32)
73
+ return (o) => buf.readFloatLE(o);
74
+ if (bitsPerSample === 64)
75
+ return (o) => buf.readDoubleLE(o);
76
+ throw new Error(`Unsupported float bit depth: ${bitsPerSample}`);
77
+ }
78
+ if (audioFormat === WAVE_FORMAT_PCM) {
79
+ if (bitsPerSample === 16)
80
+ return (o) => buf.readInt16LE(o) / 32768;
81
+ if (bitsPerSample === 24) {
82
+ return (o) => {
83
+ // 24-bit signed little-endian → sign-extend the high byte.
84
+ const v = buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16);
85
+ const signed = v & 0x800000 ? v - 0x1000000 : v;
86
+ return signed / 8388608;
87
+ };
88
+ }
89
+ if (bitsPerSample === 32)
90
+ return (o) => buf.readInt32LE(o) / 2147483648;
91
+ if (bitsPerSample === 8)
92
+ return (o) => (buf.readUInt8(o) - 128) / 128;
93
+ throw new Error(`Unsupported PCM bit depth: ${bitsPerSample}`);
94
+ }
95
+ throw new Error(`Unsupported WAV audio format: 0x${audioFormat.toString(16)}`);
96
+ }
97
+ /** Encode planar Float32 channels to a 32-bit float WAV buffer. No clipping. */
98
+ export function encodeWavFloat32(channels, sampleRate) {
99
+ const numChannels = channels.length;
100
+ if (numChannels === 0)
101
+ throw new Error('encodeWavFloat32: no channels');
102
+ const frames = channels[0].length;
103
+ const bytesPerSample = 4;
104
+ const blockAlign = numChannels * bytesPerSample;
105
+ const dataLength = frames * blockAlign;
106
+ const buf = Buffer.alloc(44 + dataLength);
107
+ buf.write('RIFF', 0, 'ascii');
108
+ buf.writeUInt32LE(36 + dataLength, 4);
109
+ buf.write('WAVE', 8, 'ascii');
110
+ buf.write('fmt ', 12, 'ascii');
111
+ buf.writeUInt32LE(16, 16); // PCM-style fmt chunk size
112
+ buf.writeUInt16LE(WAVE_FORMAT_IEEE_FLOAT, 20);
113
+ buf.writeUInt16LE(numChannels, 22);
114
+ buf.writeUInt32LE(sampleRate, 24);
115
+ buf.writeUInt32LE(sampleRate * blockAlign, 28); // byte rate
116
+ buf.writeUInt16LE(blockAlign, 32);
117
+ buf.writeUInt16LE(32, 34); // bits per sample
118
+ buf.write('data', 36, 'ascii');
119
+ buf.writeUInt32LE(dataLength, 40);
120
+ let offset = 44;
121
+ for (let f = 0; f < frames; f++) {
122
+ for (let ch = 0; ch < numChannels; ch++) {
123
+ buf.writeFloatLE(channels[ch][f], offset);
124
+ offset += bytesPerSample;
125
+ }
126
+ }
127
+ return buf;
128
+ }
129
+ export async function encodeWavFloat32File(path, channels, sampleRate) {
130
+ await writeFile(path, encodeWavFloat32(channels, sampleRate));
131
+ }
132
+ /** Element-wise A − (B + C + …). All inputs must share frame count + channel
133
+ * count (true by construction — same source, length-preserving models). Output
134
+ * keeps float headroom; no clipping/normalize. */
135
+ export function subtractWavs(minuend, ...subtrahends) {
136
+ const numChannels = minuend.channels.length;
137
+ const frames = minuend.frames;
138
+ for (const s of subtrahends) {
139
+ if (s.channels.length !== numChannels) {
140
+ throw new Error(`Channel-count mismatch in phase cancellation: ${s.channels.length} vs ${numChannels}`);
141
+ }
142
+ if (s.frames !== frames) {
143
+ throw new Error(`Frame-count mismatch in phase cancellation: ${s.frames} vs ${frames}`);
144
+ }
145
+ }
146
+ const out = Array.from({ length: numChannels }, (_, ch) => {
147
+ const result = new Float32Array(frames);
148
+ const base = minuend.channels[ch];
149
+ for (let i = 0; i < frames; i++) {
150
+ let v = base[i];
151
+ for (const s of subtrahends)
152
+ v -= s.channels[ch][i];
153
+ result[i] = v;
154
+ }
155
+ return result;
156
+ });
157
+ return { sampleRate: minuend.sampleRate, channels: out, frames };
158
+ }
159
+ //# sourceMappingURL=wav.js.map
@@ -0,0 +1,14 @@
1
+ export interface AuroraKeyConfig {
2
+ sunoApiKey?: string;
3
+ kieApiKey?: string;
4
+ mvsepApiKey?: string;
5
+ sunoApiBaseUrl?: string;
6
+ }
7
+ export declare function getConfigPath(): string;
8
+ export declare function readKeyConfig(): AuroraKeyConfig;
9
+ export declare function writeKeyConfig(partial: AuroraKeyConfig): AuroraKeyConfig;
10
+ export declare function getSunoKey(): string | undefined;
11
+ export declare function getKieKey(): string | undefined;
12
+ export declare function getSunoBaseUrlOverride(): string | undefined;
13
+ export declare function getMvsepKey(): string | undefined;
14
+ export declare function requireMvsepKey(): string;
package/dist/config.js ADDED
@@ -0,0 +1,50 @@
1
+ // Provider API-key resolution. Priority: environment variables (works with the
2
+ // MCP config `env` block and CI) → ~/.aurora/config.json (written by
3
+ // `aurora keys set`). Auth to Aurora itself: NONE — the app runs fully open
4
+ // (AUTH_ENABLED=false; slates-api device-code auth arrives with D4, out of
5
+ // scope here). These keys are the PROVIDER dev keys (dev-direct mode).
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ export function getConfigPath() {
10
+ return join(homedir(), '.aurora', 'config.json');
11
+ }
12
+ export function readKeyConfig() {
13
+ const path = getConfigPath();
14
+ if (!existsSync(path))
15
+ return {};
16
+ try {
17
+ return JSON.parse(readFileSync(path, 'utf-8'));
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ export function writeKeyConfig(partial) {
24
+ const merged = { ...readKeyConfig(), ...partial };
25
+ const dir = join(homedir(), '.aurora');
26
+ mkdirSync(dir, { recursive: true });
27
+ writeFileSync(getConfigPath(), JSON.stringify(merged, null, 2));
28
+ return merged;
29
+ }
30
+ export function getSunoKey() {
31
+ return process.env.SUNO_API_KEY || readKeyConfig().sunoApiKey;
32
+ }
33
+ export function getKieKey() {
34
+ return process.env.KIE_API_KEY || readKeyConfig().kieApiKey;
35
+ }
36
+ export function getSunoBaseUrlOverride() {
37
+ return process.env.SUNO_API_BASE_URL || readKeyConfig().sunoApiBaseUrl;
38
+ }
39
+ export function getMvsepKey() {
40
+ return process.env.MVSEP_API_KEY || readKeyConfig().mvsepApiKey;
41
+ }
42
+ export function requireMvsepKey() {
43
+ const key = getMvsepKey();
44
+ if (!key) {
45
+ throw new Error('MVSEP_API_KEY is not configured. Set the MVSEP_API_KEY environment variable ' +
46
+ '(e.g. in your MCP config "env" block) or run: aurora keys set --mvsep-api-key <key>');
47
+ }
48
+ return key;
49
+ }
50
+ //# sourceMappingURL=config.js.map
package/dist/db.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function getDb(): Database.Database;
3
+ export declare function closeDb(): void;
package/dist/db.js ADDED
@@ -0,0 +1,121 @@
1
+ // Aurora DB access without Electron — same better-sqlite3, same WAL pragmas,
2
+ // same migrations as aurora/src/main/database/. The MCP and the running app
3
+ // can coexist on this DB: WAL allows concurrent readers, busy_timeout waits
4
+ // out transient writer locks.
5
+ //
6
+ // SCHEMA LOCKSTEP RULE: this file mirrors aurora/src/main/database/migrations.ts
7
+ // at schema version 1. If the app migrates past v1, openDb() refuses to write
8
+ // with an "update your aurora-mcp packages" error instead of corrupting newer
9
+ // schema assumptions.
10
+ import Database from 'better-sqlite3';
11
+ import { randomUUID } from 'node:crypto';
12
+ import { mkdirSync } from 'node:fs';
13
+ import { getDbPath, getUserDataDir } from './paths.js';
14
+ const KNOWN_SCHEMA_VERSION = 1;
15
+ let db = null;
16
+ export function getDb() {
17
+ if (db)
18
+ return db;
19
+ mkdirSync(getUserDataDir(), { recursive: true });
20
+ db = new Database(getDbPath());
21
+ db.pragma('journal_mode = WAL');
22
+ db.pragma('foreign_keys = ON');
23
+ db.pragma('busy_timeout = 5000');
24
+ const version = db.pragma('user_version', { simple: true });
25
+ if (version > KNOWN_SCHEMA_VERSION) {
26
+ const path = getDbPath();
27
+ db.close();
28
+ db = null;
29
+ throw new Error(`Aurora database at ${path} is schema v${version}, newer than this tool understands (v${KNOWN_SCHEMA_VERSION}). ` +
30
+ 'Update the @ericdisero/aurora-* packages (npm i -g @ericdisero/aurora-cli@latest, or clear the npx cache).');
31
+ }
32
+ runMigrations(db);
33
+ return db;
34
+ }
35
+ export function closeDb() {
36
+ if (db) {
37
+ db.close();
38
+ db = null;
39
+ }
40
+ }
41
+ // Verbatim port of aurora/src/main/database/migrations.ts (schema v1).
42
+ function runMigrations(database) {
43
+ database.exec(`
44
+ CREATE TABLE IF NOT EXISTS projects (
45
+ id TEXT PRIMARY KEY,
46
+ name TEXT NOT NULL,
47
+ source TEXT NOT NULL CHECK(source IN ('generated', 'imported')),
48
+ audio_path TEXT NOT NULL,
49
+ gen_meta TEXT,
50
+ created_at INTEGER NOT NULL,
51
+ updated_at INTEGER NOT NULL
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS project_stems (
55
+ id TEXT PRIMARY KEY,
56
+ project_id TEXT NOT NULL,
57
+ stem_type TEXT NOT NULL CHECK(stem_type IN ('vocals','kick','snare','toms','hats','bass','ee')),
58
+ path TEXT NOT NULL,
59
+ origin TEXT NOT NULL CHECK(origin IN ('mvsep','synthesized')),
60
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
61
+ );
62
+
63
+ CREATE TABLE IF NOT EXISTS reference_tracks (
64
+ id TEXT PRIMARY KEY,
65
+ name TEXT NOT NULL,
66
+ audio_path TEXT NOT NULL,
67
+ cached_curve_path TEXT,
68
+ curve_status TEXT NOT NULL DEFAULT 'none'
69
+ CHECK(curve_status IN ('none','analyzing','cached','error')),
70
+ created_at INTEGER NOT NULL
71
+ );
72
+
73
+ CREATE INDEX IF NOT EXISTS idx_project_stems_project ON project_stems(project_id);
74
+ CREATE INDEX IF NOT EXISTS idx_projects_updated ON projects(updated_at DESC);
75
+ `);
76
+ const version = database.pragma('user_version', { simple: true });
77
+ if (version < 1) {
78
+ database.transaction(() => {
79
+ database.exec(`
80
+ ALTER TABLE projects ADD COLUMN dir_name TEXT;
81
+
82
+ CREATE TABLE IF NOT EXISTS project_assets (
83
+ id TEXT PRIMARY KEY,
84
+ project_id TEXT NOT NULL,
85
+ kind TEXT NOT NULL CHECK(kind IN ('generation','cover','import','reference','master')),
86
+ name TEXT NOT NULL,
87
+ path TEXT NOT NULL,
88
+ origin TEXT,
89
+ source_asset_id TEXT,
90
+ ref_id TEXT,
91
+ created_at INTEGER NOT NULL,
92
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
93
+ );
94
+ CREATE INDEX IF NOT EXISTS idx_project_assets_project ON project_assets(project_id);
95
+
96
+ ALTER TABLE project_stems ADD COLUMN asset_id TEXT;
97
+ DROP INDEX IF EXISTS idx_project_stems_unique;
98
+ CREATE INDEX IF NOT EXISTS idx_project_stems_asset ON project_stems(asset_id);
99
+ `);
100
+ const projects = database
101
+ .prepare('SELECT id, name, source, audio_path, gen_meta, created_at FROM projects')
102
+ .all();
103
+ const setDir = database.prepare('UPDATE projects SET dir_name = ? WHERE id = ?');
104
+ const insertAsset = database.prepare(`INSERT INTO project_assets (id, project_id, kind, name, path, origin, created_at)
105
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);
106
+ const linkStems = database.prepare('UPDATE project_stems SET asset_id = ? WHERE project_id = ?');
107
+ for (const p of projects) {
108
+ setDir.run(p.id, p.id);
109
+ if (p.audio_path) {
110
+ const assetId = randomUUID();
111
+ const kind = p.source === 'generated' ? 'generation' : 'import';
112
+ insertAsset.run(assetId, p.id, kind, p.name, p.audio_path, p.gen_meta, p.created_at);
113
+ linkStems.run(assetId, p.id);
114
+ }
115
+ }
116
+ database.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_project_stems_asset_unique ON project_stems(asset_id, stem_type);');
117
+ database.pragma('user_version = 1');
118
+ })();
119
+ }
120
+ }
121
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1,7 @@
1
+ export { ALL_OPERATIONS, type Operation, type OperationResult } from './operations/index.js';
2
+ export { SKILLS } from './skills/content.js';
3
+ export { getUserDataDir, getDbPath, getProjectsDirectory, getSettings } from './paths.js';
4
+ export { readKeyConfig, writeKeyConfig, getConfigPath, getSunoKey, getKieKey, getMvsepKey } from './config.js';
5
+ export { getDb, closeDb } from './db.js';
6
+ export { listJobs, loadJob } from './jobs.js';
7
+ export * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { ALL_OPERATIONS } from './operations/index.js';
2
+ export { SKILLS } from './skills/content.js';
3
+ export { getUserDataDir, getDbPath, getProjectsDirectory, getSettings } from './paths.js';
4
+ export { readKeyConfig, writeKeyConfig, getConfigPath, getSunoKey, getKieKey, getMvsepKey } from './config.js';
5
+ export { getDb, closeDb } from './db.js';
6
+ export { listJobs, loadJob } from './jobs.js';
7
+ export * from './types.js';
8
+ //# sourceMappingURL=index.js.map
package/dist/jobs.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { type SplitJobName } from './split.js';
2
+ export type JobKind = 'generate' | 'sounds' | 'cover' | 'split';
3
+ export interface JobManifest {
4
+ jobId: string;
5
+ kind: JobKind;
6
+ status: 'running' | 'done' | 'error';
7
+ error?: string;
8
+ createdAt: string;
9
+ updatedAt: string;
10
+ projectId: string;
11
+ /** Display-name base for landed assets. */
12
+ baseName: string;
13
+ /** Original op params, for traceability. */
14
+ params: Record<string, unknown>;
15
+ provider: {
16
+ /** generate / sounds / cover */
17
+ taskId?: string;
18
+ sourceAssetId?: string | null;
19
+ /** split */
20
+ assetId?: string;
21
+ stemsDir?: string;
22
+ hashes?: Record<SplitJobName, string>;
23
+ };
24
+ /** Per-sub-unit idempotency flags (split job names / 'assets'). */
25
+ landed: Record<string, boolean>;
26
+ /** DB ids of landed assets. */
27
+ assetIds: string[];
28
+ /** Landed stems (type + path). */
29
+ stems: Array<{
30
+ stemType: string;
31
+ path: string;
32
+ }>;
33
+ lastStatus?: string;
34
+ /** Listenable mid-generation preview URLs (expire server-side — never persist
35
+ * as asset paths; they're a head start, not the product). */
36
+ streamUrls?: string[];
37
+ stage: string;
38
+ }
39
+ export declare function saveJob(m: JobManifest): Promise<void>;
40
+ export declare function loadJob(jobId: string): Promise<JobManifest | null>;
41
+ export declare function listJobs(): Promise<JobManifest[]>;
42
+ export declare function newJobManifest(kind: JobKind, jobId: string, projectId: string, baseName: string, params: Record<string, unknown>, provider: JobManifest['provider']): JobManifest;
43
+ /** Advance a running job by ONE provider poll, landing whatever is ready.
44
+ * Idempotent and serialized per jobId — the status op can hit it repeatedly. */
45
+ export declare function advanceJob(m: JobManifest): Promise<JobManifest>;