@biznetgio/neo-app 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -42,7 +42,7 @@ token into agent/MCP configuration.
42
42
 
43
43
  ## Authentication
44
44
 
45
- API key (`kap_...`) for now. Create one in the portal (Developer Tools → API tokens).
45
+ API key (`kap_...`) for now. Create one in the portal (Settings → API tokens).
46
46
 
47
47
  - Interactive: `neo-app login` prompts (hidden) and stores the key at
48
48
  `~/.config/neo-app/credentials.json` (mode `0600`), scoped per control-plane URL.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biznetgio/neo-app",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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
@@ -6,7 +6,7 @@ Keep this in sync with pkg/api/prompt.md (the copy the Go server embeds).
6
6
  -->
7
7
  # Set this repo up on NEO App
8
8
 
9
- **Version:** 0.3.0 · **Last updated:** 2026-10-12
9
+ **Version:** 0.4.0 · **Last updated:** 2026-10-12
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
@@ -48,7 +48,7 @@ npx -y @biznetgio/neo-app@latest agent setup --project <project-id>
48
48
  ## 2. Authenticate (human step)
49
49
 
50
50
  `agent setup` runs this when no credential is stored. The human provides their NEO App
51
- API key when prompted — create one in the portal under **Developer Tools → API tokens**.
51
+ API key when prompted — create one in the portal under **Settings → API tokens**.
52
52
  The CLI reads it directly and stores it user-only (`~/.config/neo-app/credentials.json`,
53
53
  mode 0600); it never lands in agent/MCP configuration. Relay the portal link to the
54
54
  human and let them create and paste the key into the CLI prompt — do not ask them to
@@ -0,0 +1,118 @@
1
+ // `neo-app deploy` — create-or-reuse a web service in the linked project and ship it,
2
+ // then poll until the deployment reaches a terminal state (parity with `insta deploy`).
3
+ // Sources: --image <ref> (prebuilt, no build) or --repo <git-url> (server builds it).
4
+
5
+ import { basename } from 'node:path';
6
+ import { resolveConfig } from '../config.js';
7
+ import { getCredential } from '../creds.js';
8
+ import { NeoApi } from '../api.js';
9
+ import { readLink } from '../link.js';
10
+ import { log, ok, fail, step, c } from '../util.js';
11
+
12
+ function sanitizeName(s) {
13
+ return (
14
+ String(s)
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9-]+/g, '-')
17
+ .replace(/^-+|-+$/g, '')
18
+ .slice(0, 60) || 'app'
19
+ );
20
+ }
21
+
22
+ function pending(res) {
23
+ return res.status === 202 && res.data && res.data.status === 'pending_approval';
24
+ }
25
+
26
+ export async function deploy(opts = {}) {
27
+ const cfg = resolveConfig(opts);
28
+ const cred = getCredential(cfg.baseUrl);
29
+ if (!cred?.token) {
30
+ fail(`not authenticated for ${cfg.baseUrl} — run 'neo-app login'`);
31
+ process.exit(1);
32
+ }
33
+ const api = new NeoApi({ baseUrl: cfg.baseUrl, token: cred.token });
34
+
35
+ const projectId = opts.project || readLink()?.projectId;
36
+ if (!projectId) {
37
+ fail("no project — run 'neo-app link <project-id>' or pass --project <id>");
38
+ process.exit(1);
39
+ }
40
+
41
+ const image = opts.image;
42
+ const repo = opts.repo;
43
+ if (!image && !repo) {
44
+ fail('specify a source: --image <ref> or --repo <git-url>');
45
+ process.exit(2);
46
+ }
47
+
48
+ const name = sanitizeName(
49
+ opts.name || (image ? image.split('/').pop().split(':')[0] : basename(repo, '.git'))
50
+ );
51
+ const environment = cfg.env === 'staging' ? 'staging' : 'production';
52
+
53
+ log(`${c.bold('Deploying')} ${name} ${c.dim(`→ ${cfg.env} · project ${projectId}`)}`);
54
+
55
+ // Create-or-reuse the service by name.
56
+ const existing = (await api.get(`/api/v1/projects/${projectId}/apps`)).data?.applications || [];
57
+ let app = existing.find((a) => a.name === name);
58
+
59
+ if (!app) {
60
+ const res = await api.post(`/api/v1/projects/${projectId}/apps`, {
61
+ name,
62
+ environment,
63
+ serviceType: 'web',
64
+ buildType: image ? 'image' : opts.build || 'railpack',
65
+ image: image || undefined,
66
+ repoUrl: repo || undefined,
67
+ branch: opts.branch || undefined,
68
+ port: opts.port ? Number(opts.port) : undefined,
69
+ tls: true,
70
+ });
71
+ if (pending(res)) {
72
+ ok(`create submitted — pending approval (${res.data.approvalId}). An owner/admin must approve, then re-run.`);
73
+ return;
74
+ }
75
+ app = res.data?.application || res.data;
76
+ ok(`created service ${name}`);
77
+ } else {
78
+ step(`service ${name} exists — redeploying`);
79
+ if (image && app.image !== image) await api.put(`/api/v1/apps/${app.id}`, { image });
80
+ }
81
+
82
+ // Ship it.
83
+ const dep = await api.post(`/api/v1/apps/${app.id}/deploy`);
84
+ if (pending(dep)) {
85
+ ok(`deploy submitted — pending approval (${dep.data.approvalId}).`);
86
+ return;
87
+ }
88
+ const deployId = dep.data?.id;
89
+ step(`deployment ${deployId} started`);
90
+
91
+ const result = await pollDeployment(api, deployId, Number(opts.timeout) || 300);
92
+
93
+ const view = (await api.get(`/api/v1/apps/${app.id}`)).data?.application || {};
94
+ const url = view.domain ? `https://${view.domain}` : '';
95
+ if (result === 'success') {
96
+ ok(`deployed ${name}${url ? ' → ' + c.bold(url) : ''}`);
97
+ } else {
98
+ fail(`deployment ${result}${deployId ? ` (neo-app logs via 'neo_get_logs' or the portal)` : ''}`);
99
+ process.exitCode = 1;
100
+ }
101
+ return { appId: app.id, url, result };
102
+ }
103
+
104
+ async function pollDeployment(api, id, timeoutSec) {
105
+ if (!id) return 'unknown';
106
+ const deadline = Date.now() + timeoutSec * 1000;
107
+ let last = '';
108
+ while (Date.now() < deadline) {
109
+ const d = (await api.get(`/api/v1/deployments/${id}`)).data || {};
110
+ if (d.status && d.status !== last) {
111
+ step(` ${d.status}`);
112
+ last = d.status;
113
+ }
114
+ if (d.status === 'success' || d.status === 'failed') return d.status;
115
+ await new Promise((r) => setTimeout(r, 4000));
116
+ }
117
+ return 'timeout';
118
+ }
@@ -2,6 +2,7 @@
2
2
  // InstaCloud's `insta project link`. Links the repo to a project (writes .neo-app.json)
3
3
  // after validating it against the authenticated account.
4
4
 
5
+ import { basename } from 'node:path';
5
6
  import { resolveConfig } from '../config.js';
6
7
  import { getCredential } from '../creds.js';
7
8
  import { NeoApi, ApiError } from '../api.js';
@@ -37,6 +38,37 @@ export async function link(projectId, opts = {}) {
37
38
  }
38
39
  }
39
40
 
41
+ // createProject makes a new project in the current org and links this repo to it
42
+ // (parity with `insta project create`). Name defaults to the working-directory name.
43
+ export async function createProject(name, opts = {}) {
44
+ const { cfg, api } = apiFor(opts);
45
+ const orgId = await api.resolveOrgId(opts.org);
46
+ const projName = String(name || opts.name || basename(process.cwd())).trim();
47
+ if (!projName) {
48
+ fail('usage: neo-app project create <name>');
49
+ process.exit(2);
50
+ }
51
+ let res;
52
+ try {
53
+ res = await api.post(`/api/v1/orgs/${orgId}/projects`, { name: projName });
54
+ } catch (e) {
55
+ fail(`could not create project '${projName}': ${e.message}`);
56
+ process.exit(1);
57
+ }
58
+ if (res.status === 202 && res.data?.status === 'pending_approval') {
59
+ ok(`create submitted — pending approval (${res.data.approvalId}). An owner/admin must approve.`);
60
+ return;
61
+ }
62
+ const proj = res.data?.project || res.data || {};
63
+ const environment = cfg.env === 'staging' ? 'staging' : 'production';
64
+ ok(`created project ${proj.name || projName}`);
65
+ if (proj.id) {
66
+ writeLink({ projectId: proj.id, environment, baseUrl: cfg.baseUrl, env: cfg.env, name: proj.name || projName });
67
+ ok(`linked ${proj.name || projName} / ${environment} ${c.dim('(.neo-app.json)')}`);
68
+ }
69
+ return proj;
70
+ }
71
+
40
72
  export async function listProjects(opts = {}) {
41
73
  const { api } = apiFor(opts);
42
74
  const orgId = await api.resolveOrgId(opts.org);
package/src/index.js CHANGED
@@ -27,12 +27,18 @@ ${c.bold('Commands')}
27
27
  config install-mcp Register the neo-app MCP server with detected agents
28
28
  login Authenticate with a NEO App API key (kap_...)
29
29
  link <project-id> Link this repo to a project (writes .neo-app.json)
30
+ deploy Deploy a web service: --image <ref> or --repo <git-url>
31
+ project create [name] Create a project (defaults to the dir name) and link it
30
32
  project list List projects in your organization (find ids to link)
31
33
  status | whoami Show connected account, org, environment, linked project
32
34
  mcp Run the local stdio MCP server (used by agents)
33
35
 
34
36
  ${c.bold('Options')}
35
- --project <id> Link this repo to a project (agent setup)
37
+ --project <id> Target/link a project (agent setup, deploy)
38
+ --image <ref> Deploy a prebuilt image (deploy)
39
+ --repo <git-url> Deploy from a Git repo, built server-side (deploy)
40
+ --name <name> Service name (deploy; default derived from the source)
41
+ --port <n> Container port (deploy)
36
42
  --agent <codex|claude-code> Target one agent (config install-mcp)
37
43
  --staging | --prod Target staging (portal.neoapp.biznetgio.dev) or production (portal.app.neoapp.id)
38
44
  --url <url> Override the control-plane URL (also NEO_APP_URL)
@@ -50,6 +56,13 @@ const options = {
50
56
  project: { type: 'string' },
51
57
  agent: { type: 'string' },
52
58
  org: { type: 'string' },
59
+ image: { type: 'string' },
60
+ repo: { type: 'string' },
61
+ name: { type: 'string' },
62
+ port: { type: 'string' },
63
+ branch: { type: 'string' },
64
+ build: { type: 'string' },
65
+ timeout: { type: 'string' },
53
66
  env: { type: 'string' },
54
67
  url: { type: 'string' },
55
68
  token: { type: 'string' },
@@ -86,8 +99,10 @@ export async function main(argv = process.argv.slice(2)) {
86
99
  if ((cmd === 'agent' && sub === 'uninstall') || (cmd === 'uninstall')) return void (await (await import('./commands/uninstall.js')).uninstall(opts));
87
100
  if (cmd === 'config' && sub === 'install-mcp') return void (await (await import('./commands/install-mcp.js')).installMcp(opts));
88
101
  if (cmd === 'login') return void (await (await import('./commands/login.js')).login(opts));
102
+ if (cmd === 'deploy') return void (await (await import('./commands/deploy.js')).deploy(opts));
89
103
  if (cmd === 'link') return void (await (await import('./commands/link.js')).link(sub, opts));
90
104
  if (cmd === 'project' && sub === 'link') return void (await (await import('./commands/link.js')).link(positionals[2], opts));
105
+ if (cmd === 'project' && sub === 'create') return void (await (await import('./commands/link.js')).createProject(positionals[2], opts));
91
106
  if (cmd === 'project' && (sub === 'list' || sub === 'ls')) return void (await (await import('./commands/link.js')).listProjects(opts));
92
107
  if (cmd === 'status' || cmd === 'whoami') return void (await (await import('./commands/status.js')).status(opts));
93
108
  if (cmd === 'mcp') return void (await (await import('./mcp/server.js')).runMcpServer());
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: neo-app
3
- version: 0.3.0
3
+ version: 0.4.0
4
4
  description: Deploy and operate apps on NEO App (Biznet Gio managed-app platform) via the neo-app CLI and the neo-app MCP tools. Use for deploying services, reading logs, managing env variables, scaling, and checking deployment/approval status.
5
5
  ---
6
6