@biznetgio/neo-app 0.4.25 → 0.4.27
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 +57 -2
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,7 +92,7 @@ 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
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 (list/create/run); 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)' },
|
|
@@ -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 {
|
|
@@ -401,6 +406,8 @@ export const TOOLS = [
|
|
|
401
406
|
path: str('Local folder or .zip path'),
|
|
402
407
|
name: str('Service name (optional)'),
|
|
403
408
|
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.' },
|
|
409
|
+
buildCommand: str('static_site: build command to run before serving (e.g. "npm ci && npm run build"); omit for a pre-built folder'),
|
|
410
|
+
publishDirectory: str('static_site: directory of built files to serve via nginx (e.g. "dist"); enables native static hosting'),
|
|
404
411
|
port: int('Container port (optional; ignored for worker)'),
|
|
405
412
|
environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production. Never defaulted (handoff §2). Deploy to staging first.' },
|
|
406
413
|
dryRun: { type: 'boolean', description: 'Preview the plan without creating/uploading/deploying anything (JNG #93)' },
|
|
@@ -424,6 +431,8 @@ export const TOOLS = [
|
|
|
424
431
|
src,
|
|
425
432
|
name: args.name,
|
|
426
433
|
serviceType: args.serviceType,
|
|
434
|
+
buildCommand: args.buildCommand,
|
|
435
|
+
publishDirectory: args.publishDirectory,
|
|
427
436
|
port: args.port,
|
|
428
437
|
environment,
|
|
429
438
|
dryRun: !!args.dryRun,
|
|
@@ -844,6 +853,8 @@ export const TOOLS = [
|
|
|
844
853
|
kind: { type: 'string', enum: ['app', 'database', 'queue', 'bucket'] },
|
|
845
854
|
name: str('Unique resource name within this stack (bindings reference it)'),
|
|
846
855
|
serviceType: str('app: web | worker | static_site'),
|
|
856
|
+
buildCommand: str('app/static_site: build command (e.g. "npm ci && npm run build")'),
|
|
857
|
+
publishDirectory: str('static_site: built-files directory to serve (e.g. "dist")'),
|
|
847
858
|
image: str('app: prebuilt image reference'),
|
|
848
859
|
repoUrl: str('app: git repository URL'),
|
|
849
860
|
branch: str('app: git branch'),
|
|
@@ -931,6 +942,50 @@ export const TOOLS = [
|
|
|
931
942
|
return op;
|
|
932
943
|
},
|
|
933
944
|
},
|
|
945
|
+
{
|
|
946
|
+
name: 'neo_delete_stack',
|
|
947
|
+
description:
|
|
948
|
+
'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.',
|
|
949
|
+
mutating: true,
|
|
950
|
+
destructive: true,
|
|
951
|
+
schema: { type: 'object', properties: { operationId: str('Operation/plan id from neo_apply_stack') }, required: ['operationId'] },
|
|
952
|
+
async call(api, args) {
|
|
953
|
+
const op = (await api.get(`/api/v1/plans/${args.operationId}`)).data;
|
|
954
|
+
const entries = Object.entries(op.result || {}).map(([name, v]) => ({ name, kind: v.kind, id: v.id })).filter((e) => e.id);
|
|
955
|
+
const rank = { app: 0, cron: 1, database: 2, queue: 2, bucket: 2 }; // services before foundations
|
|
956
|
+
entries.sort((a, b) => (rank[a.kind] ?? 3) - (rank[b.kind] ?? 3));
|
|
957
|
+
const path = { app: 'apps', database: 'databases', queue: 'queues', cron: 'crons', bucket: 'buckets' };
|
|
958
|
+
const deleted = [];
|
|
959
|
+
for (const e of entries) {
|
|
960
|
+
if (!path[e.kind]) continue;
|
|
961
|
+
try { await api.del(`/api/v1/${path[e.kind]}/${e.id}`); deleted.push({ name: e.name, kind: e.kind, status: 'deleted' }); }
|
|
962
|
+
catch (err) { deleted.push({ name: e.name, kind: e.kind, status: 'failed', error: err.message }); }
|
|
963
|
+
}
|
|
964
|
+
return { operationId: args.operationId, order: 'reverse-dependency (services → foundations)', deleted };
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: 'neo_reconcile_usage',
|
|
969
|
+
description:
|
|
970
|
+
'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.',
|
|
971
|
+
mutating: false,
|
|
972
|
+
schema: { type: 'object', properties: { planId: str('Plan id from neo_plan_stack/apply') }, required: ['planId'] },
|
|
973
|
+
async call(api, args) {
|
|
974
|
+
const plan = (await api.get(`/api/v1/plans/${args.planId}`)).data;
|
|
975
|
+
let usage = null;
|
|
976
|
+
try { usage = (await api.get(`/api/v1/projects/${plan.projectId}/usage`)).data; } catch { /* usage may be unavailable */ }
|
|
977
|
+
return {
|
|
978
|
+
planId: args.planId,
|
|
979
|
+
projectId: plan.projectId,
|
|
980
|
+
estimatedMonthly: plan.cost?.monthly ?? null,
|
|
981
|
+
currency: plan.cost?.currency,
|
|
982
|
+
priceBook: plan.cost?.priceBook,
|
|
983
|
+
variableSkus: plan.cost?.variable || [],
|
|
984
|
+
actualUsage: redactSecrets(usage),
|
|
985
|
+
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.',
|
|
986
|
+
};
|
|
987
|
+
},
|
|
988
|
+
},
|
|
934
989
|
// --- Cleanup (handoff 3.6): MCP can delete every resource type it can reach, so
|
|
935
990
|
// an agent never leaks a resource it created. These are DESTRUCTIVE and permanent;
|
|
936
991
|
// the server hard-deletes the record and tears down the running workload. ---
|