@biznetgio/neo-app 0.4.26 → 0.4.28
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/package.json +1 -1
- package/src/deploySource.js +4 -1
- package/src/mcp/tools.js +52 -5
package/package.json
CHANGED
package/src/deploySource.js
CHANGED
|
@@ -140,7 +140,7 @@ function looksStatic(dir) {
|
|
|
140
140
|
// With `dryRun`, it performs NO create/update/upload/deploy — only the GETs needed to
|
|
141
141
|
// build a plan — and returns { dryRun:true, plan } (JNG #93).
|
|
142
142
|
// Returns { pending, approvalId } or { appId, url, result }. `log(msg)` is optional.
|
|
143
|
-
export async function deploySource(api, { projectId, src, name, port, build, serviceType, environment, dryRun = false, timeout = 300, log = () => {} }) {
|
|
143
|
+
export async function deploySource(api, { projectId, src, name, port, build, serviceType, buildCommand, publishDirectory, environment, dryRun = false, timeout = 300, log = () => {} }) {
|
|
144
144
|
const defaultName =
|
|
145
145
|
src.mode === 'image' ? src.image.split('/').pop().split(':')[0]
|
|
146
146
|
: src.mode === 'git' ? basename(src.repo, '.git')
|
|
@@ -220,6 +220,9 @@ export async function deploySource(api, { projectId, src, name, port, build, ser
|
|
|
220
220
|
// A worker has no public listener: don't request a port or TLS/ingress.
|
|
221
221
|
port: isWorker ? undefined : (port ? Number(port) : undefined),
|
|
222
222
|
tls: isWorker ? false : true,
|
|
223
|
+
// static_site native hosting: build + serve a published directory (§4.3).
|
|
224
|
+
buildCommand: svcType === 'static_site' ? (buildCommand || undefined) : undefined,
|
|
225
|
+
publishDirectory: svcType === 'static_site' ? (publishDirectory || undefined) : undefined,
|
|
223
226
|
});
|
|
224
227
|
if (pending(res)) return { pending: true, approvalId: res.data.approvalId };
|
|
225
228
|
app = res.data?.application || res.data;
|
package/src/mcp/tools.js
CHANGED
|
@@ -92,9 +92,9 @@ export const TOOLS = [
|
|
|
92
92
|
const resourceTypes = [
|
|
93
93
|
{ type: 'web', create: true, deploy: true, scale: true, logs: true, variables: true, delete: true },
|
|
94
94
|
{ type: 'template', create: true, deploy: true, scale: false, logs: true, variables: true, delete: true },
|
|
95
|
-
{ type: 'static_site', create: 'via_folder', deploy: true, scale: false, logs: true, variables: true, delete: true, note: 'neo_deploy_folder serviceType=static_site (auto-detected for static-only folders
|
|
95
|
+
{ type: 'static_site', create: 'via_folder', deploy: true, scale: false, logs: true, variables: true, delete: true, nativeStatic: true, note: 'neo_deploy_folder serviceType=static_site with buildCommand/publishDirectory → native nginx static hosting (0.4.27); auto-detected for static-only folders' },
|
|
96
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)' },
|
|
97
|
-
{ type: 'database', create: true, get: true, list: true, update: true, backup: true, delete: true, note: 'neo_create/list/get/update(resize)/delete_database + backup (
|
|
97
|
+
{ type: 'database', create: true, get: true, list: true, update: true, backup: true, verify: true, delete: true, note: 'neo_create/list/get/update(resize)/delete_database + backup + verify (SQL round-trip probe); restore still TODO (handoff 4.4)' },
|
|
98
98
|
{ type: 'queue', create: true, get: true, list: true, update: true, delete: true, note: 'neo_create/list/get/update/delete_queue (0.4.15-0.4.25) (handoff 4.5)' },
|
|
99
99
|
{ type: 'cron', create: true, get: true, list: true, run: true, runs: true, delete: true, note: 'neo_create/list/get/run/list_runs/delete_cron (0.4.15-0.4.21) (handoff 4.6)' },
|
|
100
100
|
{ type: 'object_storage', create: true, get: true, list: true, objects: true, upload: true, delete: true, note: 'neo_create/list/get/delete_bucket + list/upload/presign(get)/delete objects (0.4.15-0.4.25) (handoff 4.7)' },
|
|
@@ -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', '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'],
|
|
108
|
+
lifecycleVerbs: ['plan', 'apply', 'get_operation', 'wait_operation', 'delete_stack', 'reconcile_usage', 'service_usage', 'create', 'list', 'get', 'update', 'verify', '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'],
|
|
@@ -293,6 +293,25 @@ export const TOOLS = [
|
|
|
293
293
|
return (await api.get(`/api/v1/orgs/${orgId}/credit`)).data;
|
|
294
294
|
},
|
|
295
295
|
},
|
|
296
|
+
{
|
|
297
|
+
name: 'neo_service_usage',
|
|
298
|
+
description: 'Get the metered usage + cost attributed to ONE service/resource in a project — per-service usage attribution (handoff §4). Filters the project usage breakdown to this resourceId.',
|
|
299
|
+
mutating: false,
|
|
300
|
+
schema: { type: 'object', properties: { projectId: str('Project id'), resourceId: str('Service/database/queue/bucket id (from neo_list_services/databases/…)') }, required: ['projectId', 'resourceId'] },
|
|
301
|
+
async call(api, args) {
|
|
302
|
+
const u = (await api.get(`/api/v1/projects/${args.projectId}/usage`)).data || {};
|
|
303
|
+
const lines = (u.lines || []).filter((l) => l.resourceId === args.resourceId);
|
|
304
|
+
const totalCost = Math.round(lines.reduce((sum, l) => sum + (l.cost || 0), 0));
|
|
305
|
+
return {
|
|
306
|
+
resourceId: args.resourceId,
|
|
307
|
+
period: u.period,
|
|
308
|
+
currency: u.currency,
|
|
309
|
+
lines,
|
|
310
|
+
totalCost,
|
|
311
|
+
note: lines.length ? undefined : 'no metered usage attributed to this resource yet (usage accrues hourly)',
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
},
|
|
296
315
|
{
|
|
297
316
|
name: 'neo_estimate',
|
|
298
317
|
description:
|
|
@@ -406,6 +425,8 @@ export const TOOLS = [
|
|
|
406
425
|
path: str('Local folder or .zip path'),
|
|
407
426
|
name: str('Service name (optional)'),
|
|
408
427
|
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.' },
|
|
428
|
+
buildCommand: str('static_site: build command to run before serving (e.g. "npm ci && npm run build"); omit for a pre-built folder'),
|
|
429
|
+
publishDirectory: str('static_site: directory of built files to serve via nginx (e.g. "dist"); enables native static hosting'),
|
|
409
430
|
port: int('Container port (optional; ignored for worker)'),
|
|
410
431
|
environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production. Never defaulted (handoff §2). Deploy to staging first.' },
|
|
411
432
|
dryRun: { type: 'boolean', description: 'Preview the plan without creating/uploading/deploying anything (JNG #93)' },
|
|
@@ -429,6 +450,8 @@ export const TOOLS = [
|
|
|
429
450
|
src,
|
|
430
451
|
name: args.name,
|
|
431
452
|
serviceType: args.serviceType,
|
|
453
|
+
buildCommand: args.buildCommand,
|
|
454
|
+
publishDirectory: args.publishDirectory,
|
|
432
455
|
port: args.port,
|
|
433
456
|
environment,
|
|
434
457
|
dryRun: !!args.dryRun,
|
|
@@ -522,6 +545,16 @@ export const TOOLS = [
|
|
|
522
545
|
return redactSecrets(result(status, data));
|
|
523
546
|
},
|
|
524
547
|
},
|
|
548
|
+
{
|
|
549
|
+
name: 'neo_verify_database',
|
|
550
|
+
description: 'Functionally verify a managed database (handoff §8.1): runs an in-cluster probe that opens the DB and does a SQL round-trip (SELECT 1), returning {verified, log} — WITHOUT handing you any credentials. Postgres only for now. Synchronous (waits up to ~60s for the probe).',
|
|
551
|
+
mutating: true,
|
|
552
|
+
schema: { type: 'object', properties: { databaseId: str('Database id (postgres)') }, required: ['databaseId'] },
|
|
553
|
+
async call(api, args) {
|
|
554
|
+
const { status, data } = await api.post(`/api/v1/databases/${args.databaseId}/verify`);
|
|
555
|
+
return result(status, data);
|
|
556
|
+
},
|
|
557
|
+
},
|
|
525
558
|
{
|
|
526
559
|
name: 'neo_list_backups',
|
|
527
560
|
description: 'List configured database backups in a project (handoff 4.4): schedule, destination, last run status.',
|
|
@@ -849,6 +882,8 @@ export const TOOLS = [
|
|
|
849
882
|
kind: { type: 'string', enum: ['app', 'database', 'queue', 'bucket'] },
|
|
850
883
|
name: str('Unique resource name within this stack (bindings reference it)'),
|
|
851
884
|
serviceType: str('app: web | worker | static_site'),
|
|
885
|
+
buildCommand: str('app/static_site: build command (e.g. "npm ci && npm run build")'),
|
|
886
|
+
publishDirectory: str('static_site: built-files directory to serve (e.g. "dist")'),
|
|
852
887
|
image: str('app: prebuilt image reference'),
|
|
853
888
|
repoUrl: str('app: git repository URL'),
|
|
854
889
|
branch: str('app: git branch'),
|
|
@@ -968,6 +1003,16 @@ export const TOOLS = [
|
|
|
968
1003
|
const plan = (await api.get(`/api/v1/plans/${args.planId}`)).data;
|
|
969
1004
|
let usage = null;
|
|
970
1005
|
try { usage = (await api.get(`/api/v1/projects/${plan.projectId}/usage`)).data; } catch { /* usage may be unavailable */ }
|
|
1006
|
+
// Per-resource actuals: attribute the project usage lines to this plan's resources.
|
|
1007
|
+
const planIds = new Set(Object.values(plan.result || {}).map((v) => v && v.id).filter(Boolean));
|
|
1008
|
+
const perResource = {};
|
|
1009
|
+
for (const l of (usage?.lines || [])) {
|
|
1010
|
+
if (!planIds.has(l.resourceId)) continue;
|
|
1011
|
+
const r = perResource[l.resourceId] || { resourceName: l.resourceName, resourceType: l.resourceType, cost: 0 };
|
|
1012
|
+
r.cost += l.cost || 0;
|
|
1013
|
+
perResource[l.resourceId] = r;
|
|
1014
|
+
}
|
|
1015
|
+
const actualForPlan = Math.round(Object.values(perResource).reduce((s, r) => s + r.cost, 0));
|
|
971
1016
|
return {
|
|
972
1017
|
planId: args.planId,
|
|
973
1018
|
projectId: plan.projectId,
|
|
@@ -975,8 +1020,10 @@ export const TOOLS = [
|
|
|
975
1020
|
currency: plan.cost?.currency,
|
|
976
1021
|
priceBook: plan.cost?.priceBook,
|
|
977
1022
|
variableSkus: plan.cost?.variable || [],
|
|
978
|
-
|
|
979
|
-
|
|
1023
|
+
actualForPlanResources: actualForPlan,
|
|
1024
|
+
perResource,
|
|
1025
|
+
projectUsageTotal: usage?.totalCost,
|
|
1026
|
+
note: 'estimatedMonthly is the FIXED monthly compute/storage estimate; actualForPlanResources sums the metered cost of THIS plan\'s resources so far (accrues hourly — ~0 just after apply). perResource breaks it down.',
|
|
980
1027
|
};
|
|
981
1028
|
},
|
|
982
1029
|
},
|