@petercjl/seedaudiocli 0.1.1 → 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.
- package/README.md +14 -4
- package/capabilities.json +22 -9
- package/npm/cli.mjs +7 -3
- package/npm/skills.mjs +7 -1
- package/npm/update.mjs +101 -0
- package/package.json +1 -1
- package/seedaudiocli/__init__.py +9 -1
- package/seedaudiocli/cli.py +102 -108
- package/seedaudiocli/config.py +15 -44
- package/seedaudiocli/jobs.py +118 -30
- package/seedaudiocli/official.py +62 -0
- package/seedaudiocli/payloads.py +104 -82
- package/seedaudiocli.config.example.json +2 -2
- package/seedaudiocli.env.example +2 -2
- package/skills/seedaudiocli/SKILL.md +16 -10
- package/skills/seedaudiocli/capabilities.json +7 -1
- package/seedaudiocli/evolink.py +0 -149
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @petercjl/seedaudiocli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Volcengine Seed Audio CLI with a portable companion Skill. Requires Node.js 22+ and Python 3.9+ with SSL; no Python third-party packages are needed.
|
|
4
4
|
|
|
5
5
|
## Install and configure
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ Credentials persist outside the npm installation:
|
|
|
27
27
|
|
|
28
28
|
macOS/Linux files use mode 0600. Windows relies on the user's profile directory ACL; administrators should keep that directory private. Import validates local structure and does not make a paid API call. `auth status` reports local availability, not remote key validity. Environment variables override saved configuration. Network errors do not clear credentials.
|
|
29
29
|
|
|
30
|
-
An existing configuration requires `--replace`; replacement creates a recoverable sibling backup before atomic write. Backups contain credentials too. Keep them private. Package uninstall/reinstall preserves configuration and task history.
|
|
30
|
+
An existing configuration requires `--replace`; replacement creates a recoverable sibling backup before atomic write. Backups contain credentials too. Keep them private. Package uninstall/reinstall preserves configuration and task history. Official env configurations are supported. To migrate an existing installation, import the new official configuration with `--replace`. Relay endpoints and relay model IDs are rejected. Obtain a Doubao Speech API key with Seed Audio access; an Ark API key is a different credential.
|
|
31
31
|
|
|
32
32
|
## Generate and recover
|
|
33
33
|
|
|
@@ -37,7 +37,13 @@ seedaudiocli generate text --prompt "Generate five seconds of instrumental piano
|
|
|
37
37
|
seedaudiocli task wait TASK_ID --download ./audio
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Actual generation can incur provider charges.
|
|
40
|
+
Actual generation can incur provider charges. The official API returns audio synchronously. The CLI records a local ID before its single POST, then saves the response and audio privately. `task get`, `task wait` and `task download` recover saved local results without network calls. There is no remote task polling. A timeout or interrupted submission is marked unknown; inspect its local record and provider billing evidence before explicitly authorizing another attempt with `--force`. A repeated successful request reuses the cached result.
|
|
41
|
+
|
|
42
|
+
The fixed endpoint is `https://openspeech.bytedance.com/api/v3/tts/create`, using `X-Api-Key` and model `seed-audio-1.0`. Prompts accept 1–3000 characters. Use `--audio-file` for local recordings or `--audio-reference` for preset speakers/public URLs (up to three total); address them with `@音频1` through `@音频3`. Use one `--image-file` or `--image-url` for image guidance. Local files are encoded directly in the official request. References are limited to 10 MiB each; audio must be at most 30 seconds. Audio and image modes are exclusive. `--speech-rate` and `--loudness-rate` are integers from -50 to 100 (0 unchanged), and `--pitch-rate` is -12 to 12. `--subtitle` saves returned subtitle data next to the audio.
|
|
43
|
+
|
|
44
|
+
Outputs support mp3, wav, pcm and ogg_opus. Select compatible sample rates with the CLI help: mp3 excludes 40000 Hz and ogg_opus requires 48000 Hz. Output files are never silently overwritten. Old history remains on disk; official records are stored separately.
|
|
45
|
+
|
|
46
|
+
Protocol reference: [Volcengine Seed Audio HTTP documentation](https://docs.volcengine.com/docs/DoubaoVoice/audio-generation-http?lang=zh).
|
|
41
47
|
|
|
42
48
|
## Agent installation
|
|
43
49
|
|
|
@@ -57,10 +63,14 @@ npm install -g @petercjl/seedaudiocli@latest
|
|
|
57
63
|
seedaudiocli skill update --agent codex
|
|
58
64
|
```
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
Global installs check and install stable latest before every run and synchronize previously registered managed Skills. Use `update check` to inspect availability and `update install` to apply it immediately. For a deliberate rollback, set `SEEDAUDIO_AUTO_UPDATE=0`, install the prior exact npm version and synchronize Skills. Configuration and history remain unchanged.
|
|
61
67
|
|
|
62
68
|
Python discovery checks an explicit `SEEDAUDIO_PYTHON`, configured Agent runtime directories and platform commands. `SEEDAUDIO_RUNTIME_ROOT` can point to a managed runtime root. A missing or broken runtime produces `PYTHON_REQUIRED` with installation guidance.
|
|
63
69
|
|
|
64
70
|
## Development and evidence
|
|
65
71
|
|
|
66
72
|
Run `npm test` for isolated CLI, configuration and lifecycle tests, and `npm pack --dry-run` to inspect published contents. Tests use synthetic keys and do not submit paid jobs. GitHub QA covers macOS, Windows and Linux package entry points. Host Skill discovery and paid audio output require separate runtime evidence; adapter status remains `implemented` until those checks are recorded. Publishing uses the committed GitHub Actions OIDC workflow after QA. Public distribution does not include credentials.
|
|
73
|
+
|
|
74
|
+
## Automatic updates
|
|
75
|
+
|
|
76
|
+
Global npm installs check stable latest before every command, install newer releases and sync managed Skills before running the requested command once. No TTL cache is used. Offline checks keep the installed version; updates back up the package for rollback. Configuration and task history remain outside the package. Use `seedaudiocli update check` or `seedaudiocli update install`. For a deliberately pinned run set `SEEDAUDIO_AUTO_UPDATE=0`. Development checkouts are Git-managed; taobao-ai-ops owns suite component updates.
|
package/capabilities.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"task get",
|
|
21
21
|
"task wait",
|
|
22
22
|
"task url",
|
|
23
|
+
"task download",
|
|
23
24
|
"task jobs",
|
|
24
25
|
"payload validate",
|
|
25
26
|
"payload submit",
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
"skill status",
|
|
28
29
|
"skill install",
|
|
29
30
|
"skill update",
|
|
30
|
-
"update
|
|
31
|
+
"update check",
|
|
32
|
+
"update install"
|
|
31
33
|
],
|
|
32
34
|
"config": {
|
|
33
35
|
"format": [
|
|
@@ -49,19 +51,30 @@
|
|
|
49
51
|
"import": "seedaudiocli config import FILE [--replace]",
|
|
50
52
|
"side_effects": "Local file write, backup on replacement; no remote call",
|
|
51
53
|
"missing": "CONFIG_REQUIRED",
|
|
52
|
-
"secrets_in_output": false
|
|
54
|
+
"secrets_in_output": false,
|
|
55
|
+
"migration": "Import official configuration with --replace; backup is automatic; relay endpoints/models are rejected."
|
|
53
56
|
},
|
|
54
57
|
"update": {
|
|
55
|
-
"trigger": "
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"configuration": "Outside package; preserved"
|
|
58
|
+
"trigger": "before-every-run",
|
|
59
|
+
"channel": "latest",
|
|
60
|
+
"automatic_install": true,
|
|
61
|
+
"configuration": "Outside package; preserved",
|
|
62
|
+
"offline": "keep-current",
|
|
63
|
+
"source_checkout": "git-managed",
|
|
64
|
+
"suite_install": "suite-managed",
|
|
65
|
+
"opt_out": "SEEDAUDIO_AUTO_UPDATE=0",
|
|
66
|
+
"managed_skills": "synchronized with backups; edited content preserved"
|
|
59
67
|
},
|
|
60
68
|
"generation": {
|
|
61
|
-
"provider": "
|
|
62
|
-
"
|
|
69
|
+
"provider": "Volcengine",
|
|
70
|
+
"endpoint": "https://openspeech.bytedance.com/api/v3/tts/create",
|
|
71
|
+
"authentication": "X-Api-Key (Doubao Speech)",
|
|
72
|
+
"model": "seed-audio-1.0",
|
|
73
|
+
"mode": "synchronous",
|
|
63
74
|
"paid": true,
|
|
64
75
|
"submit_retry": false,
|
|
65
|
-
"
|
|
76
|
+
"local_references": true,
|
|
77
|
+
"remote_polling": false,
|
|
78
|
+
"recovery": "Persist local record before POST; save response and audio; export locally by record ID. Unknown outcomes require inspection and explicit authorization before --force."
|
|
66
79
|
}
|
|
67
80
|
}
|
package/npm/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import { beforeRun, checkUpdate, installUpdate } from './update.mjs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { root, pkg, fail, findPython, runPython } from './runtime.mjs';
|
|
5
6
|
import { source, targetPath, status, install } from './skills.mjs';
|
|
@@ -8,7 +9,8 @@ const args = process.argv.slice(2);
|
|
|
8
9
|
const print = value => console.log(JSON.stringify(value, null, 2));
|
|
9
10
|
const flag = name => { const i = args.indexOf(name); return i < 0 ? undefined : args[i + 1]; };
|
|
10
11
|
try {
|
|
11
|
-
if (
|
|
12
|
+
if (await beforeRun(args)) { /* child completed */ }
|
|
13
|
+
else if (['version', '--version'].includes(args[0])) console.log(`seedaudiocli ${pkg.version}`);
|
|
12
14
|
else if (args[0] === 'capabilities') print(JSON.parse(fs.readFileSync(path.join(root, 'capabilities.json'), 'utf8')));
|
|
13
15
|
else if (args[0] === 'doctor') {
|
|
14
16
|
const python = findPython();
|
|
@@ -26,9 +28,11 @@ try {
|
|
|
26
28
|
else throw fail('INVALID_COMMAND', 'Use skill source|status|install|update.');
|
|
27
29
|
}
|
|
28
30
|
} else if (args[0] === 'update') {
|
|
29
|
-
|
|
31
|
+
const check = await checkUpdate();
|
|
32
|
+
if (args[1] === 'install' && check.updateAvailable) print(installUpdate(check));
|
|
33
|
+
else print({ ...check, policy: 'before-every-run', opt_out: 'SEEDAUDIO_AUTO_UPDATE=0' });
|
|
30
34
|
} else if (!args.length || ['--help', '-h'].includes(args[0])) {
|
|
31
|
-
console.log('seedaudiocli: setup | config import FILE [--replace] | auth status | generate | task | payload\nPackage: version | capabilities --json | doctor --json | skill source/status/install/update [--agent codex|sealseek] [--root SKILLS_DIRECTORY] | update
|
|
35
|
+
console.log('seedaudiocli: setup | config import FILE [--replace] | auth status | generate | task | payload\nPackage: version | capabilities --json | doctor --json | skill source/status/install/update [--agent codex|sealseek] [--root SKILLS_DIRECTORY] | update check/install\nRuntime: Node.js 22+, Python 3.9+ (SEEDAUDIO_PYTHON optional).');
|
|
32
36
|
process.exitCode = runPython(['--help']).status || 0;
|
|
33
37
|
} else {
|
|
34
38
|
const result = runPython(args);
|
package/npm/skills.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
+
import { stateRoot } from './update.mjs';
|
|
5
6
|
import { root, pkg, fail } from './runtime.mjs';
|
|
6
7
|
|
|
7
8
|
export const source = path.join(root, 'skills', 'seedaudiocli');
|
|
@@ -46,6 +47,10 @@ export function status(target) {
|
|
|
46
47
|
}
|
|
47
48
|
export function install(target) {
|
|
48
49
|
const state = status(target);
|
|
50
|
+
const registry = path.join(stateRoot(), 'skill-targets');
|
|
51
|
+
fs.mkdirSync(registry, { recursive: true });
|
|
52
|
+
const registration = path.join(registry, crypto.createHash('sha256').update(target).digest('hex') + '.json');
|
|
53
|
+
if (!exists(registration)) fs.writeFileSync(registration, JSON.stringify({ target }), { flag: 'wx', mode: 0o600 });
|
|
49
54
|
if (state.installed && !state.managed) throw fail('SKILL_UNMANAGED', 'An existing user-owned Skill occupies this target; preserve it and choose a new root or explicitly migrate it.');
|
|
50
55
|
if (state.edited) throw fail('SKILL_EDITED', 'Managed Skill files contain local edits; preserve and reconcile those edits before updating.');
|
|
51
56
|
if (state.current && state.version === pkg.version) return { ...state, record: undefined, ok: true, changed: false };
|
|
@@ -60,7 +65,8 @@ export function install(target) {
|
|
|
60
65
|
let backup;
|
|
61
66
|
try {
|
|
62
67
|
if (state.installed) {
|
|
63
|
-
backup =
|
|
68
|
+
backup = path.join(stateRoot(), 'skill-backups', crypto.randomUUID());
|
|
69
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
64
70
|
fs.cpSync(target, backup, { recursive: true, errorOnExist: true, force: false });
|
|
65
71
|
} else fs.mkdirSync(target);
|
|
66
72
|
// In-place updates support hosts holding a directory handle open. Backup is recoverable.
|
package/npm/update.mjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { root, pkg, fail } from './runtime.mjs';
|
|
7
|
+
|
|
8
|
+
export const stateRoot = () => process.env.SEEDAUDIO_STATE_DIR || (process.platform === 'win32' ? path.join(process.env.LOCALAPPDATA || os.homedir(), 'seedaudiocli') : path.join(os.homedir(), '.local', 'state', 'seedaudiocli'));
|
|
9
|
+
export function newer(a, b) {
|
|
10
|
+
if (!/^\d+\.\d+\.\d+$/.test(a)) return false;
|
|
11
|
+
const x=a.split('.').map(Number), y=b.split('.').map(Number);
|
|
12
|
+
for(let i=0;i<3;i++) { if(x[i]!==y[i]) return x[i]>y[i]; }
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export function globalPrefix(location=root, platform=process.platform) {
|
|
16
|
+
const modules=path.dirname(path.dirname(location));
|
|
17
|
+
if(path.basename(modules)!=='node_modules' || fs.existsSync(path.join(location,'.git'))) return null;
|
|
18
|
+
if(platform==='win32') return path.dirname(modules);
|
|
19
|
+
return path.basename(path.dirname(modules))==='lib' ? path.dirname(path.dirname(modules)) : null;
|
|
20
|
+
}
|
|
21
|
+
export async function checkUpdate(registry=fetch) {
|
|
22
|
+
try {
|
|
23
|
+
const r=await registry(`https://registry.npmjs.org/${encodeURIComponent(pkg.name)}/latest`,{signal:AbortSignal.timeout(8000)});
|
|
24
|
+
if(!r.ok) throw Error('Registry unavailable');
|
|
25
|
+
const data=await r.json();
|
|
26
|
+
if(data.name!==pkg.name || !/^\d+\.\d+\.\d+$/.test(data.version)) throw Error('Invalid stable package');
|
|
27
|
+
return {ok:true,current:pkg.version,available:data.version,updateAvailable:newer(data.version,pkg.version),tag:'latest'};
|
|
28
|
+
} catch { return {ok:false,code:'REGISTRY_UNAVAILABLE',current:pkg.version,updateAvailable:false,offline:'keep-current'}; }
|
|
29
|
+
}
|
|
30
|
+
function npm(args) {
|
|
31
|
+
let cli=process.env.npm_execpath;
|
|
32
|
+
if(!cli?.endsWith('npm-cli.js')) {
|
|
33
|
+
const found=spawnSync(process.platform==='win32'?'where':'which',[process.platform==='win32'?'npm.cmd':'npm'],{encoding:'utf8',timeout:10000});
|
|
34
|
+
const launcher=found.stdout?.trim().split(/\r?\n/)[0];
|
|
35
|
+
cli=launcher && (process.platform==='win32' ? path.join(path.dirname(launcher),'node_modules','npm','bin','npm-cli.js') : fs.realpathSync(launcher));
|
|
36
|
+
}
|
|
37
|
+
if(!cli || !fs.existsSync(cli)) throw fail('NPM_UNAVAILABLE','npm is required for updates.');
|
|
38
|
+
const r=spawnSync(process.execPath,[cli,...args],{encoding:'utf8',timeout:180000});
|
|
39
|
+
if(r.status!==0) throw fail('UPDATE_FAILED','npm update failed; check npm connectivity and installation permissions.');
|
|
40
|
+
return r.stdout;
|
|
41
|
+
}
|
|
42
|
+
export function installUpdate(check, services={}) {
|
|
43
|
+
const prefix=services.prefix || globalPrefix();
|
|
44
|
+
if(!prefix) throw fail('UPDATE_OWNER_REQUIRED','This installation is managed by its source checkout or parent package.');
|
|
45
|
+
const state=stateRoot(); fs.mkdirSync(state,{recursive:true});
|
|
46
|
+
const lock=path.join(state,'update.lock');
|
|
47
|
+
try { fs.mkdirSync(lock); } catch { throw fail('UPDATE_BUSY','Another update is running. Retry this command after it finishes.'); }
|
|
48
|
+
const run=services.npm || npm, spawn=services.spawn || spawnSync;
|
|
49
|
+
const backup=path.join(state,'package-backups',crypto.randomUUID());
|
|
50
|
+
const targets=[];
|
|
51
|
+
try {
|
|
52
|
+
// Recheck the live package under the lock; another process may have updated it.
|
|
53
|
+
const live=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
|
|
54
|
+
if(!newer(check.available,live.version)) return {ok:true,changed:false,version:live.version};
|
|
55
|
+
fs.mkdirSync(backup,{recursive:true});
|
|
56
|
+
const archive=JSON.parse(run(['pack',root,'--ignore-scripts','--pack-destination',backup,'--json']))[0];
|
|
57
|
+
const registered=path.join(state,'skill-targets');
|
|
58
|
+
const known=[path.join(process.env.CODEX_HOME || path.join(os.homedir(),'.codex'),'skills','seedaudiocli')];
|
|
59
|
+
for(const f of (fs.existsSync(registered) ? fs.readdirSync(registered) : [])) {
|
|
60
|
+
try { known.push(JSON.parse(fs.readFileSync(path.join(registered,f),'utf8')).target); } catch { /* invalid registration */ }
|
|
61
|
+
}
|
|
62
|
+
for(const target of new Set(known)) {
|
|
63
|
+
try { if(JSON.parse(fs.readFileSync(path.join(target,'.seedaudiocli-managed.json'),'utf8')).package===pkg.name) targets.push(target); } catch { /* unmanaged or absent */ }
|
|
64
|
+
}
|
|
65
|
+
const entry=path.join(root,'npm','cli.mjs');
|
|
66
|
+
const sync=target=>spawn(process.execPath,[entry,'skill','update','--root',path.dirname(target)],{encoding:'utf8',timeout:30000,env:{...process.env,SEEDAUDIO_AUTO_UPDATE:'0'}});
|
|
67
|
+
let mutated=false;
|
|
68
|
+
try {
|
|
69
|
+
mutated=true;
|
|
70
|
+
run(['install','--global','--prefix',prefix,'--ignore-scripts','--no-audit','--no-fund',`${pkg.name}@${check.available}`]);
|
|
71
|
+
const version=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8')).version;
|
|
72
|
+
if(version!==check.available) throw fail('UPDATE_VERIFY_FAILED','Installed package version mismatch.');
|
|
73
|
+
for(const target of targets) if(sync(target).status!==0) throw fail('SKILL_SYNC_FAILED','A managed Skill needs reconciliation.');
|
|
74
|
+
return {ok:true,changed:true,from:pkg.version,version,backup:path.join(backup,archive.filename)};
|
|
75
|
+
} catch(e) {
|
|
76
|
+
if(mutated) {
|
|
77
|
+
try { run(['install','--global','--prefix',prefix,'--ignore-scripts',path.join(backup,archive.filename)]); }
|
|
78
|
+
catch { throw fail('ROLLBACK_REQUIRED','Automatic recovery failed; restore the package archive before running commands.'); }
|
|
79
|
+
for(const target of targets) if(sync(target).status!==0) throw fail('ROLLBACK_SYNC_REQUIRED','Package restored; reconcile edited Skill files before updating.');
|
|
80
|
+
}
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
} finally { fs.rmdirSync(lock); }
|
|
84
|
+
}
|
|
85
|
+
export async function beforeRun(args, services={}) {
|
|
86
|
+
if(process.env.SEEDAUDIO_AUTO_UPDATE==='0' || !globalPrefix() || args[0]==='update') return false;
|
|
87
|
+
const check=await (services.check || checkUpdate)();
|
|
88
|
+
if(!check.ok) console.error('[seedaudiocli] Update check unavailable; using installed version.');
|
|
89
|
+
if(!check.updateAvailable) return false;
|
|
90
|
+
try {
|
|
91
|
+
const result=(services.install || installUpdate)(check);
|
|
92
|
+
console.error(`[seedaudiocli] Using latest ${result.version}.`);
|
|
93
|
+
} catch(e) {
|
|
94
|
+
if(['UPDATE_BUSY','ROLLBACK_SYNC_REQUIRED','ROLLBACK_REQUIRED'].includes(e.code)) throw e;
|
|
95
|
+
console.error(`[seedaudiocli] ${e.code || 'UPDATE_FAILED'}; using restored installed version.`);
|
|
96
|
+
}
|
|
97
|
+
// Delegate once only after update/rollback; never replay a business request.
|
|
98
|
+
const r=spawnSync(process.execPath,[path.join(root,'npm','cli.mjs'),...args],{stdio:'inherit',env:{...process.env,SEEDAUDIO_AUTO_UPDATE:'0'}});
|
|
99
|
+
process.exitCode=r.status??1;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
package/package.json
CHANGED
package/seedaudiocli/__init__.py
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"""Version follows the canonical npm package metadata."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = json.loads((Path(__file__).resolve().parent.parent / "package.json").read_text(encoding="utf-8"))["version"]
|
|
7
|
+
except (OSError, ValueError, KeyError):
|
|
8
|
+
from importlib.metadata import version
|
|
9
|
+
__version__ = version("seedaudiocli")
|