filemayor 3.6.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.
- package/LICENSE +90 -90
- package/README.md +96 -96
- package/core/analyzer.js +163 -163
- package/core/categories.js +235 -235
- package/core/cleaner.js +527 -527
- package/core/config.js +562 -562
- package/core/fs-abstraction.js +110 -38
- package/core/index.js +135 -135
- package/core/intent-interpreter.js +209 -66
- package/core/license.js +403 -339
- package/core/organizer.js +414 -414
- package/core/reporter.js +783 -783
- package/core/scanner.js +466 -466
- package/core/security.js +370 -370
- package/core/sop-parser.js +564 -564
- package/core/telemetry.js +74 -0
- package/core/vault.js +83 -69
- package/core/watcher.js +478 -478
- package/index.js +895 -877
- package/package.json +2 -2
|
@@ -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
CHANGED
|
@@ -3,21 +3,20 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ═══════════════════════════════════════════════════════════════════
|
|
5
5
|
* FILEMAYOR CORE — VAULT (ZERO-DEPENDENCY SECRET MANAGER)
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Uses the OS-native credential store to keep API keys out of
|
|
8
|
-
* the filesystem entirely. Falls back to
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* the filesystem entirely. Falls back to environment variables only.
|
|
9
|
+
*
|
|
11
10
|
* Supported backends:
|
|
12
11
|
* macOS → security (Keychain Services)
|
|
13
|
-
* Windows →
|
|
12
|
+
* Windows → PowerShell CredentialManager
|
|
14
13
|
* Linux → secret-tool (freedesktop Secret Service)
|
|
15
14
|
* ═══════════════════════════════════════════════════════════════════
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
17
|
'use strict';
|
|
19
18
|
|
|
20
|
-
const {
|
|
19
|
+
const { execFileSync } = require('child_process');
|
|
21
20
|
const path = require('path');
|
|
22
21
|
const fs = require('fs');
|
|
23
22
|
|
|
@@ -26,72 +25,96 @@ const SERVICE_NAME = 'FileMayor';
|
|
|
26
25
|
// ─── Platform-Specific Implementations ───────────────────────────
|
|
27
26
|
|
|
28
27
|
function _saveSecret_darwin(key, value) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
execFileSync('security', [
|
|
29
|
+
'add-generic-password',
|
|
30
|
+
'-a', SERVICE_NAME,
|
|
31
|
+
'-s', key,
|
|
32
|
+
'-w', value,
|
|
33
|
+
'-U',
|
|
34
|
+
], { stdio: 'ignore' });
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function _getSecret_darwin(key) {
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
return execFileSync('security', [
|
|
39
|
+
'find-generic-password',
|
|
40
|
+
'-a', SERVICE_NAME,
|
|
41
|
+
'-s', key,
|
|
42
|
+
'-w',
|
|
43
|
+
], { encoding: 'utf8' }).trim();
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
function _deleteSecret_darwin(key) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
execFileSync('security', [
|
|
48
|
+
'delete-generic-password',
|
|
49
|
+
'-a', SERVICE_NAME,
|
|
50
|
+
'-s', key,
|
|
51
|
+
], { stdio: 'ignore' });
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
function _saveSecret_win32(key, value) {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
});
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
function _getSecret_win32(key) {
|
|
57
|
-
|
|
58
|
-
const
|
|
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 }`;
|
|
59
77
|
try {
|
|
60
|
-
return
|
|
78
|
+
return execFileSync('powershell', [
|
|
79
|
+
'-NoProfile', '-NonInteractive', '-Command', scriptBlock,
|
|
80
|
+
], { encoding: 'utf8' }).trim();
|
|
61
81
|
} catch {
|
|
62
|
-
// Fallback: try simpler cmdkey parsing
|
|
63
82
|
return null;
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
function _saveSecret_linux(key, value) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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'] });
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
function _getSecret_linux(key) {
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
return execFileSync('secret-tool', [
|
|
98
|
+
'lookup',
|
|
99
|
+
'service', SERVICE_NAME,
|
|
100
|
+
'key', key,
|
|
101
|
+
], { encoding: 'utf8' }).trim();
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
// ─── The Vault ────────────────────────────────────────────────────
|
|
82
105
|
|
|
83
106
|
const Vault = {
|
|
84
107
|
/**
|
|
85
|
-
* Save a secret to the system keychain
|
|
86
|
-
* @param {string} key
|
|
87
|
-
* @param {string} value
|
|
88
|
-
* @returns {boolean}
|
|
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
|
|
89
112
|
*/
|
|
90
113
|
saveSecret(key, value) {
|
|
91
114
|
try {
|
|
92
|
-
if (process.platform === 'darwin')
|
|
93
|
-
else if (process.platform === 'win32')
|
|
94
|
-
else
|
|
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);
|
|
95
118
|
return true;
|
|
96
119
|
} catch (err) {
|
|
97
120
|
console.warn(`[Vault] Could not save to system keychain: ${err.message}`);
|
|
@@ -100,14 +123,17 @@ const Vault = {
|
|
|
100
123
|
},
|
|
101
124
|
|
|
102
125
|
/**
|
|
103
|
-
* Retrieve a secret — checks System Keychain → ENV
|
|
104
|
-
*
|
|
105
|
-
*
|
|
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}
|
|
106
132
|
*/
|
|
107
133
|
getSecret(key) {
|
|
108
134
|
// Priority 1: System Keychain (most secure)
|
|
109
135
|
try {
|
|
110
|
-
if (process.platform === 'darwin')
|
|
136
|
+
if (process.platform === 'darwin') return _getSecret_darwin(key);
|
|
111
137
|
else if (process.platform === 'win32') {
|
|
112
138
|
const val = _getSecret_win32(key);
|
|
113
139
|
if (val) return val;
|
|
@@ -118,34 +144,22 @@ const Vault = {
|
|
|
118
144
|
// Priority 2: Environment Variable
|
|
119
145
|
if (process.env[key]) return process.env[key];
|
|
120
146
|
|
|
121
|
-
// Priority 3: .env file (least secure, but common)
|
|
122
|
-
try {
|
|
123
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
124
|
-
if (fs.existsSync(envPath)) {
|
|
125
|
-
const envFile = fs.readFileSync(envPath, 'utf-8');
|
|
126
|
-
for (const line of envFile.split('\n')) {
|
|
127
|
-
const trimmed = line.trim();
|
|
128
|
-
if (trimmed.startsWith('#') || !trimmed) continue;
|
|
129
|
-
const [k, ...vals] = trimmed.split('=');
|
|
130
|
-
if (k.trim() === key && vals.length) {
|
|
131
|
-
return vals.join('=').trim();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} catch { /* .env not available */ }
|
|
136
|
-
|
|
137
147
|
return null;
|
|
138
148
|
},
|
|
139
149
|
|
|
140
150
|
/**
|
|
141
|
-
* Delete a secret from the system keychain
|
|
142
|
-
* @param {string} key
|
|
143
|
-
* @returns {boolean}
|
|
151
|
+
* Delete a secret from the system keychain.
|
|
152
|
+
* @param {string} key Secret name
|
|
153
|
+
* @returns {boolean} true on success
|
|
144
154
|
*/
|
|
145
155
|
deleteSecret(key) {
|
|
146
156
|
try {
|
|
147
|
-
if (process.platform === 'darwin')
|
|
148
|
-
|
|
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
|
+
}
|
|
149
163
|
return true;
|
|
150
164
|
} catch {
|
|
151
165
|
return false;
|
|
@@ -153,13 +167,13 @@ const Vault = {
|
|
|
153
167
|
},
|
|
154
168
|
|
|
155
169
|
/**
|
|
156
|
-
* Check if a secret exists anywhere in the priority chain
|
|
157
|
-
* @param {string} key
|
|
170
|
+
* Check if a secret exists anywhere in the priority chain.
|
|
171
|
+
* @param {string} key
|
|
158
172
|
* @returns {boolean}
|
|
159
173
|
*/
|
|
160
174
|
hasSecret(key) {
|
|
161
175
|
return Vault.getSecret(key) !== null;
|
|
162
|
-
}
|
|
176
|
+
},
|
|
163
177
|
};
|
|
164
178
|
|
|
165
179
|
module.exports = Vault;
|