@biznetgio/neo-app 0.4.29 → 0.4.30

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biznetgio/neo-app",
3
- "version": "0.4.29",
3
+ "version": "0.4.30",
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/prompt.md CHANGED
@@ -2,11 +2,11 @@
2
2
  NEO App setup prompt — served at https://portal.neoapp.biznetgio.dev/prompt.md (staging)
3
3
  and https://portal.app.neoapp.id/prompt.md (production).
4
4
  Contains NO credentials or user-specific secrets. Versioned; keep last-updated current.
5
- Keep this in sync with pkg/api/prompt.md (the copy the Go server embeds).
5
+ Keep this in sync with cli/prompt.md (the npm package's bundled copy).
6
6
  -->
7
7
  # Set this repo up on NEO App
8
8
 
9
- **Version:** 0.4.3 · **Last updated:** 2026-10-13
9
+ **Version:** 0.4.4 · **Last updated:** 2026-10-15
10
10
 
11
11
  These are the official setup instructions from NEO App (Biznet Gio's managed-app
12
12
  platform). If you are an AI coding agent, complete the steps below yourself by running
@@ -109,6 +109,35 @@ tool. Report the connected account, organization, environment, detected agent
109
109
  integrations, and any unresolved errors. **Do not deploy unless the human separately
110
110
  asked for a deployment.**
111
111
 
112
+ ## Deploying a resource (the MCP lifecycle)
113
+
114
+ Once setup is verified and the human has asked for a deployment, use the `neo_*` MCP
115
+ tools — never the portal UI. The lifecycle is the same for every resource type:
116
+
117
+ 1. **Understand the repo** (for code deploys): `neo_scan_repo` enumerates deployable
118
+ components in a monorepo. Ask the human which to deploy; never collapse a monorepo
119
+ into one service.
120
+ 2. **Plan and show the cost**: `neo_plan_stack` returns an immutable, priced plan
121
+ (`planId`, `planHash`, cost in IDR, expiry). Show the human the cost and get an
122
+ explicit **Approve & Create**. Individual creates (`neo_create_database`,
123
+ `neo_create_queue`) instead return the cost and create nothing until you re-call
124
+ with `confirm: true`.
125
+ 3. **Apply**: `neo_apply_stack({planId, planHash})`. If the price book changed since the
126
+ plan, apply is refused — re-plan to re-quote. A `pending_approval` result is the
127
+ approval policy working, not an error; wait for it.
128
+ 4. **Watch it reach health**: `neo_wait_operation` until terminal — the plan deploys in
129
+ dependency order and gates on health.
130
+ 5. **Prove it works**: `neo_verify_database` (SQL round-trip), read logs, hit the URL.
131
+ 6. **Clean up** when asked: `neo_delete_*` / `neo_delete_stack`, or `neo_delete_project`
132
+ (confirm-guarded) for the whole project.
133
+
134
+ For a **scheduled job**, call `neo_cron_questionnaire` first to collect the schedule and
135
+ target, then `neo_create_cron`. To expose a managed database or queue to the internet,
136
+ use `neo_enable_db_public_access` / `neo_enable_queue_public_access` (deploy it first).
137
+
138
+ Never deploy to **production** without an explicit request, and never default a missing
139
+ environment — every create/deploy names `staging` or `production`.
140
+
112
141
  ## Environment
113
142
 
114
143
  The CLI targets production by default. Set `NEO_APP_URL` to point it at a different
package/src/mcp/tools.js CHANGED
@@ -121,9 +121,9 @@ export const TOOLS = [
121
121
  { 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' },
122
122
  { 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)' },
123
123
  { type: 'database', create: true, get: true, list: true, update: true, backup: true, verify: true, publicAccess: true, delete: true, note: 'neo_create/list/get/update(resize)/delete_database + backup + verify (SQL round-trip probe) + enable/disable_db_public_access; restore still TODO (handoff 4.4)' },
124
- { type: 'queue', create: true, get: true, list: true, update: true, publicAccess: true, delete: true, note: 'neo_create/list/get/update/delete_queue + enable/disable_queue_public_access (handoff 4.5)' },
125
- { 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)' },
126
- { 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)' },
124
+ { type: 'queue', create: true, get: true, list: true, update: true, verify: true, publicAccess: true, delete: true, note: 'neo_create/list/get/update/verify(publish→persist)/delete_queue + enable/disable_queue_public_access (handoff 4.5)' },
125
+ { type: 'cron', create: true, get: true, list: true, run: true, runs: true, questionnaire: true, delete: true, note: 'neo_cron_questionnaire (guided) + create/list/get/run/list_runs/delete_cron (handoff 4.6)' },
126
+ { type: 'object_storage', create: true, get: true, list: true, objects: true, upload: true, verify: true, delete: true, note: 'neo_create/list/get/verify(put→list→delete)/delete_bucket + list/upload/presign(get)/delete objects (handoff 4.7)' },
127
127
  ];
128
128
 
129
129
  return {
@@ -131,7 +131,7 @@ export const TOOLS = [
131
131
  controlPlane,
132
132
  resourceTypes,
133
133
  // Verbs this MCP build exposes today (no create-DB/queue/cron/bucket, no costed plan/apply yet).
134
- lifecycleVerbs: ['plan', 'apply', 'get_operation', 'wait_operation', 'delete_stack', 'reconcile_usage', 'service_usage', 'create', 'list', 'get', 'update', 'verify', 'enable_public_access', 'disable_public_access', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'scan_repo', 'bind', 'unbind', 'run_cron', 'presign_object', 'backup', 'delete', 'delete_project', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
134
+ lifecycleVerbs: ['plan', 'apply', 'get_operation', 'wait_operation', 'delete_stack', 'reconcile_usage', 'service_usage', 'create', 'list', 'get', 'update', 'verify', 'enable_public_access', 'disable_public_access', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'scan_repo', 'bind', 'unbind', 'run_cron', 'cron_questionnaire', 'presign_object', 'backup', 'delete', 'delete_project', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
135
135
  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)' },
136
136
  sourceTypes: ['git', 'image', 'dockerfile', 'local', 'template'],
137
137
  environments: ['staging', 'production'],
@@ -584,6 +584,26 @@ export const TOOLS = [
584
584
  return result(status, data);
585
585
  },
586
586
  },
587
+ {
588
+ name: 'neo_verify_queue',
589
+ description: 'Functionally verify a managed queue (handoff §8): runs an in-cluster probe that connects to NATS JetStream, publishes a message to a temporary stream, and confirms it was persisted — a real publish→persist round-trip, returning {verified, log} WITHOUT handing you any credentials. The queue must be deployed. Synchronous (~90s).',
590
+ mutating: true,
591
+ schema: { type: 'object', properties: { queueId: str('Queue id (deployed)') }, required: ['queueId'] },
592
+ async call(api, args) {
593
+ const { status, data } = await api.post(`/api/v1/queues/${args.queueId}/verify`);
594
+ return result(status, data);
595
+ },
596
+ },
597
+ {
598
+ name: 'neo_verify_bucket',
599
+ description: 'Functionally verify an object-storage bucket (handoff §8/§9): does a put → list → delete round-trip of a tiny temporary object through the real OBS backend, proving the bucket credentials and endpoint work, returning {verified, put, listed, deleted}. The probe object is always cleaned up. No credentials returned.',
600
+ mutating: true,
601
+ schema: { type: 'object', properties: { bucketId: str('Bucket id') }, required: ['bucketId'] },
602
+ async call(api, args) {
603
+ const { status, data } = await api.post(`/api/v1/buckets/${args.bucketId}/verify`);
604
+ return result(status, data);
605
+ },
606
+ },
587
607
  {
588
608
  name: 'neo_list_backups',
589
609
  description: 'List configured database backups in a project (handoff 4.4): schedule, destination, last run status.',
@@ -688,9 +708,43 @@ export const TOOLS = [
688
708
  return redactSecrets(result(status, data));
689
709
  },
690
710
  },
711
+ {
712
+ name: 'neo_cron_questionnaire',
713
+ description: 'Return the ordered questions (with allowed values and which neo_create_cron field each answer fills) needed to define a scheduled job. Read-only — call this FIRST when a user wants a cron but has not given a full schedule/target, ask them these questions, then call neo_create_cron with the collected answers (handoff 4.6).',
714
+ mutating: false,
715
+ schema: { type: 'object', properties: {} },
716
+ async call() {
717
+ return {
718
+ questions: [
719
+ { field: 'name', ask: 'What should this job be called?', type: 'string' },
720
+ { field: 'mode', ask: 'What does the job run?', type: 'enum', options: [
721
+ { value: 'app_command', when: 'run a command inside an existing service, or a one-off image', fills: ['serviceId OR image', 'command'] },
722
+ { value: 'http_endpoint', when: 'call a URL on a schedule (webhook/ping)', fills: ['url', 'method'] },
723
+ ] },
724
+ { field: 'scheduleType', ask: 'How often should it run?', type: 'enum', options: [
725
+ { value: 'cron', fills: ['schedule'], hint: 'cron expression, e.g. "0 * * * *" (hourly)' },
726
+ { value: 'interval', fills: ['intervalMinutes'], hint: 'every N minutes' },
727
+ { value: 'seconds', fills: ['intervalMinutes'], hint: 'sub-minute/seconds cadence' },
728
+ { value: 'once', fills: [], hint: 'run a single time' },
729
+ ] },
730
+ { field: 'timezone', ask: 'Which timezone for the schedule? (optional, IANA, e.g. Asia/Jakarta)', type: 'string', optional: true },
731
+ { field: 'environment', ask: 'staging or production? (never defaulted)', type: 'enum', options: ['staging', 'production'], required: true },
732
+ ],
733
+ branches: {
734
+ app_command: 'Provide serviceId (an existing service) OR image, plus command (argv array).',
735
+ http_endpoint: 'Provide url and method (GET/POST/…).',
736
+ },
737
+ examples: [
738
+ { intent: 'Run `node cleanup.js` in my web service every night at 2am Jakarta', args: { name: 'nightly-cleanup', mode: 'app_command', scheduleType: 'cron', schedule: '0 2 * * *', timezone: 'Asia/Jakarta', serviceId: '<web service id>', command: ['node', 'cleanup.js'], environment: 'staging' } },
739
+ { intent: 'Ping a health URL every 15 minutes', args: { name: 'healthcheck', mode: 'http_endpoint', scheduleType: 'interval', intervalMinutes: 15, url: 'https://example.app/health', method: 'GET', environment: 'production' } },
740
+ ],
741
+ next: 'Collect the answers, then call neo_create_cron. It may return pending_approval (expected).',
742
+ };
743
+ },
744
+ },
691
745
  {
692
746
  name: 'neo_create_cron',
693
- description: 'Create a scheduled job (cron) in a project (handoff 4.6). Target is either an existing service command (mode=app_command, serviceId+command) or an HTTP endpoint (mode=http_endpoint, url+method). Schedule via scheduleType (cron|interval|once|seconds) + schedule/intervalMinutes. May return pending_approval.',
747
+ description: 'Create a scheduled job (cron) in a project (handoff 4.6). If the user gave an incomplete spec, call neo_cron_questionnaire first. Target is either an existing service command (mode=app_command, serviceId+command) or an HTTP endpoint (mode=http_endpoint, url+method). Schedule via scheduleType (cron|interval|once|seconds) + schedule/intervalMinutes. May return pending_approval.',
694
748
  mutating: true,
695
749
  schema: {
696
750
  type: 'object',