securenow 5.15.2 → 5.16.1
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/NPM_README.md +37 -8
- package/README.md +9 -3
- package/SKILL-CLI.md +19 -11
- package/cli/auth.js +23 -5
- package/cli/config.js +62 -12
- package/cli.js +11 -6
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +13 -3
- package/docs/API-KEYS-GUIDE.md +17 -1
- package/package.json +1 -1
package/NPM_README.md
CHANGED
|
@@ -148,7 +148,10 @@ npx securenow login
|
|
|
148
148
|
# Or use a token for CI/headless environments
|
|
149
149
|
npx securenow login --token <YOUR_JWT>
|
|
150
150
|
|
|
151
|
-
#
|
|
151
|
+
# Log in for this project only (per-project credentials)
|
|
152
|
+
npx securenow login --local
|
|
153
|
+
|
|
154
|
+
# Check who you're logged in as (shows auth source)
|
|
152
155
|
npx securenow whoami
|
|
153
156
|
```
|
|
154
157
|
|
|
@@ -327,12 +330,15 @@ npx securenow config set format json
|
|
|
327
330
|
npx securenow config path
|
|
328
331
|
```
|
|
329
332
|
|
|
330
|
-
Config files are stored in `~/.securenow
|
|
333
|
+
Config files are stored in `~/.securenow/` (global) or `.securenow/` in the project root (per-project):
|
|
331
334
|
|
|
332
335
|
| File | Description |
|
|
333
336
|
|------|-------------|
|
|
334
|
-
|
|
|
335
|
-
|
|
|
337
|
+
| `~/.securenow/config.json` | API URL, default app, output format |
|
|
338
|
+
| `~/.securenow/credentials.json` | Auth token — global (file permissions: 0600) |
|
|
339
|
+
| `.securenow/credentials.json` | Auth token — project-local (use `login --local`) |
|
|
340
|
+
|
|
341
|
+
**Resolution order:** `SECURENOW_TOKEN` env var → project `.securenow/credentials.json` → global `~/.securenow/credentials.json`.
|
|
336
342
|
|
|
337
343
|
### Global Flags
|
|
338
344
|
|
|
@@ -348,14 +354,37 @@ Every command supports these flags:
|
|
|
348
354
|
|
|
349
355
|
| Variable | Description |
|
|
350
356
|
|----------|-------------|
|
|
357
|
+
| `SECURENOW_TOKEN` | JWT token — overrides all file-based credentials |
|
|
351
358
|
| `SECURENOW_API_URL` | Override the API base URL |
|
|
352
359
|
| `SECURENOW_DEBUG` | Show stack traces on errors |
|
|
353
360
|
| `NO_COLOR` | Disable colored output |
|
|
354
361
|
|
|
362
|
+
### Multi-Project Sessions
|
|
363
|
+
|
|
364
|
+
Use `--local` to maintain separate logins per project on the same machine:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# In project A — log in as user-a@company.com
|
|
368
|
+
cd ~/projects/project-a
|
|
369
|
+
npx securenow login --local
|
|
370
|
+
|
|
371
|
+
# In project B — log in as user-b@company.com
|
|
372
|
+
cd ~/projects/project-b
|
|
373
|
+
npx securenow login --local
|
|
374
|
+
|
|
375
|
+
# Each project uses its own credentials independently
|
|
376
|
+
npx securenow whoami # Shows auth source: project (.securenow/)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
You can also use the `SECURENOW_TOKEN` env var for per-terminal sessions without touching any files.
|
|
380
|
+
|
|
355
381
|
### CI/CD Integration
|
|
356
382
|
|
|
357
383
|
```bash
|
|
358
|
-
# Authenticate with a token in CI
|
|
384
|
+
# Authenticate with a token in CI (env var — no file needed)
|
|
385
|
+
SECURENOW_TOKEN=$MY_SECRET npx securenow issues --json
|
|
386
|
+
|
|
387
|
+
# Or use login with explicit token
|
|
359
388
|
npx securenow login --token $SECURENOW_TOKEN
|
|
360
389
|
|
|
361
390
|
# Use --json for machine-readable output
|
|
@@ -375,9 +404,9 @@ fi
|
|
|
375
404
|
| Category | Command | Description |
|
|
376
405
|
|----------|---------|-------------|
|
|
377
406
|
| **Setup** | `init` | Auto-scaffold instrumentation for your framework |
|
|
378
|
-
| **Auth** | `login` | Authenticate via browser or `--
|
|
379
|
-
| | `logout` | Clear credentials |
|
|
380
|
-
| | `whoami` | Show session info |
|
|
407
|
+
| **Auth** | `login` | Authenticate via browser, `--token`, or `--local` |
|
|
408
|
+
| | `logout` | Clear credentials (`--local` for project only) |
|
|
409
|
+
| | `whoami` | Show session info and auth source |
|
|
381
410
|
| **Apps** | `apps` | List applications |
|
|
382
411
|
| | `apps create <name>` | Create application |
|
|
383
412
|
| | `apps info <id>` | Application details |
|
package/README.md
CHANGED
|
@@ -264,8 +264,10 @@ Most users won't need this — just add `-r securenow/register` to your existing
|
|
|
264
264
|
|---------|-------------|
|
|
265
265
|
| `securenow login` | Log in via browser (opens OAuth flow) |
|
|
266
266
|
| `securenow login --token <TOKEN>` | Log in with a token (for CI/headless) |
|
|
267
|
+
| `securenow login --local` | Log in and save credentials to the current project only |
|
|
267
268
|
| `securenow logout` | Clear stored credentials |
|
|
268
|
-
| `securenow
|
|
269
|
+
| `securenow logout --local` | Clear project-local credentials only |
|
|
270
|
+
| `securenow whoami` | Show current session info (including auth source) |
|
|
269
271
|
|
|
270
272
|
### Applications
|
|
271
273
|
|
|
@@ -356,15 +358,19 @@ Most users won't need this — just add `-r securenow/register` to your existing
|
|
|
356
358
|
| `--json` | Output as JSON (works on every command) |
|
|
357
359
|
| `--help` | Show help for any command |
|
|
358
360
|
| `--app <key>` | Specify app key (or set default with `config set defaultApp`) |
|
|
361
|
+
| `--local` | Save/clear credentials per-project (login/logout only) |
|
|
359
362
|
|
|
360
363
|
### Configuration
|
|
361
364
|
|
|
362
|
-
Credentials and settings are stored in `~/.securenow
|
|
365
|
+
Credentials and settings are stored in `~/.securenow/` (global) or `.securenow/` (per-project):
|
|
363
366
|
|
|
364
367
|
| File | Purpose |
|
|
365
368
|
|------|---------|
|
|
366
369
|
| `~/.securenow/config.json` | API URL, default app, preferences |
|
|
367
|
-
| `~/.securenow/credentials.json` | Auth token (restricted permissions) |
|
|
370
|
+
| `~/.securenow/credentials.json` | Auth token — global (restricted permissions) |
|
|
371
|
+
| `.securenow/credentials.json` | Auth token — project-local (use `login --local`) |
|
|
372
|
+
|
|
373
|
+
**Credential resolution order:** `SECURENOW_TOKEN` env var → project `.securenow/credentials.json` → global `~/.securenow/credentials.json`.
|
|
368
374
|
|
|
369
375
|
Override the API URL with `securenow config set apiUrl <url>` or the `SECURENOW_API_URL` environment variable.
|
|
370
376
|
|
package/SKILL-CLI.md
CHANGED
|
@@ -18,9 +18,12 @@ npx securenow <command>
|
|
|
18
18
|
```bash
|
|
19
19
|
securenow login # opens browser OAuth; stores JWT in ~/.securenow/credentials.json
|
|
20
20
|
securenow login --token <JWT> # headless / CI login (get token from dashboard Settings)
|
|
21
|
-
securenow
|
|
21
|
+
securenow login --local # save credentials to this project only (.securenow/)
|
|
22
|
+
securenow whoami # verify session (shows auth source)
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
**Per-project credentials:** Use `--local` to keep separate logins in different project directories on the same machine. Credentials resolve in order: `SECURENOW_TOKEN` env var → project `.securenow/credentials.json` → global `~/.securenow/credentials.json`.
|
|
26
|
+
|
|
24
27
|
### Integrate With Your App
|
|
25
28
|
|
|
26
29
|
The CLI can also instrument any Node.js app at launch — no code changes:
|
|
@@ -47,22 +50,25 @@ Save this file as `.cursor/skills/securenow-cli/SKILL.md` in your project. Your
|
|
|
47
50
|
|
|
48
51
|
## Configuration
|
|
49
52
|
|
|
50
|
-
Config lives in `~/.securenow
|
|
53
|
+
Config lives in `~/.securenow/` (global) and optionally `.securenow/` (per-project):
|
|
51
54
|
|
|
52
55
|
| File | Content |
|
|
53
56
|
|------|---------|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
57
|
+
| `~/.securenow/config.json` | `apiUrl`, `appUrl`, `defaultApp`, `output` |
|
|
58
|
+
| `~/.securenow/credentials.json` | `token`, `email`, `expiresAt` (global) |
|
|
59
|
+
| `.securenow/credentials.json` | `token`, `email`, `expiresAt` (project-local, use `login --local`) |
|
|
60
|
+
|
|
61
|
+
**Credential resolution order:** `SECURENOW_TOKEN` env var → `.securenow/credentials.json` (project) → `~/.securenow/credentials.json` (global).
|
|
56
62
|
|
|
57
63
|
```bash
|
|
58
64
|
securenow config set apiUrl https://api.securenow.ai
|
|
59
65
|
securenow config set defaultApp my-app-key
|
|
60
66
|
securenow config get # show all
|
|
61
67
|
securenow config get defaultApp # show one
|
|
62
|
-
securenow config path # print file paths
|
|
68
|
+
securenow config path # print file paths + active auth source
|
|
63
69
|
```
|
|
64
70
|
|
|
65
|
-
Environment overrides: `SECURENOW_API_URL`, `SECURENOW_APP_URL`, `SECURENOW_APP` (default app key).
|
|
71
|
+
Environment overrides: `SECURENOW_TOKEN` (JWT), `SECURENOW_API_URL`, `SECURENOW_APP_URL`, `SECURENOW_APP` (default app key).
|
|
66
72
|
|
|
67
73
|
## Global Flags
|
|
68
74
|
|
|
@@ -94,10 +100,12 @@ Spawns `node --require securenow/register [--import otel/hook.mjs] <script>`. ES
|
|
|
94
100
|
### Authentication
|
|
95
101
|
|
|
96
102
|
```bash
|
|
97
|
-
securenow login # browser-based OAuth (
|
|
103
|
+
securenow login # browser-based OAuth (stores in global ~/.securenow/)
|
|
98
104
|
securenow login --token <JWT> # headless / CI login
|
|
99
|
-
securenow
|
|
100
|
-
securenow
|
|
105
|
+
securenow login --local # save credentials to project .securenow/ (per-project session)
|
|
106
|
+
securenow logout # clear active credentials (local if present, else global)
|
|
107
|
+
securenow logout --local # clear project-local credentials only
|
|
108
|
+
securenow whoami # show email, user ID, API URL, auth source, expiry, default app
|
|
101
109
|
```
|
|
102
110
|
|
|
103
111
|
### Applications
|
|
@@ -389,8 +397,8 @@ All commands support `--json` for structured output. When piping to other tools
|
|
|
389
397
|
|
|
390
398
|
| Exit code / Error | Meaning | Recovery |
|
|
391
399
|
|------------------|---------|----------|
|
|
392
|
-
| `Session expired` | JWT expired | `securenow login` |
|
|
393
|
-
| `Not logged in` | No token
|
|
400
|
+
| `Session expired` | JWT expired | `securenow login` (or `login --local`) |
|
|
401
|
+
| `Not logged in` | No token found | `securenow login` or set `SECURENOW_TOKEN` env var |
|
|
394
402
|
| `Access denied (403)` | Insufficient plan or permissions | Upgrade plan or check user role |
|
|
395
403
|
| `Cannot connect` | API unreachable | Check `SECURENOW_API_URL` or network |
|
|
396
404
|
| `Unknown command` | Typo or unrecognized command | `securenow help` |
|
package/cli/auth.js
CHANGED
|
@@ -58,7 +58,16 @@ async function loginWithBrowser() {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (token) {
|
|
61
|
-
|
|
61
|
+
const payload = decodeJwtPayload(token);
|
|
62
|
+
const email = payload?.email || '';
|
|
63
|
+
const emailHtml = email
|
|
64
|
+
? `<p style="font-size:18px;margin:16px 0 4px">Signed in as <strong>${email.replace(/</g, '<').replace(/>/g, '>')}</strong></p>`
|
|
65
|
+
: '';
|
|
66
|
+
res.end(`<html><body style="font-family:system-ui;text-align:center;padding:60px">` +
|
|
67
|
+
`<h2 style="color:#22c55e">\u2713 Logged in to SecureNow</h2>` +
|
|
68
|
+
emailHtml +
|
|
69
|
+
`<p style="color:#666">You can close this window and return to the terminal.</p>` +
|
|
70
|
+
`</body></html>`);
|
|
62
71
|
server.close();
|
|
63
72
|
resolve(token);
|
|
64
73
|
return;
|
|
@@ -120,6 +129,8 @@ async function loginWithToken(token) {
|
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
async function login(args, flags) {
|
|
132
|
+
const local = !!flags.local;
|
|
133
|
+
|
|
123
134
|
if (flags.token) {
|
|
124
135
|
const token = flags.token;
|
|
125
136
|
await loginWithToken(token);
|
|
@@ -127,9 +138,11 @@ async function login(args, flags) {
|
|
|
127
138
|
const email = payload?.email || 'unknown';
|
|
128
139
|
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
129
140
|
|
|
130
|
-
config.setAuth(token, email, exp);
|
|
141
|
+
config.setAuth(token, email, exp, { local });
|
|
142
|
+
if (local) config.ensureLocalGitignore();
|
|
131
143
|
console.log('');
|
|
132
144
|
ui.success(`Logged in as ${ui.c.bold(email)}`);
|
|
145
|
+
if (local) ui.info('Credentials saved to project .securenow/ (local)');
|
|
133
146
|
if (exp) {
|
|
134
147
|
const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
|
|
135
148
|
ui.info(`Session expires in ${days} days`);
|
|
@@ -143,9 +156,11 @@ async function login(args, flags) {
|
|
|
143
156
|
const email = payload?.email || 'unknown';
|
|
144
157
|
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
145
158
|
|
|
146
|
-
config.setAuth(token, email, exp);
|
|
159
|
+
config.setAuth(token, email, exp, { local });
|
|
160
|
+
if (local) config.ensureLocalGitignore();
|
|
147
161
|
console.log('');
|
|
148
162
|
ui.success(`Logged in as ${ui.c.bold(email)}`);
|
|
163
|
+
if (local) ui.info('Credentials saved to project .securenow/ (local)');
|
|
149
164
|
if (exp) {
|
|
150
165
|
const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
|
|
151
166
|
ui.info(`Session expires in ${days} days`);
|
|
@@ -165,14 +180,16 @@ async function login(args, flags) {
|
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
182
|
|
|
168
|
-
async function logout() {
|
|
183
|
+
async function logout(args, flags) {
|
|
184
|
+
const local = flags ? flags.local : undefined;
|
|
169
185
|
const creds = config.loadCredentials();
|
|
170
|
-
config.clearCredentials();
|
|
186
|
+
config.clearCredentials({ local });
|
|
171
187
|
if (creds.email) {
|
|
172
188
|
ui.success(`Logged out from ${ui.c.bold(creds.email)}`);
|
|
173
189
|
} else {
|
|
174
190
|
ui.success('Logged out');
|
|
175
191
|
}
|
|
192
|
+
if (local) ui.info('Cleared project-local credentials');
|
|
176
193
|
}
|
|
177
194
|
|
|
178
195
|
async function whoami() {
|
|
@@ -192,6 +209,7 @@ async function whoami() {
|
|
|
192
209
|
['Email', creds.email || payload?.email || 'unknown'],
|
|
193
210
|
['User ID', payload?.sub || 'unknown'],
|
|
194
211
|
['API', config.getApiUrl()],
|
|
212
|
+
['Auth Source', config.getAuthSource()],
|
|
195
213
|
];
|
|
196
214
|
if (creds.expiresAt) {
|
|
197
215
|
const days = Math.ceil((creds.expiresAt - Date.now()) / (1000 * 60 * 60 * 24));
|
package/cli/config.js
CHANGED
|
@@ -8,6 +8,10 @@ const CONFIG_DIR = path.join(os.homedir(), '.securenow');
|
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
9
|
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
|
|
10
10
|
|
|
11
|
+
const LOCAL_CONFIG_DIR = path.join(process.cwd(), '.securenow');
|
|
12
|
+
const LOCAL_CONFIG_FILE = path.join(LOCAL_CONFIG_DIR, 'config.json');
|
|
13
|
+
const LOCAL_CREDENTIALS_FILE = path.join(LOCAL_CONFIG_DIR, 'credentials.json');
|
|
14
|
+
|
|
11
15
|
const DEFAULTS = {
|
|
12
16
|
apiUrl: 'https://api.securenow.ai',
|
|
13
17
|
appUrl: 'https://app.securenow.ai',
|
|
@@ -15,9 +19,9 @@ const DEFAULTS = {
|
|
|
15
19
|
output: 'table',
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
function ensureDir() {
|
|
19
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
20
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
22
|
+
function ensureDir(dir) {
|
|
23
|
+
if (!fs.existsSync(dir || CONFIG_DIR)) {
|
|
24
|
+
fs.mkdirSync(dir || CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -30,7 +34,7 @@ function loadJSON(filepath) {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
function saveJSON(filepath, data) {
|
|
33
|
-
ensureDir();
|
|
37
|
+
ensureDir(path.dirname(filepath));
|
|
34
38
|
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
35
39
|
if (process.platform === 'win32') {
|
|
36
40
|
try {
|
|
@@ -40,8 +44,25 @@ function saveJSON(filepath, data) {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
function hasLocalCredentials() {
|
|
48
|
+
return fs.existsSync(LOCAL_CREDENTIALS_FILE);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveCredentialsFile() {
|
|
52
|
+
if (fs.existsSync(LOCAL_CREDENTIALS_FILE)) return LOCAL_CREDENTIALS_FILE;
|
|
53
|
+
return CREDENTIALS_FILE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getAuthSource() {
|
|
57
|
+
if (process.env.SECURENOW_TOKEN) return 'env (SECURENOW_TOKEN)';
|
|
58
|
+
if (fs.existsSync(LOCAL_CREDENTIALS_FILE)) return 'project (.securenow/)';
|
|
59
|
+
return 'global (~/.securenow/)';
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
function loadConfig() {
|
|
44
|
-
|
|
63
|
+
const global = loadJSON(CONFIG_FILE);
|
|
64
|
+
const local = fs.existsSync(LOCAL_CONFIG_FILE) ? loadJSON(LOCAL_CONFIG_FILE) : {};
|
|
65
|
+
return { ...DEFAULTS, ...global, ...local };
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
function saveConfig(config) {
|
|
@@ -59,20 +80,29 @@ function setConfigValue(key, value) {
|
|
|
59
80
|
}
|
|
60
81
|
|
|
61
82
|
function loadCredentials() {
|
|
62
|
-
return loadJSON(
|
|
83
|
+
return loadJSON(resolveCredentialsFile());
|
|
63
84
|
}
|
|
64
85
|
|
|
65
|
-
function saveCredentials(creds) {
|
|
66
|
-
|
|
86
|
+
function saveCredentials(creds, { local = false } = {}) {
|
|
87
|
+
const targetFile = local ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
88
|
+
saveJSON(targetFile, creds);
|
|
67
89
|
}
|
|
68
90
|
|
|
69
|
-
function clearCredentials() {
|
|
91
|
+
function clearCredentials({ local } = {}) {
|
|
70
92
|
try {
|
|
71
|
-
|
|
93
|
+
if (local === true) {
|
|
94
|
+
fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
|
|
95
|
+
} else if (local === false || !hasLocalCredentials()) {
|
|
96
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
97
|
+
} else {
|
|
98
|
+
fs.unlinkSync(LOCAL_CREDENTIALS_FILE);
|
|
99
|
+
}
|
|
72
100
|
} catch {}
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
function getToken() {
|
|
104
|
+
if (process.env.SECURENOW_TOKEN) return process.env.SECURENOW_TOKEN;
|
|
105
|
+
|
|
76
106
|
const creds = loadCredentials();
|
|
77
107
|
if (!creds.token) return null;
|
|
78
108
|
|
|
@@ -82,8 +112,23 @@ function getToken() {
|
|
|
82
112
|
return creds.token;
|
|
83
113
|
}
|
|
84
114
|
|
|
85
|
-
function setAuth(token, email, expiresAt) {
|
|
86
|
-
saveCredentials({ token, email, expiresAt });
|
|
115
|
+
function setAuth(token, email, expiresAt, { local = false } = {}) {
|
|
116
|
+
saveCredentials({ token, email, expiresAt }, { local });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function ensureLocalGitignore() {
|
|
120
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
121
|
+
const entry = '.securenow/';
|
|
122
|
+
try {
|
|
123
|
+
if (fs.existsSync(gitignorePath)) {
|
|
124
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
125
|
+
if (!content.split('\n').some(line => line.trim() === entry)) {
|
|
126
|
+
fs.appendFileSync(gitignorePath, `\n# SecureNow local credentials\n${entry}\n`);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
fs.writeFileSync(gitignorePath, `# SecureNow local credentials\n${entry}\n`);
|
|
130
|
+
}
|
|
131
|
+
} catch {}
|
|
87
132
|
}
|
|
88
133
|
|
|
89
134
|
function getApiUrl() {
|
|
@@ -102,6 +147,8 @@ module.exports = {
|
|
|
102
147
|
CONFIG_DIR,
|
|
103
148
|
CONFIG_FILE,
|
|
104
149
|
CREDENTIALS_FILE,
|
|
150
|
+
LOCAL_CONFIG_DIR,
|
|
151
|
+
LOCAL_CREDENTIALS_FILE,
|
|
105
152
|
loadConfig,
|
|
106
153
|
saveConfig,
|
|
107
154
|
getConfigValue,
|
|
@@ -111,6 +158,9 @@ module.exports = {
|
|
|
111
158
|
clearCredentials,
|
|
112
159
|
getToken,
|
|
113
160
|
setAuth,
|
|
161
|
+
getAuthSource,
|
|
162
|
+
hasLocalCredentials,
|
|
163
|
+
ensureLocalGitignore,
|
|
114
164
|
getApiUrl,
|
|
115
165
|
getAppUrl,
|
|
116
166
|
getDefaultApp,
|
package/cli.js
CHANGED
|
@@ -47,14 +47,15 @@ const COMMANDS = {
|
|
|
47
47
|
},
|
|
48
48
|
login: {
|
|
49
49
|
desc: 'Authenticate with SecureNow',
|
|
50
|
-
usage: 'securenow login [--token <TOKEN>]',
|
|
51
|
-
flags: { token: 'Authenticate with a token directly' },
|
|
50
|
+
usage: 'securenow login [--token <TOKEN>] [--local]',
|
|
51
|
+
flags: { token: 'Authenticate with a token directly', local: 'Save credentials to this project only (.securenow/)' },
|
|
52
52
|
run: (a, f) => require('./cli/auth').login(a, f),
|
|
53
53
|
},
|
|
54
54
|
logout: {
|
|
55
55
|
desc: 'Clear stored credentials',
|
|
56
|
-
usage: 'securenow logout',
|
|
57
|
-
|
|
56
|
+
usage: 'securenow logout [--local]',
|
|
57
|
+
flags: { local: 'Clear project-local credentials only' },
|
|
58
|
+
run: (a, f) => require('./cli/auth').logout(a, f),
|
|
58
59
|
},
|
|
59
60
|
whoami: {
|
|
60
61
|
desc: 'Show current session info',
|
|
@@ -276,8 +277,12 @@ const COMMANDS = {
|
|
|
276
277
|
desc: 'Show config file path',
|
|
277
278
|
run: () => {
|
|
278
279
|
const conf = require('./cli/config');
|
|
279
|
-
console.log(`Config:
|
|
280
|
-
console.log(`Credentials:
|
|
280
|
+
console.log(`Config: ${conf.CONFIG_FILE}`);
|
|
281
|
+
console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
|
|
282
|
+
if (conf.hasLocalCredentials()) {
|
|
283
|
+
console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
|
|
284
|
+
}
|
|
285
|
+
console.log(`Auth source: ${conf.getAuthSource()}`);
|
|
281
286
|
},
|
|
282
287
|
},
|
|
283
288
|
},
|
|
@@ -764,10 +764,14 @@ The SecureNow CLI is your terminal command center. Below is every command organi
|
|
|
764
764
|
|
|
765
765
|
| Command | What It Does |
|
|
766
766
|
|---------|-------------|
|
|
767
|
-
| `securenow login` | Opens browser to authenticate |
|
|
767
|
+
| `securenow login` | Opens browser to authenticate (global session) |
|
|
768
768
|
| `securenow login --token <T>` | Authenticate with a token (for CI/CD or headless servers) |
|
|
769
|
+
| `securenow login --local` | Save credentials to this project only (per-project session) |
|
|
769
770
|
| `securenow logout` | Clear stored credentials |
|
|
770
|
-
| `securenow
|
|
771
|
+
| `securenow logout --local` | Clear project-local credentials only |
|
|
772
|
+
| `securenow whoami` | Show current session (email, API URL, auth source, expiry, default app) |
|
|
773
|
+
|
|
774
|
+
**Per-project credentials:** Use `--local` to maintain separate logins for different projects on the same machine. The CLI resolves credentials in order: `SECURENOW_TOKEN` env var → project `.securenow/credentials.json` → global `~/.securenow/credentials.json`.
|
|
771
775
|
|
|
772
776
|
### App Management
|
|
773
777
|
|
|
@@ -1313,9 +1317,15 @@ Or re-authenticate with a token:
|
|
|
1313
1317
|
npx securenow login --token <YOUR_TOKEN>
|
|
1314
1318
|
```
|
|
1315
1319
|
|
|
1320
|
+
Or set the env var directly:
|
|
1321
|
+
|
|
1322
|
+
```bash
|
|
1323
|
+
SECURENOW_TOKEN=<YOUR_JWT> npx securenow whoami
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1316
1326
|
### CLI says "Session expired"
|
|
1317
1327
|
|
|
1318
|
-
Tokens expire after a set period. Re-run `securenow login` to get a fresh session.
|
|
1328
|
+
Tokens expire after a set period. Re-run `securenow login` to get a fresh session. Use `securenow whoami` to check which credential source is active.
|
|
1319
1329
|
|
|
1320
1330
|
---
|
|
1321
1331
|
|
package/docs/API-KEYS-GUIDE.md
CHANGED
|
@@ -128,7 +128,7 @@ The firewall SDK reads this automatically on startup.
|
|
|
128
128
|
### In CI/CD
|
|
129
129
|
|
|
130
130
|
```yaml
|
|
131
|
-
# GitHub Actions example
|
|
131
|
+
# GitHub Actions example — SDK firewall key
|
|
132
132
|
env:
|
|
133
133
|
SECURENOW_API_KEY: ${{ secrets.SECURENOW_API_KEY }}
|
|
134
134
|
|
|
@@ -139,6 +139,22 @@ steps:
|
|
|
139
139
|
echo "$ISSUES" | jq '.issues | length'
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
### CLI Authentication in CI/CD
|
|
143
|
+
|
|
144
|
+
For CLI commands in CI, use the `SECURENOW_TOKEN` env var to skip file-based login:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
# GitHub Actions example — CLI auth via env var
|
|
148
|
+
env:
|
|
149
|
+
SECURENOW_TOKEN: ${{ secrets.SECURENOW_CLI_TOKEN }}
|
|
150
|
+
|
|
151
|
+
steps:
|
|
152
|
+
- run: npx securenow issues --json --status open
|
|
153
|
+
- run: npx securenow forensics "critical attacks in last 24h" --json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `SECURENOW_TOKEN` env var takes priority over any stored credentials.
|
|
157
|
+
|
|
142
158
|
---
|
|
143
159
|
|
|
144
160
|
## Key Management
|
package/package.json
CHANGED