filemayor 2.1.0 → 4.0.5

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.
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ const PING_URL = 'https://filemayor.com/api/ping';
8
+ const STATE_FILE = path.join(os.homedir(), '.filemayor-telemetry.json');
9
+
10
+ // Fire-and-forget anonymous ping.
11
+ // Sends: version, OS platform, CPU arch, and top-level command name.
12
+ // Never sends: file names, paths, sizes, contents, or any PII.
13
+ // Opt-out: set FILEMAYOR_NO_TELEMETRY=1 in environment.
14
+ // Fires at most once per version (tracked in ~/.filemayor-telemetry.json).
15
+ function ping(version, cmd) {
16
+ if (process.env.FILEMAYOR_NO_TELEMETRY === '1') return;
17
+ if (process.env.CI) return;
18
+
19
+ try {
20
+ const state = readState();
21
+ if (state.pinged && state.pinged.includes(version)) return;
22
+
23
+ const payload = JSON.stringify({
24
+ v: version,
25
+ os: os.platform(),
26
+ arch: os.arch(),
27
+ cmd: cmd || 'unknown',
28
+ });
29
+
30
+ // Fire over https using built-in https module — no extra deps
31
+ const https = require('https');
32
+ const url = new URL(PING_URL);
33
+ const req = https.request({
34
+ hostname: url.hostname,
35
+ path: url.pathname,
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Length': Buffer.byteLength(payload),
40
+ 'User-Agent': `filemayor-cli/${version}`,
41
+ },
42
+ timeout: 3000,
43
+ });
44
+
45
+ req.on('error', () => {}); // swallow all errors — telemetry must never crash the CLI
46
+ req.on('timeout', () => req.destroy());
47
+ req.end(payload);
48
+
49
+ markPinged(state, version);
50
+ } catch {
51
+ // intentionally silent
52
+ }
53
+ }
54
+
55
+ function readState() {
56
+ try {
57
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
58
+ } catch {
59
+ return { pinged: [] };
60
+ }
61
+ }
62
+
63
+ function markPinged(state, version) {
64
+ try {
65
+ const pinged = Array.isArray(state.pinged) ? state.pinged : [];
66
+ pinged.push(version);
67
+ // Keep only last 20 versions to bound file size
68
+ fs.writeFileSync(STATE_FILE, JSON.stringify({ pinged: pinged.slice(-20) }), 'utf-8');
69
+ } catch {
70
+ // intentionally silent
71
+ }
72
+ }
73
+
74
+ module.exports = { ping };
package/core/vault.js ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ═══════════════════════════════════════════════════════════════════
5
+ * FILEMAYOR CORE — VAULT (ZERO-DEPENDENCY SECRET MANAGER)
6
+ *
7
+ * Uses the OS-native credential store to keep API keys out of
8
+ * the filesystem entirely. Falls back to environment variables only.
9
+ *
10
+ * Supported backends:
11
+ * macOS → security (Keychain Services)
12
+ * Windows → PowerShell CredentialManager
13
+ * Linux → secret-tool (freedesktop Secret Service)
14
+ * ═══════════════════════════════════════════════════════════════════
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { execFileSync } = require('child_process');
20
+ const path = require('path');
21
+ const fs = require('fs');
22
+
23
+ const SERVICE_NAME = 'FileMayor';
24
+
25
+ // ─── Platform-Specific Implementations ───────────────────────────
26
+
27
+ function _saveSecret_darwin(key, value) {
28
+ execFileSync('security', [
29
+ 'add-generic-password',
30
+ '-a', SERVICE_NAME,
31
+ '-s', key,
32
+ '-w', value,
33
+ '-U',
34
+ ], { stdio: 'ignore' });
35
+ }
36
+
37
+ function _getSecret_darwin(key) {
38
+ return execFileSync('security', [
39
+ 'find-generic-password',
40
+ '-a', SERVICE_NAME,
41
+ '-s', key,
42
+ '-w',
43
+ ], { encoding: 'utf8' }).trim();
44
+ }
45
+
46
+ function _deleteSecret_darwin(key) {
47
+ execFileSync('security', [
48
+ 'delete-generic-password',
49
+ '-a', SERVICE_NAME,
50
+ '-s', key,
51
+ ], { stdio: 'ignore' });
52
+ }
53
+
54
+ function _saveSecret_win32(key, value) {
55
+ // Pass the secret value via an environment variable to avoid it
56
+ // appearing in the command string or the process table.
57
+ const scriptBlock = `
58
+ $target = "${SERVICE_NAME}_${key.replace(/[^A-Za-z0-9_-]/g, '_')}"
59
+ $user = "${key.replace(/[^A-Za-z0-9_-]/g, '_')}"
60
+ $pass = $env:FM_VAULT_VALUE
61
+ cmdkey /generic:$target /user:$user /pass:$pass | Out-Null
62
+ `.trim();
63
+ execFileSync('powershell', [
64
+ '-NoProfile', '-NonInteractive', '-Command', scriptBlock,
65
+ ], {
66
+ stdio: 'ignore',
67
+ env: { ...process.env, FM_VAULT_VALUE: value },
68
+ });
69
+ }
70
+
71
+ function _getSecret_win32(key) {
72
+ const safeKey = key.replace(/[^A-Za-z0-9_-]/g, '_');
73
+ const target = `${SERVICE_NAME}_${safeKey}`;
74
+ const scriptBlock =
75
+ `$c = Get-StoredCredential -Target '${target}'; ` +
76
+ `if ($c) { $c.GetNetworkCredential().Password } else { exit 1 }`;
77
+ try {
78
+ return execFileSync('powershell', [
79
+ '-NoProfile', '-NonInteractive', '-Command', scriptBlock,
80
+ ], { encoding: 'utf8' }).trim();
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ function _saveSecret_linux(key, value) {
87
+ // Pass the secret via stdin so it never appears in the process table.
88
+ execFileSync('secret-tool', [
89
+ 'store',
90
+ '--label', `${SERVICE_NAME} ${key}`,
91
+ 'service', SERVICE_NAME,
92
+ 'key', key,
93
+ ], { input: value + '\n', stdio: ['pipe', 'ignore', 'ignore'] });
94
+ }
95
+
96
+ function _getSecret_linux(key) {
97
+ return execFileSync('secret-tool', [
98
+ 'lookup',
99
+ 'service', SERVICE_NAME,
100
+ 'key', key,
101
+ ], { encoding: 'utf8' }).trim();
102
+ }
103
+
104
+ // ─── The Vault ────────────────────────────────────────────────────
105
+
106
+ const Vault = {
107
+ /**
108
+ * Save a secret to the system keychain.
109
+ * @param {string} key Secret name (e.g. 'GEMINI_API_KEY')
110
+ * @param {string} value Secret value
111
+ * @returns {boolean} true on success
112
+ */
113
+ saveSecret(key, value) {
114
+ try {
115
+ if (process.platform === 'darwin') _saveSecret_darwin(key, value);
116
+ else if (process.platform === 'win32') _saveSecret_win32(key, value);
117
+ else _saveSecret_linux(key, value);
118
+ return true;
119
+ } catch (err) {
120
+ console.warn(`[Vault] Could not save to system keychain: ${err.message}`);
121
+ return false;
122
+ }
123
+ },
124
+
125
+ /**
126
+ * Retrieve a secret — checks System Keychain → ENV.
127
+ * The .env plaintext fallback has been removed. Set secrets via
128
+ * `filemayor config set KEY value` (stores in keychain) or
129
+ * export them as shell environment variables before starting.
130
+ * @param {string} key Secret name
131
+ * @returns {string|null}
132
+ */
133
+ getSecret(key) {
134
+ // Priority 1: System Keychain (most secure)
135
+ try {
136
+ if (process.platform === 'darwin') return _getSecret_darwin(key);
137
+ else if (process.platform === 'win32') {
138
+ const val = _getSecret_win32(key);
139
+ if (val) return val;
140
+ }
141
+ else return _getSecret_linux(key);
142
+ } catch { /* Keychain not available or key not found */ }
143
+
144
+ // Priority 2: Environment Variable
145
+ if (process.env[key]) return process.env[key];
146
+
147
+ return null;
148
+ },
149
+
150
+ /**
151
+ * Delete a secret from the system keychain.
152
+ * @param {string} key Secret name
153
+ * @returns {boolean} true on success
154
+ */
155
+ deleteSecret(key) {
156
+ try {
157
+ if (process.platform === 'darwin') _deleteSecret_darwin(key);
158
+ else if (process.platform === 'linux') {
159
+ execFileSync('secret-tool', [
160
+ 'clear', 'service', SERVICE_NAME, 'key', key,
161
+ ], { stdio: 'ignore' });
162
+ }
163
+ return true;
164
+ } catch {
165
+ return false;
166
+ }
167
+ },
168
+
169
+ /**
170
+ * Check if a secret exists anywhere in the priority chain.
171
+ * @param {string} key
172
+ * @returns {boolean}
173
+ */
174
+ hasSecret(key) {
175
+ return Vault.getSecret(key) !== null;
176
+ },
177
+ };
178
+
179
+ module.exports = Vault;