securenow 5.3.2 → 5.3.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.
Files changed (3) hide show
  1. package/cli/apps.js +140 -13
  2. package/cli.js +2 -2
  3. package/package.json +1 -1
package/cli/apps.js CHANGED
@@ -4,32 +4,74 @@ const { api, requireAuth } = require('./client');
4
4
  const config = require('./config');
5
5
  const ui = require('./ui');
6
6
 
7
+ const FREE_TRIAL_URL = 'https://freetrial.securenow.ai:4318';
8
+
9
+ function instanceUrl(inst) {
10
+ if (!inst) return FREE_TRIAL_URL;
11
+ return `${inst.protocol || 'https'}://${inst.host}:4318`;
12
+ }
13
+
14
+ async function fetchInstanceMap() {
15
+ try {
16
+ const data = await api.get('/instances');
17
+ const instances = data.instances || [];
18
+ const map = {};
19
+ for (const inst of instances) {
20
+ map[inst._id] = inst;
21
+ }
22
+ return map;
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+
7
28
  async function list(args, flags) {
8
29
  requireAuth();
9
30
  const s = ui.spinner('Fetching applications');
10
31
 
11
32
  try {
12
- const data = await api.get('/applications');
13
- const apps = data.applications || [];
33
+ const [appData, instMap] = await Promise.all([
34
+ api.get('/applications'),
35
+ fetchInstanceMap(),
36
+ ]);
37
+ const apps = appData.applications || [];
14
38
  s.stop(`Found ${apps.length} application${apps.length !== 1 ? 's' : ''}`);
15
39
  console.log('');
16
40
 
17
41
  if (flags.json) {
18
- ui.json(apps);
42
+ const enriched = apps.map(app => ({
43
+ ...app,
44
+ instanceUrl: instanceUrl(app.instanceId ? instMap[app.instanceId] : null),
45
+ }));
46
+ ui.json(enriched);
19
47
  return;
20
48
  }
21
49
 
22
50
  const defaultApp = config.getDefaultApp();
23
- const rows = apps.map(app => [
24
- app.name + (app.key === defaultApp ? ui.c.cyan(' (default)') : ''),
25
- ui.c.dim(app.key),
26
- app.hosts?.length ? app.hosts.join(', ') : ui.c.dim(''),
27
- ui.timeAgo(app.createdAt),
28
- ]);
51
+ const rows = apps.map(app => {
52
+ const inst = app.instanceId ? instMap[app.instanceId] : null;
53
+ const url = instanceUrl(inst);
54
+ const instLabel = inst ? `${inst.name} ${ui.c.dim(url)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(url)}`;
55
+ return [
56
+ app.name + (app.key === defaultApp ? ui.c.cyan(' (default)') : ''),
57
+ ui.c.dim(app.key),
58
+ instLabel,
59
+ ui.timeAgo(app.createdAt),
60
+ ];
61
+ });
29
62
 
30
- ui.table(['Name', 'Key', 'Hosts', 'Created'], rows);
63
+ ui.table(['Name', 'Key', 'Instance', 'Created'], rows);
31
64
  console.log('');
32
65
 
66
+ if (apps.length > 0) {
67
+ console.log(` ${ui.c.bold('Add to your .env:')}`);
68
+ const first = apps.find(a => a.key === defaultApp) || apps[0];
69
+ const firstInst = first.instanceId ? instMap[first.instanceId] : null;
70
+ console.log(` SECURENOW_APPID=${first.key}`);
71
+ console.log(` SECURENOW_INSTANCE=${instanceUrl(firstInst)}`);
72
+ console.log('');
73
+ }
74
+
33
75
  if (!defaultApp && apps.length > 0) {
34
76
  ui.info(`Tip: Set a default app with ${ui.c.bold('securenow config set defaultApp <key>')}`);
35
77
  console.log('');
@@ -56,21 +98,36 @@ async function create(args, flags) {
56
98
  if (flags.hosts) {
57
99
  body.hosts = flags.hosts.split(',').map(h => h.trim());
58
100
  }
101
+
59
102
  if (flags.instance) {
60
103
  body.instanceId = flags.instance;
104
+ } else if (process.stdin.isTTY) {
105
+ body.instanceId = await pickInstance();
61
106
  }
62
107
 
108
+ const selectedInstanceId = body.instanceId;
109
+
63
110
  const s = ui.spinner(`Creating application "${name}"`);
64
111
  try {
65
112
  const result = await api.post('/applications', body);
66
113
  const app = result.application || result;
67
114
  s.stop(`Application created`);
68
115
 
116
+ let inst = null;
117
+ if (selectedInstanceId) {
118
+ try {
119
+ const instData = await api.get(`/instances/${selectedInstanceId}`);
120
+ inst = instData.instance || null;
121
+ } catch {}
122
+ }
123
+ const envUrl = instanceUrl(inst);
124
+
69
125
  console.log('');
70
126
  ui.keyValue([
71
127
  ['Name', app.name],
72
128
  ['Key', ui.c.green(ui.c.bold(app.key))],
73
129
  ['ID', ui.c.dim(app._id)],
130
+ ['Instance', inst ? `${inst.name} ${ui.c.dim(`(${envUrl})`)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(`(${envUrl})`)}`],
74
131
  ['Hosts', app.hosts?.length ? app.hosts.join(', ') : ui.c.dim('none')],
75
132
  ]);
76
133
 
@@ -78,12 +135,13 @@ async function create(args, flags) {
78
135
  console.log(` ${ui.c.bold('Add to your .env.local:')}`);
79
136
  console.log('');
80
137
  console.log(` SECURENOW_APPID=${app.key}`);
138
+ console.log(` SECURENOW_INSTANCE=${envUrl}`);
81
139
  console.log('');
82
140
  ui.info(`Set as default: ${ui.c.bold(`securenow config set defaultApp ${app.key}`)}`);
83
141
  console.log('');
84
142
 
85
143
  if (flags.json) {
86
- ui.json(app);
144
+ ui.json({ ...app, instanceUrl: envUrl });
87
145
  }
88
146
  } catch (err) {
89
147
  s.fail('Failed to create application');
@@ -91,6 +149,61 @@ async function create(args, flags) {
91
149
  }
92
150
  }
93
151
 
152
+ async function pickInstance() {
153
+ const s = ui.spinner('Loading instances');
154
+ let instances = [];
155
+ try {
156
+ const data = await api.get('/instances');
157
+ instances = data.instances || [];
158
+ s.stop('Instances loaded');
159
+ } catch {
160
+ s.stop('Could not load instances');
161
+ return null;
162
+ }
163
+
164
+ const FREE_TRIAL_LABEL = `${ui.c.green('Free Trial')} ${ui.c.dim('— SecureNow managed instance (no setup needed)')}`;
165
+
166
+ const choices = [{ label: FREE_TRIAL_LABEL, value: null }];
167
+
168
+ for (const inst of instances) {
169
+ const status = inst.status === 'active' ? ui.c.green('●') : ui.c.red('●');
170
+ const apps = inst.linkedApps ? ui.c.dim(` (${inst.linkedApps} app${inst.linkedApps !== 1 ? 's' : ''})`) : '';
171
+ choices.push({
172
+ label: `${status} ${inst.name}${apps} ${ui.c.dim(`[${inst._id}]`)}`,
173
+ value: inst._id,
174
+ });
175
+ }
176
+
177
+ const appUrl = config.getAppUrl();
178
+ choices.push({
179
+ label: `${ui.c.cyan('+')} Create a new instance ${ui.c.dim('(opens browser)')}`,
180
+ value: '__new__',
181
+ });
182
+
183
+ const selected = await ui.select('Which instance should this app use?', choices);
184
+
185
+ if (selected === '__new__') {
186
+ const url = `${appUrl}/dashboard/settings/instances`;
187
+ ui.info(`Opening ${ui.c.underline(url)}`);
188
+ openBrowser(url);
189
+ ui.info('Create your instance in the browser, then run this command again.');
190
+ process.exit(0);
191
+ }
192
+
193
+ return selected;
194
+ }
195
+
196
+ function openBrowser(url) {
197
+ const { execSync } = require('child_process');
198
+ try {
199
+ if (process.platform === 'darwin') execSync(`open "${url}"`);
200
+ else if (process.platform === 'win32') execSync(`start "" "${url}"`);
201
+ else execSync(`xdg-open "${url}"`);
202
+ } catch {
203
+ ui.warn(`Could not open browser. Visit: ${url}`);
204
+ }
205
+ }
206
+
94
207
  async function info(args, flags) {
95
208
  requireAuth();
96
209
 
@@ -104,10 +217,19 @@ async function info(args, flags) {
104
217
  try {
105
218
  const data = await api.get(`/applications/${id}`);
106
219
  const app = data.application || data;
220
+
221
+ let inst = null;
222
+ if (app.instanceId) {
223
+ try {
224
+ const instData = await api.get(`/instances/${app.instanceId}`);
225
+ inst = instData.instance || null;
226
+ } catch {}
227
+ }
228
+ const envUrl = instanceUrl(inst);
107
229
  s.stop('Application details loaded');
108
230
 
109
231
  if (flags.json) {
110
- ui.json(app);
232
+ ui.json({ ...app, instanceUrl: envUrl });
111
233
  return;
112
234
  }
113
235
 
@@ -117,11 +239,16 @@ async function info(args, flags) {
117
239
  ui.keyValue([
118
240
  ['Key', ui.c.bold(app.key)],
119
241
  ['ID', ui.c.dim(app._id)],
242
+ ['Instance', inst ? `${inst.name} ${ui.c.dim(`(${envUrl})`)}` : `${ui.c.green('Free Trial')} ${ui.c.dim(`(${envUrl})`)}`],
120
243
  ['Hosts', app.hosts?.length ? app.hosts.join(', ') : ui.c.dim('none')],
121
- ['Instance', app.instanceId || ui.c.dim('default')],
122
244
  ['Created', app.createdAt ? new Date(app.createdAt).toLocaleString() : '—'],
123
245
  ['Updated', app.updatedAt ? new Date(app.updatedAt).toLocaleString() : '—'],
124
246
  ]);
247
+
248
+ console.log('');
249
+ console.log(` ${ui.c.bold('Environment variables:')}`);
250
+ console.log(` SECURENOW_APPID=${app.key}`);
251
+ console.log(` SECURENOW_INSTANCE=${envUrl}`);
125
252
  console.log('');
126
253
  } catch (err) {
127
254
  s.fail('Failed to fetch application');
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const ui = require('./cli/ui');
@@ -60,7 +60,7 @@ const COMMANDS = {
60
60
  usage: 'securenow apps <subcommand> [options]',
61
61
  sub: {
62
62
  list: { desc: 'List all applications', run: (a, f) => require('./cli/apps').list(a, f) },
63
- create: { desc: 'Create a new application', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
63
+ create: { desc: 'Create a new application (interactive instance picker)', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
64
64
  info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
65
65
  delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
66
66
  default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.3.2",
3
+ "version": "5.3.4",
4
4
  "description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",