pushnetgo-cli 1.0.3 → 1.0.4

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.
@@ -2,16 +2,21 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const { resolveApiKey } = require('../lib/auth');
5
- const { uploadFile, saveTrack, downloadUrlToFile } = require('../lib/uploader');
5
+ const { hasServiceAccount, syncMusicHttp } = require('../lib/http-client');
6
6
 
7
7
  async function syncMusic(options) {
8
8
  const projectId = options.project || 'default';
9
9
  const dir = options.dir;
10
10
  const url = options.url;
11
-
12
- await resolveApiKey(options.key);
11
+ const key = await resolveApiKey(options.key);
13
12
 
14
13
  if (dir) {
14
+ if (!hasServiceAccount()) {
15
+ console.error('Local directory upload requires Firebase service account.');
16
+ console.error('Use --url instead: pushnetgo-cli sync-music --url "https://..."');
17
+ process.exit(1);
18
+ }
19
+ const { uploadFile, saveTrack } = require('../lib/uploader');
15
20
  const files = fs.readdirSync(dir)
16
21
  .filter(f => f.endsWith('.mp3'))
17
22
  .map(f => path.join(dir, f));
@@ -40,23 +45,32 @@ async function syncMusic(options) {
40
45
  }
41
46
 
42
47
  if (url) {
43
- console.log(`Download: ${url}`);
44
- const tmpDir = os.tmpdir();
45
- const tmpFile = path.join(tmpDir, `mureka_${Date.now()}.mp3`);
46
- await downloadUrlToFile(url, tmpFile);
47
- console.log(`Downloaded to: ${tmpFile}`);
48
-
49
- const trackId = `mureka_${Date.now()}`;
50
- const artist = 'Mureka AI';
51
- const title = `Generated ${new Date().toISOString().slice(0, 10)}`;
52
-
53
- process.stdout.write('Uploading...');
54
- const downloadUrl = await uploadFile(tmpFile, `music_files/${trackId}.mp3`);
55
- await saveTrack({ trackId, title, artist, downloadUrl, projectId });
56
- console.log(' OK');
57
-
58
- try { fs.unlinkSync(tmpFile); } catch (_) {}
59
- console.log(`Mureka track published to project ${projectId}`);
48
+ if (hasServiceAccount()) {
49
+ // SDK mode: download → upload via Firebase Admin
50
+ const { downloadUrlToFile, uploadFile, saveTrack } = require('../lib/uploader');
51
+ console.log(`Download: ${url}`);
52
+ const tmpDir = os.tmpdir();
53
+ const tmpFile = path.join(tmpDir, `mureka_${Date.now()}.mp3`);
54
+ await downloadUrlToFile(url, tmpFile);
55
+ console.log(`Downloaded to: ${tmpFile}`);
56
+
57
+ const trackId = `mureka_${Date.now()}`;
58
+ const artist = 'Mureka AI';
59
+ const title = `Generated ${new Date().toISOString().slice(0, 10)}`;
60
+
61
+ process.stdout.write('Uploading...');
62
+ const downloadUrl = await uploadFile(tmpFile, `music_files/${trackId}.mp3`);
63
+ await saveTrack({ trackId, title, artist, downloadUrl, projectId });
64
+ console.log(' OK');
65
+
66
+ try { fs.unlinkSync(tmpFile); } catch (_) {}
67
+ console.log(`Mureka track published to project ${projectId}`);
68
+ } else {
69
+ // HTTP mode: call API
70
+ console.log(`Submitting: ${url} -> project ${projectId}`);
71
+ const result = await syncMusicHttp(key, url, projectId);
72
+ console.log(`OK: ${result.trackId} — ${result.downloadUrl}`);
73
+ }
60
74
  }
61
75
  }
62
76
 
package/commands/token.js CHANGED
@@ -1,37 +1,44 @@
1
- const { getFirestore } = require('../lib/firestore');
1
+ const { hasServiceAccount, checkTokenHttp } = require('../lib/http-client');
2
+ const { validateKeyFormat, resolveKeyEnv } = require('../lib/auth');
2
3
 
3
4
  async function checkToken(key) {
4
- const token = key || process.env.MYAPP_API_KEY;
5
+ const token = resolveKeyEnv(key);
5
6
 
6
- if (!token) {
7
- console.error('Usage: pushnetgo-cli token check --key pushnetgo-xxxx');
8
- console.error(' or: export MYAPP_API_KEY="pushnetgo-xxxx"');
9
- process.exit(1);
10
- }
11
-
12
- if (!token.startsWith('pushnetgo-')) {
13
- console.error('Invalid prefix. PushNetGo keys must start with pushnetgo-');
14
- process.exit(1);
15
- }
7
+ if (hasServiceAccount()) {
8
+ const { getFirestore } = require('../lib/firestore');
9
+ const db = getFirestore();
10
+ const snap = await db.collection('tokens')
11
+ .where('tokenValue', '==', token)
12
+ .limit(1)
13
+ .get();
16
14
 
17
- const db = getFirestore();
18
- const snap = await db.collection('tokens')
19
- .where('tokenValue', '==', token)
20
- .limit(1)
21
- .get();
15
+ if (snap.empty) {
16
+ console.log(`NOT FOUND: ${token.slice(0, 16)}...`);
17
+ return;
18
+ }
22
19
 
23
- if (snap.empty) {
24
- console.log(`NOT FOUND: ${token.slice(0, 16)}...`);
25
- return;
20
+ const d = snap.docs[0].data();
21
+ console.log(`OK: ${token}`);
22
+ console.log(` active: ${d.isActive}`);
23
+ console.log(` usage: ${d.usageCount || 0}`);
24
+ console.log(` userId: ${d.userId}`);
25
+ console.log(` perms: ${JSON.stringify(d.permissions || d.scopes || [])}`);
26
+ console.log(` created: ${d.createdAt?.toDate?.() || d.createdAt}`);
27
+ } else {
28
+ // HTTP mode
29
+ try {
30
+ const result = await checkTokenHttp(token);
31
+ console.log(`OK: ${result.token}`);
32
+ console.log(` active: ${result.active}`);
33
+ console.log(` usage: ${result.usageCount || 0}`);
34
+ console.log(` userId: ${result.userId}`);
35
+ console.log(` perms: ${JSON.stringify(result.permissions || [])}`);
36
+ console.log(` created: ${result.createdAt}`);
37
+ } catch (e) {
38
+ console.error(`Error: ${e.error || e.message || e}`);
39
+ process.exit(1);
40
+ }
26
41
  }
27
-
28
- const d = snap.docs[0].data();
29
- console.log(`OK: ${token}`);
30
- console.log(` active: ${d.isActive}`);
31
- console.log(` usage: ${d.usageCount || 0}`);
32
- console.log(` userId: ${d.userId}`);
33
- console.log(` perms: ${JSON.stringify(d.permissions || d.scopes || [])}`);
34
- console.log(` created: ${d.createdAt?.toDate?.() || d.createdAt}`);
35
42
  }
36
43
 
37
44
  module.exports = { checkToken };
package/lib/auth.js CHANGED
@@ -1,49 +1,72 @@
1
- const { getFirestore, admin } = require('./firestore');
2
-
3
- async function resolveApiKey(cliKey) {
4
- const key = cliKey || process.env.MYAPP_API_KEY;
5
-
6
- if (!key) {
7
- console.error('No API Key found. Set env var or pass --key:');
8
- console.error(' export MYAPP_API_KEY="pushnetgo-xxxx"');
9
- console.error(' or: pushnetgo-cli sync-music --key pushnetgo-xxxx ...');
10
- process.exit(1);
11
- }
1
+ const { hasServiceAccount } = require('./http-client');
12
2
 
3
+ function validateKeyFormat(key) {
13
4
  if (!key.startsWith('pushnetgo-') || key.length < 22) {
14
5
  console.error('Invalid key format. Expected pushnetgo- prefix + 32 hex chars');
15
6
  process.exit(1);
16
7
  }
8
+ }
17
9
 
18
- const db = getFirestore();
19
- const snap = await db.collection('tokens')
20
- .where('tokenValue', '==', key)
21
- .where('isActive', '==', true)
22
- .limit(1)
23
- .get();
24
-
25
- if (snap.empty) {
26
- console.error('Key not found or inactive');
10
+ function resolveKeyEnv(cliKey) {
11
+ const key = cliKey || process.env.MYAPP_API_KEY;
12
+ if (!key) {
13
+ console.error('No API Key found. Set env var or pass --key:');
14
+ console.error(' export MYAPP_API_KEY="pushnetgo-xxxx"');
15
+ console.error(' or: pushnetgo-cli sync-music --key pushnetgo-xxxx ...');
27
16
  process.exit(1);
28
17
  }
18
+ validateKeyFormat(key);
19
+ return key;
20
+ }
21
+
22
+ async function resolveApiKey(cliKey) {
23
+ const key = resolveKeyEnv(cliKey);
29
24
 
30
- const data = snap.docs[0].data();
31
- const perms = data.permissions || data.scopes || [];
32
- const hasUpload = perms.some(p => p === 'musicUpload' || p === 'music_upload');
25
+ if (hasServiceAccount()) {
26
+ const { getFirestore, admin } = require('./firestore');
27
+ const db = getFirestore();
28
+ const snap = await db.collection('tokens')
29
+ .where('tokenValue', '==', key)
30
+ .where('isActive', '==', true)
31
+ .limit(1)
32
+ .get();
33
33
 
34
- if (!hasUpload) {
35
- console.error('This key lacks musicUpload permission');
36
- process.exit(1);
37
- }
34
+ if (snap.empty) {
35
+ console.error('Key not found or inactive');
36
+ process.exit(1);
37
+ }
38
38
 
39
- console.log(`Auth OK uid: ${data.userId}`);
39
+ const data = snap.docs[0].data();
40
+ const perms = data.permissions || data.scopes || [];
41
+ const hasUpload = perms.some(p => p === 'musicUpload' || p === 'music_upload');
40
42
 
41
- await db.collection('tokens').doc(snap.docs[0].id).update({
42
- lastUsedAt: admin.firestore.FieldValue.serverTimestamp(),
43
- usageCount: admin.firestore.FieldValue.increment(1),
44
- });
43
+ if (!hasUpload) {
44
+ console.error('This key lacks musicUpload permission');
45
+ process.exit(1);
46
+ }
47
+
48
+ console.log(`Auth OK — uid: ${data.userId}`);
49
+ await db.collection('tokens').doc(snap.docs[0].id).update({
50
+ lastUsedAt: admin.firestore.FieldValue.serverTimestamp(),
51
+ usageCount: admin.firestore.FieldValue.increment(1),
52
+ });
53
+ } else {
54
+ // HTTP mode: validate via API
55
+ const { checkTokenHttp } = require('./http-client');
56
+ try {
57
+ const result = await checkTokenHttp(key);
58
+ if (!result.active) {
59
+ console.error('Token is inactive');
60
+ process.exit(1);
61
+ }
62
+ console.log(`Auth OK — uid: ${result.userId}`);
63
+ } catch (e) {
64
+ console.error('Token validation failed:', e.error || e.message || e);
65
+ process.exit(1);
66
+ }
67
+ }
45
68
 
46
69
  return key;
47
70
  }
48
71
 
49
- module.exports = { resolveApiKey };
72
+ module.exports = { resolveApiKey, resolveKeyEnv, validateKeyFormat };
@@ -0,0 +1,57 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ const API_URL = 'https://us-central1-flutter-ai-playground-f8e3b.cloudfunctions.net/unifiedGatewayApi';
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ let _hasSA = null;
10
+ function hasServiceAccount() {
11
+ if (_hasSA !== null) return _hasSA;
12
+ const saPath = path.resolve(__dirname, '../../configs/service-account.json');
13
+ _hasSA = fs.existsSync(saPath);
14
+ return _hasSA;
15
+ }
16
+
17
+ function _request(body, apiKey) {
18
+ return new Promise((resolve, reject) => {
19
+ const data = JSON.stringify(body);
20
+ const url = new URL(API_URL);
21
+ const client = url.protocol === 'https:' ? https : http;
22
+ const req = client.request({
23
+ hostname: url.hostname,
24
+ path: url.pathname,
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ 'Content-Length': Buffer.byteLength(data),
29
+ 'x-api-key': apiKey,
30
+ },
31
+ }, (res) => {
32
+ let body = '';
33
+ res.on('data', chunk => body += chunk);
34
+ res.on('end', () => {
35
+ try {
36
+ const json = JSON.parse(body);
37
+ res.statusCode >= 400 ? reject(json) : resolve(json);
38
+ } catch (_) {
39
+ reject({ error: `HTTP ${res.statusCode}: ${body.slice(0, 200)}` });
40
+ }
41
+ });
42
+ });
43
+ req.on('error', reject);
44
+ req.write(data);
45
+ req.end();
46
+ });
47
+ }
48
+
49
+ async function checkTokenHttp(key) {
50
+ return _request({ action: 'token-check' }, key);
51
+ }
52
+
53
+ async function syncMusicHttp(key, url, projectId) {
54
+ return _request({ action: 'sync-music', url, projectId }, key);
55
+ }
56
+
57
+ module.exports = { hasServiceAccount, checkTokenHttp, syncMusicHttp, API_URL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushnetgo-cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "PushNetGo App CLI — HTTP API: https://us-central1-flutter-ai-playground-f8e3b.cloudfunctions.net/unifiedGatewayApi",
5
5
  "bin": {
6
6
  "pushnetgo-cli": "./bin/pushnetgo-cli.js"