@biznetgio/neo-app 0.4.25 → 0.4.26

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 +50 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biznetgio/neo-app",
3
- "version": "0.4.25",
3
+ "version": "0.4.26",
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,7 +105,7 @@ 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: ['plan', 'apply', 'get_operation', 'wait_operation', 'create', 'list', 'get', 'update', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'scan_repo', 'bind', 'unbind', 'run_cron', 'presign_object', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
108
+ lifecycleVerbs: ['plan', 'apply', 'get_operation', 'wait_operation', 'delete_stack', 'reconcile_usage', 'create', 'list', 'get', 'update', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'scan_repo', 'bind', 'unbind', 'run_cron', 'presign_object', 'backup', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
109
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)' },
110
110
  sourceTypes: ['git', 'image', 'dockerfile', 'local', 'template'],
111
111
  environments: ['staging', 'production'],
@@ -349,6 +349,11 @@ export const TOOLS = [
349
349
  suggestedBuildType: s.type === 'docker' ? 'dockerfile' : 'railpack',
350
350
  contextDir: s.path || undefined,
351
351
  detectedDependencies: s.deps || [], // database | queue | object_storage, inferred from the manifest
352
+ // Confidence + evidence (handoff 6): an explicit Dockerfile/index.html is high
353
+ // confidence; a language marker is inferred (medium). The stack marker + any
354
+ // detectedDependencies ARE the evidence.
355
+ typeConfidence: (s.type === 'docker' || s.type === 'static') ? 'high' : 'medium',
356
+ evidence: { marker: s.type, manifestDeps: s.deps || [] },
352
357
  }));
353
358
  const anyDeps = components.some((c) => c.detectedDependencies.length);
354
359
  return {
@@ -931,6 +936,50 @@ export const TOOLS = [
931
936
  return op;
932
937
  },
933
938
  },
939
+ {
940
+ name: 'neo_delete_stack',
941
+ description:
942
+ 'Tear down a whole stack created by neo_apply_stack — deletes its resources in REVERSE dependency order (services first, then databases/queues/crons/buckets, handoff 3.6/7). Destructive and irreversible. Pass the operationId/planId from the apply.',
943
+ mutating: true,
944
+ destructive: true,
945
+ schema: { type: 'object', properties: { operationId: str('Operation/plan id from neo_apply_stack') }, required: ['operationId'] },
946
+ async call(api, args) {
947
+ const op = (await api.get(`/api/v1/plans/${args.operationId}`)).data;
948
+ const entries = Object.entries(op.result || {}).map(([name, v]) => ({ name, kind: v.kind, id: v.id })).filter((e) => e.id);
949
+ const rank = { app: 0, cron: 1, database: 2, queue: 2, bucket: 2 }; // services before foundations
950
+ entries.sort((a, b) => (rank[a.kind] ?? 3) - (rank[b.kind] ?? 3));
951
+ const path = { app: 'apps', database: 'databases', queue: 'queues', cron: 'crons', bucket: 'buckets' };
952
+ const deleted = [];
953
+ for (const e of entries) {
954
+ if (!path[e.kind]) continue;
955
+ try { await api.del(`/api/v1/${path[e.kind]}/${e.id}`); deleted.push({ name: e.name, kind: e.kind, status: 'deleted' }); }
956
+ catch (err) { deleted.push({ name: e.name, kind: e.kind, status: 'failed', error: err.message }); }
957
+ }
958
+ return { operationId: args.operationId, order: 'reverse-dependency (services → foundations)', deleted };
959
+ },
960
+ },
961
+ {
962
+ name: 'neo_reconcile_usage',
963
+ description:
964
+ 'Reconcile an approved plan\'s estimated cost against actual metered usage (handoff A2). Returns the plan\'s fixed monthly estimate + price-book snapshot + the variable SKUs, and the project\'s actual usage, so billing can be checked against what was approved. Usage accrues hourly — a freshly-applied stack shows ~0 until it runs.',
965
+ mutating: false,
966
+ schema: { type: 'object', properties: { planId: str('Plan id from neo_plan_stack/apply') }, required: ['planId'] },
967
+ async call(api, args) {
968
+ const plan = (await api.get(`/api/v1/plans/${args.planId}`)).data;
969
+ let usage = null;
970
+ try { usage = (await api.get(`/api/v1/projects/${plan.projectId}/usage`)).data; } catch { /* usage may be unavailable */ }
971
+ return {
972
+ planId: args.planId,
973
+ projectId: plan.projectId,
974
+ estimatedMonthly: plan.cost?.monthly ?? null,
975
+ currency: plan.cost?.currency,
976
+ priceBook: plan.cost?.priceBook,
977
+ variableSkus: plan.cost?.variable || [],
978
+ actualUsage: redactSecrets(usage),
979
+ note: 'estimatedMonthly is the FIXED compute/storage estimate; actualUsage is metered (incl. variable usage). Compare over a full billing period — a just-applied stack has not accrued usage yet.',
980
+ };
981
+ },
982
+ },
934
983
  // --- Cleanup (handoff 3.6): MCP can delete every resource type it can reach, so
935
984
  // an agent never leaks a resource it created. These are DESTRUCTIVE and permanent;
936
985
  // the server hard-deletes the record and tears down the running workload. ---