@biznetgio/neo-app 0.3.0 → 0.4.0
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 +1 -1
- package/package.json +1 -1
- package/prompt.md +2 -2
- package/src/commands/deploy.js +118 -0
- package/src/index.js +14 -1
- package/src/skill/SKILL.md +1 -1
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 (
|
|
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
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.
|
|
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 **
|
|
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
|
+
}
|
package/src/index.js
CHANGED
|
@@ -27,12 +27,17 @@ ${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>
|
|
30
31
|
project list List projects in your organization (find ids to link)
|
|
31
32
|
status | whoami Show connected account, org, environment, linked project
|
|
32
33
|
mcp Run the local stdio MCP server (used by agents)
|
|
33
34
|
|
|
34
35
|
${c.bold('Options')}
|
|
35
|
-
--project <id>
|
|
36
|
+
--project <id> Target/link a project (agent setup, deploy)
|
|
37
|
+
--image <ref> Deploy a prebuilt image (deploy)
|
|
38
|
+
--repo <git-url> Deploy from a Git repo, built server-side (deploy)
|
|
39
|
+
--name <name> Service name (deploy; default derived from the source)
|
|
40
|
+
--port <n> Container port (deploy)
|
|
36
41
|
--agent <codex|claude-code> Target one agent (config install-mcp)
|
|
37
42
|
--staging | --prod Target staging (portal.neoapp.biznetgio.dev) or production (portal.app.neoapp.id)
|
|
38
43
|
--url <url> Override the control-plane URL (also NEO_APP_URL)
|
|
@@ -50,6 +55,13 @@ const options = {
|
|
|
50
55
|
project: { type: 'string' },
|
|
51
56
|
agent: { type: 'string' },
|
|
52
57
|
org: { type: 'string' },
|
|
58
|
+
image: { type: 'string' },
|
|
59
|
+
repo: { type: 'string' },
|
|
60
|
+
name: { type: 'string' },
|
|
61
|
+
port: { type: 'string' },
|
|
62
|
+
branch: { type: 'string' },
|
|
63
|
+
build: { type: 'string' },
|
|
64
|
+
timeout: { type: 'string' },
|
|
53
65
|
env: { type: 'string' },
|
|
54
66
|
url: { type: 'string' },
|
|
55
67
|
token: { type: 'string' },
|
|
@@ -86,6 +98,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
86
98
|
if ((cmd === 'agent' && sub === 'uninstall') || (cmd === 'uninstall')) return void (await (await import('./commands/uninstall.js')).uninstall(opts));
|
|
87
99
|
if (cmd === 'config' && sub === 'install-mcp') return void (await (await import('./commands/install-mcp.js')).installMcp(opts));
|
|
88
100
|
if (cmd === 'login') return void (await (await import('./commands/login.js')).login(opts));
|
|
101
|
+
if (cmd === 'deploy') return void (await (await import('./commands/deploy.js')).deploy(opts));
|
|
89
102
|
if (cmd === 'link') return void (await (await import('./commands/link.js')).link(sub, opts));
|
|
90
103
|
if (cmd === 'project' && sub === 'link') return void (await (await import('./commands/link.js')).link(positionals[2], opts));
|
|
91
104
|
if (cmd === 'project' && (sub === 'list' || sub === 'ls')) return void (await (await import('./commands/link.js')).listProjects(opts));
|
package/src/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: neo-app
|
|
3
|
-
version: 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
|
|