channel-worker 1.0.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/bin/cli.js +105 -0
- package/lib/api-client.js +64 -0
- package/lib/daemon.js +84 -0
- package/lib/heartbeat.js +30 -0
- package/lib/job-executor.js +122 -0
- package/lib/job-poller.js +63 -0
- package/package.json +24 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), '.channel-worker');
|
|
8
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
+
|
|
10
|
+
function parseArgs(args) {
|
|
11
|
+
const result = {};
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
if (args[i].startsWith('--')) {
|
|
14
|
+
const key = args[i].slice(2);
|
|
15
|
+
const val = args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true;
|
|
16
|
+
result[key] = val;
|
|
17
|
+
if (val !== true) i++;
|
|
18
|
+
} else if (!result._cmd) {
|
|
19
|
+
result._cmd = args[i];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function loadConfig() {
|
|
26
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
27
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveConfig(config) {
|
|
33
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
34
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const args = parseArgs(process.argv.slice(2));
|
|
38
|
+
const cmd = args._cmd || 'start';
|
|
39
|
+
|
|
40
|
+
if (cmd === 'init') {
|
|
41
|
+
const config = {
|
|
42
|
+
worker_id: args.id || `worker-${os.hostname()}`,
|
|
43
|
+
api_url: args.api || 'http://localhost:3001',
|
|
44
|
+
max_concurrent: parseInt(args.concurrent || '2', 10),
|
|
45
|
+
nst_api_key: args['nst-key'] || '',
|
|
46
|
+
extension_path: args.extension || '',
|
|
47
|
+
};
|
|
48
|
+
saveConfig(config);
|
|
49
|
+
console.log(`[channel-worker] Config saved to ${CONFIG_FILE}`);
|
|
50
|
+
console.log(JSON.stringify(config, null, 2));
|
|
51
|
+
console.log('\nRun: channel-worker start');
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (cmd === 'start') {
|
|
56
|
+
// Merge CLI args over saved config
|
|
57
|
+
const saved = loadConfig();
|
|
58
|
+
const config = {
|
|
59
|
+
worker_id: args.id || saved.worker_id || `worker-${os.hostname()}`,
|
|
60
|
+
api_url: args.api || saved.api_url || 'http://localhost:3001',
|
|
61
|
+
max_concurrent: parseInt(args.concurrent || saved.max_concurrent || '2', 10),
|
|
62
|
+
nst_api_key: args['nst-key'] || saved.nst_api_key || '',
|
|
63
|
+
extension_path: args.extension || saved.extension_path || '',
|
|
64
|
+
verbose: !!args.verbose,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (!config.worker_id) {
|
|
68
|
+
console.error('[channel-worker] Error: --id required. Run: channel-worker init --id <name> --api <url>');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Save merged config for next time
|
|
73
|
+
saveConfig(config);
|
|
74
|
+
|
|
75
|
+
const { Daemon } = require('../lib/daemon');
|
|
76
|
+
const daemon = new Daemon(config);
|
|
77
|
+
daemon.start();
|
|
78
|
+
|
|
79
|
+
} else if (cmd === 'config') {
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
console.log(JSON.stringify(config, null, 2));
|
|
82
|
+
|
|
83
|
+
} else {
|
|
84
|
+
console.log(`
|
|
85
|
+
channel-worker — Channel Manager worker daemon
|
|
86
|
+
|
|
87
|
+
Commands:
|
|
88
|
+
init Configure worker (saves to ~/.channel-worker/config.json)
|
|
89
|
+
start Start the daemon
|
|
90
|
+
config Show current config
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
--id <name> Worker ID (unique name)
|
|
94
|
+
--api <url> Dashboard API URL (http://host:3001)
|
|
95
|
+
--concurrent <n> Max concurrent browser instances (default: 2)
|
|
96
|
+
--nst-key <key> Nstbrowser API key
|
|
97
|
+
--extension <path> Path to content-creator extension
|
|
98
|
+
--verbose Enable verbose logging
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
channel-worker init --id win-worker --api http://192.168.1.52:3001
|
|
102
|
+
channel-worker start
|
|
103
|
+
channel-worker start --id win-worker --api http://192.168.1.52:3001
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class ApiClient {
|
|
2
|
+
constructor(baseUrl) {
|
|
3
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async request(method, path, body = null) {
|
|
7
|
+
const url = `${this.baseUrl}${path}`;
|
|
8
|
+
const options = {
|
|
9
|
+
method,
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
};
|
|
12
|
+
if (body) options.body = JSON.stringify(body);
|
|
13
|
+
|
|
14
|
+
const res = await fetch(url, options);
|
|
15
|
+
const json = await res.json();
|
|
16
|
+
|
|
17
|
+
if (!json.success) {
|
|
18
|
+
throw new Error(json.message || `API error ${res.status}`);
|
|
19
|
+
}
|
|
20
|
+
return json.data;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Worker
|
|
24
|
+
async register(workerData) {
|
|
25
|
+
return this.request('POST', '/workers/register', workerData);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async heartbeat(workerId) {
|
|
29
|
+
return this.request('POST', '/workers/heartbeat', { worker_id: workerId });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Jobs
|
|
33
|
+
async getNextJob(workerId) {
|
|
34
|
+
return this.request('GET', `/jobs/next?worker_id=${workerId}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async claimJob(jobId, workerId) {
|
|
38
|
+
return this.request('PUT', `/jobs/${jobId}`, {
|
|
39
|
+
status: 'running',
|
|
40
|
+
worker_id: workerId,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async updateJob(jobId, data) {
|
|
45
|
+
return this.request('PUT', `/jobs/${jobId}`, data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Channel info
|
|
49
|
+
async getChannel(channelId) {
|
|
50
|
+
return this.request('GET', `/channels/${channelId}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getMasterChannel(masterId) {
|
|
54
|
+
return this.request('GET', `/masters/${masterId}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Settings
|
|
58
|
+
async getSetting(key) {
|
|
59
|
+
const data = await this.request('GET', `/settings/${key}`);
|
|
60
|
+
return data?.value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { ApiClient };
|
package/lib/daemon.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { ApiClient } = require('./api-client');
|
|
2
|
+
const { Heartbeat } = require('./heartbeat');
|
|
3
|
+
const { JobPoller } = require('./job-poller');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
class Daemon {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.api = new ApiClient(config.api_url);
|
|
10
|
+
this.heartbeat = new Heartbeat(this.api, config.worker_id);
|
|
11
|
+
this.poller = new JobPoller(this.api, config);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async start() {
|
|
15
|
+
console.log(`
|
|
16
|
+
╔══════════════════════════════════════╗
|
|
17
|
+
║ channel-worker v1.0.0 ║
|
|
18
|
+
╠══════════════════════════════════════╣
|
|
19
|
+
║ Worker ID: ${this.config.worker_id.padEnd(22)}║
|
|
20
|
+
║ API URL: ${this.config.api_url.padEnd(22)}║
|
|
21
|
+
║ Concurrent: ${String(this.config.max_concurrent).padEnd(22)}║
|
|
22
|
+
╚══════════════════════════════════════╝
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
// Register with dashboard
|
|
26
|
+
try {
|
|
27
|
+
await this.api.register({
|
|
28
|
+
worker_id: this.config.worker_id,
|
|
29
|
+
name: this.config.worker_id,
|
|
30
|
+
ip_address: this.getLocalIP(),
|
|
31
|
+
max_concurrent: this.config.max_concurrent,
|
|
32
|
+
});
|
|
33
|
+
console.log('[daemon] Registered with dashboard ✓');
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(`[daemon] Failed to register: ${err.message}`);
|
|
36
|
+
console.error('[daemon] Is the dashboard API running? Retrying in 10s...');
|
|
37
|
+
setTimeout(() => this.start(), 10000);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Start heartbeat
|
|
42
|
+
this.heartbeat.start();
|
|
43
|
+
console.log('[daemon] Heartbeat started (every 30s)');
|
|
44
|
+
|
|
45
|
+
// Start job polling
|
|
46
|
+
this.poller.start();
|
|
47
|
+
console.log('[daemon] Job poller started (every 5s)');
|
|
48
|
+
console.log('[daemon] Waiting for jobs...\n');
|
|
49
|
+
|
|
50
|
+
// Graceful shutdown
|
|
51
|
+
const shutdown = async () => {
|
|
52
|
+
console.log('\n[daemon] Shutting down...');
|
|
53
|
+
this.heartbeat.stop();
|
|
54
|
+
this.poller.stop();
|
|
55
|
+
|
|
56
|
+
// Mark offline
|
|
57
|
+
try {
|
|
58
|
+
await this.api.request('PUT', `/workers/${this.config.worker_id}`, {
|
|
59
|
+
status: 'offline',
|
|
60
|
+
});
|
|
61
|
+
} catch { /* ignore */ }
|
|
62
|
+
|
|
63
|
+
console.log('[daemon] Goodbye.');
|
|
64
|
+
process.exit(0);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
process.on('SIGINT', shutdown);
|
|
68
|
+
process.on('SIGTERM', shutdown);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getLocalIP() {
|
|
72
|
+
const interfaces = os.networkInterfaces();
|
|
73
|
+
for (const name of Object.keys(interfaces)) {
|
|
74
|
+
for (const iface of interfaces[name]) {
|
|
75
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
76
|
+
return iface.address;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return '127.0.0.1';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { Daemon };
|
package/lib/heartbeat.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Heartbeat {
|
|
2
|
+
constructor(api, workerId, intervalMs = 30000) {
|
|
3
|
+
this.api = api;
|
|
4
|
+
this.workerId = workerId;
|
|
5
|
+
this.intervalMs = intervalMs;
|
|
6
|
+
this.timer = null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
start() {
|
|
10
|
+
this.send(); // immediate first heartbeat
|
|
11
|
+
this.timer = setInterval(() => this.send(), this.intervalMs);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
stop() {
|
|
15
|
+
if (this.timer) {
|
|
16
|
+
clearInterval(this.timer);
|
|
17
|
+
this.timer = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async send() {
|
|
22
|
+
try {
|
|
23
|
+
await this.api.heartbeat(this.workerId);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`[heartbeat] Failed: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { Heartbeat };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
class JobExecutor {
|
|
2
|
+
constructor(api, config) {
|
|
3
|
+
this.api = api;
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async execute(job) {
|
|
8
|
+
const jobId = job._id;
|
|
9
|
+
console.log(`[executor] Starting job ${jobId} — ${job.flow_type} for channel ${job.channel_id}`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// 1. Get channel + master info
|
|
13
|
+
await this.updateStatus(jobId, 'sourcing');
|
|
14
|
+
const channel = await this.api.getChannel(job.channel_id);
|
|
15
|
+
const master = await this.api.getMasterChannel(channel.master_id);
|
|
16
|
+
const brandKit = master.brand_kit || {};
|
|
17
|
+
|
|
18
|
+
console.log(`[executor] Channel: ${channel.name} | Master: ${master.name} | Profile: ${channel.nst_profile_id}`);
|
|
19
|
+
|
|
20
|
+
// 2. Launch Nstbrowser profile
|
|
21
|
+
if (channel.nst_profile_id) {
|
|
22
|
+
await this.updateStatus(jobId, 'rendering', { progress: 10 });
|
|
23
|
+
await this.launchProfile(channel.nst_profile_id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 3. Execute based on flow type
|
|
27
|
+
switch (job.flow_type) {
|
|
28
|
+
case 'clone':
|
|
29
|
+
await this.executeClone(jobId, job, channel, brandKit);
|
|
30
|
+
break;
|
|
31
|
+
case 'generate':
|
|
32
|
+
await this.executeGenerate(jobId, job, channel, brandKit);
|
|
33
|
+
break;
|
|
34
|
+
case 'repurpose':
|
|
35
|
+
await this.executeRepurpose(jobId, job, channel, brandKit);
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
throw new Error(`Unknown flow type: ${job.flow_type}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 4. Post-process
|
|
42
|
+
await this.updateStatus(jobId, 'post_processing', { progress: 80 });
|
|
43
|
+
// FFmpeg captions, trim, etc — TODO
|
|
44
|
+
|
|
45
|
+
// 5. Publish to platforms
|
|
46
|
+
await this.updateStatus(jobId, 'publishing', { progress: 90 });
|
|
47
|
+
// YouTube/TikTok/FB upload — TODO
|
|
48
|
+
|
|
49
|
+
// 6. Done
|
|
50
|
+
await this.updateStatus(jobId, 'published', { progress: 100 });
|
|
51
|
+
console.log(`[executor] Job ${jobId} completed successfully`);
|
|
52
|
+
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`[executor] Job ${jobId} failed: ${err.message}`);
|
|
55
|
+
await this.api.updateJob(jobId, {
|
|
56
|
+
status: 'failed',
|
|
57
|
+
error_message: err.message,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async executeClone(jobId, job, channel, brandKit) {
|
|
63
|
+
// Step 1: Download source video
|
|
64
|
+
await this.updateStatus(jobId, 'sourcing', { progress: 20 });
|
|
65
|
+
console.log(`[executor:clone] Source: ${job.source_url}`);
|
|
66
|
+
// TODO: yt-dlp download
|
|
67
|
+
|
|
68
|
+
// Step 2: Transcribe
|
|
69
|
+
await this.updateStatus(jobId, 'scripting', { progress: 40 });
|
|
70
|
+
// TODO: Whisper/Groq transcribe
|
|
71
|
+
|
|
72
|
+
// Step 3: Rewrite script with brand voice
|
|
73
|
+
console.log(`[executor:clone] Rewriting with brand voice: ${brandKit.voice?.personality || 'default'}`);
|
|
74
|
+
// TODO: Claude API rewrite
|
|
75
|
+
|
|
76
|
+
// Step 4: Render new video
|
|
77
|
+
await this.updateStatus(jobId, 'rendering', { progress: 60 });
|
|
78
|
+
// TODO: Veo3 via extension auto-pilot OR Hailuo API
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async executeGenerate(jobId, job, channel, brandKit) {
|
|
82
|
+
// Step 1: Generate script
|
|
83
|
+
await this.updateStatus(jobId, 'scripting', { progress: 30 });
|
|
84
|
+
console.log(`[executor:generate] Generating script for: ${job.caption || 'topic from planner'}`);
|
|
85
|
+
// TODO: Claude API generate
|
|
86
|
+
|
|
87
|
+
// Step 2: Render video
|
|
88
|
+
await this.updateStatus(jobId, 'rendering', { progress: 60 });
|
|
89
|
+
// TODO: Veo3 via extension OR Hailuo API
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async executeRepurpose(jobId, job, channel, brandKit) {
|
|
93
|
+
// Step 1: Download long-form video
|
|
94
|
+
await this.updateStatus(jobId, 'sourcing', { progress: 20 });
|
|
95
|
+
// TODO: yt-dlp download
|
|
96
|
+
|
|
97
|
+
// Step 2: Extract clips
|
|
98
|
+
await this.updateStatus(jobId, 'rendering', { progress: 50 });
|
|
99
|
+
// TODO: Jiang-Clips or FunClip
|
|
100
|
+
|
|
101
|
+
// Step 3: Add captions
|
|
102
|
+
await this.updateStatus(jobId, 'post_processing', { progress: 70 });
|
|
103
|
+
// TODO: FFmpeg captions
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async launchProfile(profileId) {
|
|
107
|
+
// TODO: Nstbrowser SDK launch profile
|
|
108
|
+
console.log(`[executor] Launching Nstbrowser profile: ${profileId}`);
|
|
109
|
+
// const { wsEndpoint } = await nstApi.launchProfile(profileId);
|
|
110
|
+
// return wsEndpoint;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async updateStatus(jobId, status, extra = {}) {
|
|
114
|
+
const update = { status, ...extra };
|
|
115
|
+
if (extra.progress) {
|
|
116
|
+
update.metadata = { progress: extra.progress };
|
|
117
|
+
}
|
|
118
|
+
await this.api.updateJob(jobId, update);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { JobExecutor };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { JobExecutor } = require('./job-executor');
|
|
2
|
+
|
|
3
|
+
class JobPoller {
|
|
4
|
+
constructor(api, config) {
|
|
5
|
+
this.api = api;
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.executor = new JobExecutor(api, config);
|
|
8
|
+
this.running = new Set(); // job IDs currently executing
|
|
9
|
+
this.timer = null;
|
|
10
|
+
this.pollIntervalMs = 5000;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
start() {
|
|
14
|
+
console.log(`[poller] Started — max concurrent: ${this.config.max_concurrent}`);
|
|
15
|
+
this.poll(); // immediate first poll
|
|
16
|
+
this.timer = setInterval(() => this.poll(), this.pollIntervalMs);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
stop() {
|
|
20
|
+
if (this.timer) {
|
|
21
|
+
clearInterval(this.timer);
|
|
22
|
+
this.timer = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get availableSlots() {
|
|
27
|
+
return this.config.max_concurrent - this.running.size;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async poll() {
|
|
31
|
+
if (this.availableSlots <= 0) return;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const job = await this.api.getNextJob(this.config.worker_id);
|
|
35
|
+
if (!job) return; // no jobs in queue
|
|
36
|
+
|
|
37
|
+
const jobId = job._id;
|
|
38
|
+
if (this.running.has(jobId)) return; // already running
|
|
39
|
+
|
|
40
|
+
// Claim the job
|
|
41
|
+
await this.api.claimJob(jobId, this.config.worker_id);
|
|
42
|
+
this.running.add(jobId);
|
|
43
|
+
|
|
44
|
+
console.log(`[poller] Claimed job ${jobId} (${this.running.size}/${this.config.max_concurrent} slots used)`);
|
|
45
|
+
|
|
46
|
+
// Execute in background — don't await
|
|
47
|
+
this.executor.execute(job)
|
|
48
|
+
.finally(() => {
|
|
49
|
+
this.running.delete(jobId);
|
|
50
|
+
console.log(`[poller] Slot freed (${this.running.size}/${this.config.max_concurrent})`);
|
|
51
|
+
// Immediately try to pick next job
|
|
52
|
+
if (this.availableSlots > 0) this.poll();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (this.config.verbose) {
|
|
57
|
+
console.error(`[poller] Poll error: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { JobPoller };
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "channel-worker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
|
|
5
|
+
"main": "lib/daemon.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"channel-worker": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node bin/cli.js start",
|
|
15
|
+
"dev": "node bin/cli.js start --verbose"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"node-fetch": "^3.3.2"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|