@petercjl/seedaudiocli 0.1.1 → 0.1.2
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 +5 -1
- package/capabilities.json +11 -5
- package/npm/cli.mjs +7 -3
- package/npm/skills.mjs +7 -1
- package/npm/update.mjs +101 -0
- package/package.json +1 -1
- package/skills/seedaudiocli/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -57,10 +57,14 @@ npm install -g @petercjl/seedaudiocli@latest
|
|
|
57
57
|
seedaudiocli skill update --agent codex
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
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
61
|
|
|
62
62
|
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
63
|
|
|
64
64
|
## Development and evidence
|
|
65
65
|
|
|
66
66
|
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.
|
|
67
|
+
|
|
68
|
+
## Automatic updates
|
|
69
|
+
|
|
70
|
+
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
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"skill status",
|
|
28
28
|
"skill install",
|
|
29
29
|
"skill update",
|
|
30
|
-
"update
|
|
30
|
+
"update check",
|
|
31
|
+
"update install"
|
|
31
32
|
],
|
|
32
33
|
"config": {
|
|
33
34
|
"format": [
|
|
@@ -52,10 +53,15 @@
|
|
|
52
53
|
"secrets_in_output": false
|
|
53
54
|
},
|
|
54
55
|
"update": {
|
|
55
|
-
"trigger": "
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"configuration": "Outside package; preserved"
|
|
56
|
+
"trigger": "before-every-run",
|
|
57
|
+
"channel": "latest",
|
|
58
|
+
"automatic_install": true,
|
|
59
|
+
"configuration": "Outside package; preserved",
|
|
60
|
+
"offline": "keep-current",
|
|
61
|
+
"source_checkout": "git-managed",
|
|
62
|
+
"suite_install": "suite-managed",
|
|
63
|
+
"opt_out": "SEEDAUDIO_AUTO_UPDATE=0",
|
|
64
|
+
"managed_skills": "synchronized with backups; edited content preserved"
|
|
59
65
|
},
|
|
60
66
|
"generation": {
|
|
61
67
|
"provider": "EvoLink",
|
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
|
@@ -52,4 +52,4 @@ Use `@audio1` through `@audio3` in prompts to address the corresponding audio re
|
|
|
52
52
|
|
|
53
53
|
`seedaudiocli skill install --agent codex` installs a managed copy. For SealSeek supply the actual discovery directory with `--root SKILLS_DIRECTORY` or `SEALSEEK_SKILLS_DIR`; installation refuses to guess the host's layout. Existing unmanaged Skills are preserved. `skill status` reports ownership and freshness; `skill update` backs up managed files and preserves customized UI metadata. Edited managed content requires reconciliation.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Global npm installations check stable `latest` before every invocation and install a newer version before executing the command once. Managed Skills are synchronized with backups. Offline checks retain the installed version. `seedaudiocli update check` inspects availability; `update install` performs an explicit update. Set `SEEDAUDIO_AUTO_UPDATE=0` for a deliberately pinned run. Development checkouts remain Git-managed. Within taobao-ai-ops use `taobao-ai-ops component run seedaudiocli ...`; the suite owns component updates. Configuration and task history remain in user directories. Record recurring contract failures for a separately authorized package update.
|