@biznetgio/neo-app 0.4.16 → 0.4.17

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/mcp/tools.js +35 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biznetgio/neo-app",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "NEO App CLI — one-command agent onboarding, MCP server, and project ops for coding agents (Codex, Claude Code).",
5
5
  "type": "module",
6
6
  "bin": {
package/src/mcp/tools.js CHANGED
@@ -28,6 +28,17 @@ function result(status, data) {
28
28
  // (password, connection string, access/secret key, token) back to the agent.
29
29
  // Recursively replace any secret-looking value with a reference marker so the
30
30
  // agent still sees the resource's shape and non-secret identifiers.
31
+ // §2 safety: a create/deploy mutation must name its environment explicitly —
32
+ // NEVER default to production. A silent prod mutation is the exact risk the
33
+ // handoff calls out ("Never default a missing environment to production").
34
+ function requireEnv(env) {
35
+ const e = String(env || '').toLowerCase();
36
+ if (e !== 'staging' && e !== 'production') {
37
+ throw new Error('environment is required and must be "staging" or "production" — it is never defaulted (handoff §2: never silently mutate production). Test on staging first.');
38
+ }
39
+ return e;
40
+ }
41
+
31
42
  const SECRET_KEY = /(password|secret|secretkey|accesskey|connectionstring|connstr|dsn|token|privatekey|apikey)/i;
32
43
  function redactSecrets(value) {
33
44
  if (Array.isArray(value)) return value.map(redactSecrets);
@@ -85,7 +96,7 @@ export const TOOLS = [
85
96
  { type: 'worker', create: 'via_folder', deploy: true, scale: true, logs: true, variables: true, delete: true, note: 'neo_deploy_folder serviceType=worker — no public ingress; repo auto-detection still TODO (handoff 6)' },
86
97
  { type: 'database', create: true, get: true, list: true, delete: true, note: 'neo_create/list/get/delete_database (0.4.15); update/plan still TODO (handoff 4.4)' },
87
98
  { type: 'queue', create: true, get: true, list: true, delete: true, note: 'neo_create/list/get/delete_queue (0.4.15); update/plan still TODO (handoff 4.5)' },
88
- { type: 'cron', create: true, get: true, list: true, delete: true, note: 'neo_create/list/get/delete_cron (0.4.15); manual-run/history still TODO (handoff 4.6)' },
99
+ { type: 'cron', create: true, get: true, list: true, run: true, delete: true, note: 'neo_create/list/get/run/delete_cron (0.4.15-0.4.17); run-history still TODO (handoff 4.6)' },
89
100
  { type: 'object_storage', create: true, get: true, list: true, delete: true, note: 'neo_create/list/get/delete_bucket (0.4.15); object put/get still TODO (handoff 4.7)' },
90
101
  ];
91
102
 
@@ -329,12 +340,13 @@ export const TOOLS = [
329
340
  name: str('Service name (optional)'),
330
341
  serviceType: { type: 'string', enum: ['web', 'worker', 'static_site'], description: 'web (default) | worker (no public URL) | static_site. Auto-detects static_site for a static-only folder; else web.' },
331
342
  port: int('Container port (optional; ignored for worker)'),
332
- environment: str('production or staging (optional, default production)'),
343
+ environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production. Never defaulted (handoff §2). Deploy to staging first.' },
333
344
  dryRun: { type: 'boolean', description: 'Preview the plan without creating/uploading/deploying anything (JNG #93)' },
334
345
  },
335
- required: ['projectId', 'path'],
346
+ required: ['projectId', 'path', 'environment'],
336
347
  },
337
348
  async call(api, args) {
349
+ const environment = requireEnv(args.environment);
338
350
  const p = resolve(args.path);
339
351
  let src;
340
352
  try {
@@ -351,7 +363,7 @@ export const TOOLS = [
351
363
  name: args.name,
352
364
  serviceType: args.serviceType,
353
365
  port: args.port,
354
- environment: args.environment || 'production',
366
+ environment,
355
367
  dryRun: !!args.dryRun,
356
368
  });
357
369
  if (r.dryRun) return { status: 'dry_run', plan: r.plan, service: r.service, environment: r.environment, estimatedCost: r.estimatedCost };
@@ -390,14 +402,15 @@ export const TOOLS = [
390
402
  name: str('Database name'),
391
403
  engine: { type: 'string', description: 'postgres | mysql (engine)' },
392
404
  version: str('Engine version, e.g. "16" (optional)'),
393
- environment: str('production or staging (optional, default production)'),
405
+ environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production (never defaulted, handoff §2)' },
394
406
  volumeSize: str('Storage size, e.g. "10Gi" (optional)'),
395
407
  replicas: int('Read/failover replicas for HA (optional; postgres/CNPG only)'),
396
408
  deploymentMode: { type: 'string', description: 'single | ha (optional)' },
397
409
  },
398
- required: ['projectId', 'name', 'engine'],
410
+ required: ['projectId', 'name', 'engine', 'environment'],
399
411
  },
400
412
  async call(api, args) {
413
+ requireEnv(args.environment);
401
414
  const { projectId, ...body } = args;
402
415
  const { status, data } = await api.post(`/api/v1/projects/${projectId}/databases`, body);
403
416
  return redactSecrets(result(status, data));
@@ -431,13 +444,14 @@ export const TOOLS = [
431
444
  projectId: str('Project id'),
432
445
  name: str('Queue name'),
433
446
  planClass: str('Plan/engine class (optional)'),
434
- environment: str('production or staging (optional, default production)'),
447
+ environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production (never defaulted, handoff §2)' },
435
448
  volumeSize: str('Storage size, e.g. "10Gi" (optional)'),
436
449
  dlqEnabled: { type: 'boolean', description: 'Enable a dead-letter queue (optional)' },
437
450
  },
438
- required: ['projectId', 'name'],
451
+ required: ['projectId', 'name', 'environment'],
439
452
  },
440
453
  async call(api, args) {
454
+ requireEnv(args.environment);
441
455
  const { projectId, ...body } = args;
442
456
  const { status, data } = await api.post(`/api/v1/projects/${projectId}/queues`, body);
443
457
  return redactSecrets(result(status, data));
@@ -480,11 +494,12 @@ export const TOOLS = [
480
494
  command: { type: 'array', items: { type: 'string' }, description: 'Command argv (mode=app_command)' },
481
495
  url: str('Target URL (mode=http_endpoint)'),
482
496
  method: str('HTTP method (mode=http_endpoint), e.g. GET/POST'),
483
- environment: str('production or staging (optional, default production)'),
497
+ environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production (never defaulted, handoff §2)' },
484
498
  },
485
- required: ['projectId', 'name', 'mode', 'scheduleType'],
499
+ required: ['projectId', 'name', 'mode', 'scheduleType', 'environment'],
486
500
  },
487
501
  async call(api, args) {
502
+ requireEnv(args.environment);
488
503
  const { projectId, ...body } = args;
489
504
  const { status, data } = await api.post(`/api/v1/projects/${projectId}/crons`, body);
490
505
  return redactSecrets(result(status, data));
@@ -508,6 +523,16 @@ export const TOOLS = [
508
523
  return redactSecrets((await api.get(`/api/v1/crons/${args.cronId}`)).data);
509
524
  },
510
525
  },
526
+ {
527
+ name: 'neo_run_cron',
528
+ description: 'Trigger a scheduled job (cron) to run now, out of schedule (handoff 4.6). Returns the run result. Not applicable to a continuous "seconds" cron.',
529
+ mutating: true,
530
+ schema: { type: 'object', properties: { cronId: str('Cron/scheduled-job id') }, required: ['cronId'] },
531
+ async call(api, args) {
532
+ const { status, data } = await api.post(`/api/v1/crons/${args.cronId}/run`);
533
+ return redactSecrets(result(status, data));
534
+ },
535
+ },
511
536
  {
512
537
  name: 'neo_create_bucket',
513
538
  description: 'Create an S3-compatible object-storage bucket in a project (handoff 4.7). Access keys are provisioned server-side and are NOT returned (write-only). May return pending_approval.',