@biznetgio/neo-app 0.4.17 → 0.4.19

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 +134 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biznetgio/neo-app",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
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
@@ -105,11 +105,12 @@ export const TOOLS = [
105
105
  controlPlane,
106
106
  resourceTypes,
107
107
  // Verbs this MCP build exposes today (no create-DB/queue/cron/bucket, no costed plan/apply yet).
108
- lifecycleVerbs: ['create', 'list', 'get', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
108
+ lifecycleVerbs: ['plan', 'apply', 'get_operation', 'create', 'list', 'get', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'bind', 'unbind', 'run_cron', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
109
+ bindings: { supported: true, targets: ['neo_db', 'neo_queue'], writeOnly: true, note: 'neo_bind_resource injects DATABASE_URL/NATS_URL server-side at deploy; the secret is never returned (handoff 5)' },
109
110
  sourceTypes: ['git', 'image', 'dockerfile', 'local', 'template'],
110
111
  environments: ['staging', 'production'],
111
112
  pricing,
112
- costConsent: { estimateAvailable: true, dryRunIncludesCost: true, planApplyEnforced: false, note: 'neo_estimate + deploy dry-run show cost (0.4.16); server-enforced neo_plan_stack / Approve & Create is still TODO (handoff 3.3-3.5)' },
113
+ costConsent: { estimateAvailable: true, dryRunIncludesCost: true, planApplyEnforced: true, note: 'neo_plan_stack returns a costed immutable plan; neo_apply_stack is server-enforced (hash/expiry/one-shot) so a billable stack requires a reviewed plan (handoff 3.3-3.5)' },
113
114
  minClientVersion: pkgVersion(),
114
115
  deprecations: [],
115
116
  };
@@ -572,6 +573,137 @@ export const TOOLS = [
572
573
  return redactSecrets((await api.get(`/api/v1/buckets/${args.bucketId}`)).data);
573
574
  },
574
575
  },
576
+ // --- Typed bindings (handoff 5): attach a DB/queue to a consumer service. The
577
+ // connection string is injected as an env var SERVER-SIDE at the consumer's next
578
+ // deploy (marked a secret) — it is never returned to the agent (write-only). ---
579
+ {
580
+ name: 'neo_bind_resource',
581
+ description:
582
+ 'Bind a managed database or queue to a consumer service so its connection is injected as an environment variable at the consumer\'s next deploy — WITHOUT ever exposing the secret to you (handoff 5, write-only). envKey defaults to DATABASE_URL (neo_db) or NATS_URL (neo_queue). After binding, redeploy the consumer (neo_deploy) for it to take effect. The target and consumer must be in the same project.',
583
+ mutating: true,
584
+ schema: {
585
+ type: 'object',
586
+ properties: {
587
+ appId: str('Consumer service/app id that will receive the env var (from neo_list_services)'),
588
+ targetType: { type: 'string', enum: ['neo_db', 'neo_queue'], description: 'neo_db | neo_queue — the resource to attach' },
589
+ targetId: str('The database or queue id to bind'),
590
+ envKey: str('Env var name to inject (optional; default DATABASE_URL for neo_db, NATS_URL for neo_queue)'),
591
+ },
592
+ required: ['appId', 'targetType', 'targetId'],
593
+ },
594
+ async call(api, args) {
595
+ const { appId, ...body } = args;
596
+ const { status, data } = await api.post(`/api/v1/apps/${appId}/bindings`, body);
597
+ return redactSecrets(result(status, data));
598
+ },
599
+ },
600
+ {
601
+ name: 'neo_list_bindings',
602
+ description: 'List the resource bindings attached to a consumer service — the env keys and target resources, never the secret connection values.',
603
+ mutating: false,
604
+ schema: { type: 'object', properties: { appId: str('Consumer service/app id') }, required: ['appId'] },
605
+ async call(api, args) {
606
+ return redactSecrets((await api.get(`/api/v1/apps/${args.appId}/bindings`)).data);
607
+ },
608
+ },
609
+ {
610
+ name: 'neo_unbind_resource',
611
+ description: 'Remove a resource binding by id. The env var stops being injected on the consumer\'s next deploy. Removes the wiring only — the database/queue itself is untouched.',
612
+ mutating: true,
613
+ destructive: true,
614
+ schema: { type: 'object', properties: { bindingId: str('Binding id (from neo_list_bindings)') }, required: ['bindingId'] },
615
+ async call(api, args) {
616
+ const { status, data } = await api.del(`/api/v1/bindings/${args.bindingId}`);
617
+ return result(status, data);
618
+ },
619
+ },
620
+ // --- Costed plan / apply backbone (handoff 3.3-3.5 + 7): plan a whole stack
621
+ // zero-write, review the cost, then apply with the plan id+hash. The server
622
+ // enforces the plan (hash/expiry/one-shot) and runs it in dependency order. ---
623
+ {
624
+ name: 'neo_plan_stack',
625
+ description:
626
+ 'Create an immutable, COSTED plan for a whole stack (services + databases/queues/buckets + bindings) WITHOUT creating anything — zero-write (handoff 3.3). Returns planId, planHash, itemized monthly/hourly cost, and ordered phases. Show the cost to the user for approval, then call neo_apply_stack with planId+planHash. The plan expires in 15 minutes.',
627
+ mutating: false,
628
+ schema: {
629
+ type: 'object',
630
+ properties: {
631
+ projectId: str('Project id'),
632
+ environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production (never defaulted, handoff §2)' },
633
+ resources: {
634
+ type: 'array',
635
+ description: 'The stack resources. Each needs a unique name + kind (app | database | queue | bucket).',
636
+ items: {
637
+ type: 'object',
638
+ properties: {
639
+ kind: { type: 'string', enum: ['app', 'database', 'queue', 'bucket'] },
640
+ name: str('Unique resource name within this stack (bindings reference it)'),
641
+ serviceType: str('app: web | worker | static_site'),
642
+ image: str('app: prebuilt image reference'),
643
+ repoUrl: str('app: git repository URL'),
644
+ branch: str('app: git branch'),
645
+ port: int('app: container port'),
646
+ computeTier: str('app/db/queue: micro | small | medium | large | xlarge'),
647
+ engine: str('database: postgres | mysql'),
648
+ version: str('database: engine version, e.g. 16'),
649
+ volumeSize: str('database: storage size, e.g. 10Gi'),
650
+ quotaGB: int('bucket: storage quota GB'),
651
+ region: str('bucket: region'),
652
+ },
653
+ required: ['kind', 'name'],
654
+ },
655
+ },
656
+ bindings: {
657
+ type: 'array',
658
+ description: 'Wire a database/queue into a consumer app (injected as an env var at deploy, server-side).',
659
+ items: {
660
+ type: 'object',
661
+ properties: {
662
+ consumer: str('The consumer app resource name'),
663
+ targetType: { type: 'string', enum: ['neo_db', 'neo_queue'] },
664
+ target: str('The database/queue resource name'),
665
+ envKey: str('Env var name (optional; default DATABASE_URL / NATS_URL)'),
666
+ },
667
+ required: ['consumer', 'targetType', 'target'],
668
+ },
669
+ },
670
+ },
671
+ required: ['projectId', 'environment', 'resources'],
672
+ },
673
+ async call(api, args) {
674
+ requireEnv(args.environment);
675
+ const { projectId, ...body } = args;
676
+ const { status, data } = await api.post(`/api/v1/projects/${projectId}/plans`, body);
677
+ return result(status, data);
678
+ },
679
+ },
680
+ {
681
+ name: 'neo_apply_stack',
682
+ description:
683
+ 'Apply a planned stack. REQUIRES the planId and the exact planHash from neo_plan_stack — the SERVER rejects a missing, changed, expired, or already-applied plan, so a billable stack can never be created without first creating + reviewing its costed plan (handoff 3.4/3.5, server-enforced approval). Creates resources in dependency order (foundations → services → bindings → deploy) and returns an operationId to poll with neo_get_operation.',
684
+ mutating: true,
685
+ schema: {
686
+ type: 'object',
687
+ properties: {
688
+ planId: str('Plan id from neo_plan_stack'),
689
+ planHash: str('Plan hash from neo_plan_stack — must match exactly (proves you reviewed this plan)'),
690
+ },
691
+ required: ['planId', 'planHash'],
692
+ },
693
+ async call(api, args) {
694
+ const { status, data } = await api.post(`/api/v1/plans/${args.planId}/apply`, { planHash: args.planHash });
695
+ return result(status, data);
696
+ },
697
+ },
698
+ {
699
+ name: 'neo_get_operation',
700
+ description: 'Get the status of a stack apply (the operationId/planId from neo_apply_stack): overall status (applying | applied | failed) plus per-resource status (creating | created | deployed | failed). Poll until terminal.',
701
+ mutating: false,
702
+ schema: { type: 'object', properties: { operationId: str('Operation id (= plan id from neo_apply_stack)') }, required: ['operationId'] },
703
+ async call(api, args) {
704
+ return redactSecrets((await api.get(`/api/v1/plans/${args.operationId}`)).data);
705
+ },
706
+ },
575
707
  // --- Cleanup (handoff 3.6): MCP can delete every resource type it can reach, so
576
708
  // an agent never leaks a resource it created. These are DESTRUCTIVE and permanent;
577
709
  // the server hard-deletes the record and tears down the running workload. ---