@nextxai/cli 0.0.0-stage → 0.1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +53 -2
  4. package/dist/ai/client.js +221 -0
  5. package/dist/auth/ci.js +64 -0
  6. package/dist/auth/credentials.js +192 -0
  7. package/dist/auth/device.js +76 -0
  8. package/dist/auth/login.js +145 -0
  9. package/dist/auth/loopback.js +98 -0
  10. package/dist/auth/mcp-oauth.js +174 -0
  11. package/dist/auth/session.js +205 -0
  12. package/dist/bin/nextx.js +5 -0
  13. package/dist/commands/ai.js +514 -0
  14. package/dist/commands/api.js +87 -0
  15. package/dist/commands/auth.js +165 -0
  16. package/dist/commands/docs.js +55 -0
  17. package/dist/commands/help.js +48 -0
  18. package/dist/commands/keys.js +85 -0
  19. package/dist/commands/mcp.js +202 -0
  20. package/dist/commands/tools.js +186 -0
  21. package/dist/commands/util.js +224 -0
  22. package/dist/commands/webhooks.js +249 -0
  23. package/dist/commands/workspace.js +44 -0
  24. package/dist/core/brand.js +51 -0
  25. package/dist/core/config.js +52 -0
  26. package/dist/core/context.js +42 -0
  27. package/dist/core/errors.js +28 -0
  28. package/dist/core/http.js +170 -0
  29. package/dist/core/i18n.js +90 -0
  30. package/dist/core/lock.js +44 -0
  31. package/dist/core/output.js +74 -0
  32. package/dist/core/paths.js +29 -0
  33. package/dist/core/prompt.js +137 -0
  34. package/dist/core/version.js +13 -0
  35. package/dist/main.js +38 -0
  36. package/dist/mcp/client.js +114 -0
  37. package/dist/program.js +102 -0
  38. package/dist/repl/App.js +771 -0
  39. package/dist/repl/components.js +66 -0
  40. package/dist/repl/index.js +12 -0
  41. package/dist/repl/markdown.js +57 -0
  42. package/dist/repl/slash.js +53 -0
  43. package/package.json +62 -4
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ /** Suggestion list under the prompt (slash commands, @ mentions). */
5
+ export function Menu({ items, index }) {
6
+ const start = Math.max(0, Math.min(index - 5, items.length - 12));
7
+ const view = items.slice(start, start + 12);
8
+ const w = Math.max(...view.map((i) => i.label.length), 8) + 2;
9
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [view.map((it, k) => {
10
+ const on = start + k === index;
11
+ return (_jsxs(Text, { color: on ? "cyan" : undefined, children: [on ? "❯ " : " ", it.label.padEnd(w), _jsx(Text, { color: on ? "cyan" : "gray", children: it.hint ?? "" })] }, it.value + k));
12
+ }), items.length > view.length ? _jsx(Text, { color: "gray", children: ` … ${items.length - view.length}` }) : null] }));
13
+ }
14
+ /** Modal single select. */
15
+ export function Select({ title, items, initial = 0, onPick, hint }) {
16
+ const [i, setI] = useState(Math.min(initial, Math.max(0, items.length - 1)));
17
+ useInput((input, key) => {
18
+ if (key.escape || (key.ctrl && input === "c"))
19
+ return onPick(undefined);
20
+ if (key.upArrow || input === "k")
21
+ setI((x) => (x - 1 + items.length) % items.length);
22
+ else if (key.downArrow || input === "j")
23
+ setI((x) => (x + 1) % items.length);
24
+ else if (key.return)
25
+ onPick(items[i]?.value);
26
+ });
27
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: title }), _jsx(Menu, { items: items, index: i }), _jsx(Text, { color: "gray", children: ` ${hint}` })] }));
28
+ }
29
+ /** Renders the prompt with a visible cursor. Key handling lives in the App (it owns history and menus). */
30
+ export function Editor({ value, cursor, placeholder, prefix = "›", disabled }) {
31
+ const lines = value.split("\n");
32
+ let pos = cursor;
33
+ return (_jsx(Box, { flexDirection: "column", children: value === "" ? (_jsxs(Text, { children: [_jsx(Text, { bold: true, color: disabled ? "gray" : "white", children: `${prefix} ` }), !disabled ? _jsx(Text, { inverse: true, children: " " }) : null, _jsx(Text, { color: "gray", children: placeholder ?? "" })] })) : (lines.map((line, n) => {
34
+ const here = pos >= 0 && pos <= line.length;
35
+ const before = here ? line.slice(0, pos) : line;
36
+ const at = here ? line.slice(pos, pos + 1) || " " : "";
37
+ const after = here ? line.slice(pos + 1) : "";
38
+ pos -= line.length + 1;
39
+ return (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: n === 0 ? `${prefix} ` : " " }), before, here && !disabled ? _jsx(Text, { inverse: true, children: at }) : at, after] }, n));
40
+ })) }));
41
+ }
42
+ /** Pure editing operations on (value, cursor). */
43
+ export const edit = {
44
+ insert(v, cur, s) {
45
+ return [v.slice(0, cur) + s + v.slice(cur), cur + s.length];
46
+ },
47
+ backspace(v, cur) {
48
+ if (cur === 0)
49
+ return [v, 0];
50
+ return [v.slice(0, cur - 1) + v.slice(cur), cur - 1];
51
+ },
52
+ del(v, cur) {
53
+ return [v.slice(0, cur) + v.slice(cur + 1), cur];
54
+ },
55
+ wordBack(v, cur) {
56
+ const left = v.slice(0, cur).replace(/\s*\S+\s*$/, "");
57
+ return [left + v.slice(cur), left.length];
58
+ },
59
+ home(v, cur) {
60
+ return v.lastIndexOf("\n", cur - 1) + 1;
61
+ },
62
+ end(v, cur) {
63
+ const n = v.indexOf("\n", cur);
64
+ return n < 0 ? v.length : n;
65
+ },
66
+ };
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from "ink";
3
+ import { App } from "./App.js";
4
+ import { resolveConversation } from "../commands/ai.js";
5
+ export async function startRepl(opts) {
6
+ const sessionId = await resolveConversation(opts.flags);
7
+ const resumed = Boolean(opts.flags.continue || opts.flags.resume);
8
+ const app = render(_jsx(App, { sessionId: sessionId, resumed: resumed, initialQuestion: opts.initialQuestion, files: (opts.flags.file ?? []).slice(0, 3) }), {
9
+ exitOnCtrlC: false,
10
+ });
11
+ await app.waitUntilExit();
12
+ }
@@ -0,0 +1,57 @@
1
+ // Small Markdown → ANSI renderer for answers: headings, bold, italics, inline code, lists, tables, links.
2
+ import { c, table } from "../core/output.js";
3
+ function inline(s) {
4
+ return s
5
+ .replace(/`([^`]+)`/g, (_, x) => c.cyan(x))
6
+ .replace(/\*\*([^*]+)\*\*/g, (_, x) => c.bold(x))
7
+ .replace(/(^|[^*])\*([^*\n]+)\*/g, (_, p, x) => p + c.dim(x))
8
+ .replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, (_, t, u) => `${t} ${c.dim(u)}`);
9
+ }
10
+ function isTableRow(l) {
11
+ return /^\s*\|.*\|\s*$/.test(l);
12
+ }
13
+ export function markdown(src) {
14
+ const lines = src.replace(/\r/g, "").split("\n");
15
+ const out = [];
16
+ let inCode = false;
17
+ for (let i = 0; i < lines.length; i++) {
18
+ const l = lines[i];
19
+ if (/^```/.test(l)) {
20
+ inCode = !inCode;
21
+ continue;
22
+ }
23
+ if (inCode) {
24
+ out.push(c.cyan(` ${l}`));
25
+ continue;
26
+ }
27
+ if (isTableRow(l) && isTableRow(lines[i + 1] ?? "") && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
28
+ const cells = (row) => row.trim().replace(/^\||\|$/g, "").split("|").map((x) => inline(x.trim()));
29
+ const head = cells(l);
30
+ const rows = [];
31
+ i += 2;
32
+ while (i < lines.length && isTableRow(lines[i]))
33
+ rows.push(cells(lines[i++]));
34
+ i--;
35
+ const numeric = head.map((_, k) => rows.length > 0 && rows.every((r) => /^[\d.,\s₫%+-]*$/.test((r[k] ?? "").replace(/\u001b\[[0-9;]*m/g, ""))));
36
+ out.push(table(head, rows, numeric.map((n) => (n ? "r" : "l"))).replace(/^/gm, " "));
37
+ continue;
38
+ }
39
+ const h = /^(#{1,6})\s+(.*)$/.exec(l);
40
+ if (h) {
41
+ out.push(c.bold(inline(h[2])));
42
+ continue;
43
+ }
44
+ const li = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(l);
45
+ if (li) {
46
+ const bullet = /\d/.test(li[2]) ? li[2] : "•";
47
+ out.push(`${li[1]}${bullet} ${inline(li[3])}`);
48
+ continue;
49
+ }
50
+ if (/^\s*>\s?/.test(l)) {
51
+ out.push(c.dim(`│ ${inline(l.replace(/^\s*>\s?/, ""))}`));
52
+ continue;
53
+ }
54
+ out.push(inline(l));
55
+ }
56
+ return out.join("\n");
57
+ }
@@ -0,0 +1,53 @@
1
+ // Slash commands of the chat. One list for the menu, `/help` and `help --json` (docs are generated from it).
2
+ import { tr } from "../core/i18n.js";
3
+ import { brand } from "../core/brand.js";
4
+ const notes = () => `${brand().bin.toUpperCase()}.md`;
5
+ export const SLASH = [
6
+ { name: "help", description: () => tr("Danh sách lệnh và phím tắt", "Commands and shortcuts") },
7
+ { name: "new", description: () => tr("Hội thoại mới (giữ workspace, agent)", "New conversation (keeps workspace, agent)") },
8
+ { name: "resume", args: "[từ khoá]", description: () => tr("Mở lại hội thoại cũ", "Resume an earlier conversation") },
9
+ { name: "rename", args: "<tên>", description: () => tr("Đặt tên hội thoại này", "Rename this conversation") },
10
+ { name: "pin", description: () => tr("Ghim / bỏ ghim hội thoại", "Pin / unpin this conversation") },
11
+ { name: "workspace", args: "[mã]", description: () => tr("Đổi workspace đang làm việc", "Switch workspace") },
12
+ { name: "agent", args: "[mã]", description: () => tr("Chọn Agent: Trợ lý chung, Bán hàng, CSKH…", "Choose an Agent: general, sales, support…") },
13
+ { name: "mode", args: "read|propose|auto", description: () => tr("Chế độ quyền: chỉ đọc · đề xuất · tự ghi việc thường", "Permission mode: read · propose · auto") },
14
+ { name: "model", args: "[tên]", description: () => tr(`Đổi model: ${brand().aiName} hoặc model của bạn`, `Switch model: ${brand().aiName} or your own`) },
15
+ { name: "attach", args: "<tệp>", description: () => tr("Đính kèm tệp Excel / CSV / ảnh / tệp chữ", "Attach an Excel / CSV / image / text file") },
16
+ { name: "pending", description: () => tr("Đề xuất ghi đang chờ xác nhận", "Writes waiting for confirmation") },
17
+ { name: "undo", args: "[id]", description: () => tr("Hoàn tác lần ghi gần nhất", "Undo the last write") },
18
+ { name: "journal", description: () => tr("Nhật ký những gì AI đã ghi", "Journal of what the AI wrote") },
19
+ { name: "why", args: "[runId]", description: () => tr("Vì sao AI trả lời vậy (các bước, công cụ đã gọi)", "Why the AI answered that way (steps, tools)") },
20
+ { name: "memory", description: () => tr("AI đang nhớ gì về công ty — sửa, khoá, quên", "What the AI remembers — edit, lock, forget") },
21
+ { name: "schedule", args: '"câu" daily 08:00', description: () => tr("Lên lịch một câu hỏi chạy hằng ngày / tuần / tháng", "Schedule a question daily / weekly / monthly") },
22
+ { name: "library", description: () => tr("Ảnh, tài liệu, tệp AI đã tạo", "Images, documents and files the AI produced") },
23
+ { name: "export", args: "md|json [tệp]", description: () => tr("Xuất hội thoại ra Markdown / JSON", "Export the conversation to Markdown / JSON") },
24
+ { name: "tools", description: () => tr("Công cụ AI bạn đang được dùng (theo quyền thật)", "AI tools you can use (by your real permissions)") },
25
+ { name: "usage", description: () => tr("Lượt AI tháng này", "AI turns this month") },
26
+ { name: "feedback", args: "up|down", description: () => tr("Báo câu trả lời vừa rồi tốt / chưa tốt", "Rate the last answer") },
27
+ { name: "support", args: "<vấn đề>", description: () => tr("Chuyển cho nhân viên hỗ trợ (mở ticket)", "Hand over to a support agent (ticket)") },
28
+ { name: "status", description: () => tr("Tài khoản, workspace, môi trường, phiên bản", "Account, workspace, environment, version") },
29
+ { name: "doctor", description: () => tr("Kiểm kết nối, đồng hồ, token, gói", "Check connectivity, clock, token, plan") },
30
+ { name: "config", args: "[khoá giá_trị]", description: () => tr("Cài đặt: ngôn ngữ, màu, thông báo cập nhật", "Settings: language, color, update notices") },
31
+ { name: "init", description: () => tr(`Tạo ${notes()}: chỉ dẫn riêng cho thư mục này`, `Create ${notes()}: instructions for this folder`) },
32
+ { name: "mcp", description: () => tr("Cài máy chủ MCP vào client khác", "Install the MCP server into another client") },
33
+ { name: "logout", description: () => tr("Đăng xuất hồ sơ này", "Log out this profile") },
34
+ { name: "clear", description: () => tr("Xoá màn hình (không xoá hội thoại)", "Clear the screen (keeps the conversation)") },
35
+ { name: "exit", description: () => tr("Thoát", "Exit") },
36
+ ];
37
+ export const SHORTCUTS = [
38
+ { keys: "Enter · Shift+Enter (\\ + Enter)", description: () => tr("Gửi · xuống dòng", "Send · new line") },
39
+ { keys: "/", description: () => tr("Menu lệnh", "Command menu") },
40
+ { keys: "@", description: () => tr("Đính kèm tệp, chọn Agent cho câu này", "Attach a file, pick an Agent for this message") },
41
+ { keys: "#", description: () => tr(`Ghi nhớ nhanh vào ${notes()} của thư mục`, `Quick note into this folder's ${notes()}`) },
42
+ { keys: "Shift+Tab", description: () => tr("Xoay chế độ quyền", "Cycle permission mode") },
43
+ { keys: "Esc", description: () => tr("Dừng câu trả lời đang chạy", "Stop the running answer") },
44
+ { keys: "Esc Esc", description: () => tr("Sửa lại câu vừa gửi", "Edit the last message") },
45
+ { keys: "↑ / ↓", description: () => tr("Lịch sử câu đã gõ", "Input history") },
46
+ { keys: "Ctrl+O", description: () => tr("Mở / gập chi tiết các bước", "Expand / collapse steps") },
47
+ { keys: "Ctrl+L", description: () => tr("Xoá màn", "Clear screen") },
48
+ { keys: "Ctrl+C · Ctrl+D", description: () => tr("Huỷ dòng đang gõ (2 lần để thoát) · thoát", "Clear input (twice to exit) · exit") },
49
+ ];
50
+ export function matchSlash(prefix) {
51
+ const p = prefix.replace(/^\//, "").toLowerCase();
52
+ return SLASH.filter((s) => s.name.startsWith(p));
53
+ }
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@nextxai/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "NextX in your terminal: chat with NextX AI on your workspace data, call the API, manage keys and webhooks",
5
+ "keywords": [
6
+ "nextx",
7
+ "cli",
8
+ "ai",
9
+ "terminal",
10
+ "api",
11
+ "mcp"
12
+ ],
13
+ "homepage": "https://developers.nextx.vn/docs/cli",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/nextx-organization/nextx-cli.git"
17
+ },
18
+ "bugs": "https://github.com/nextx-organization/nextx-cli/issues",
19
+ "license": "MIT",
20
+ "author": "NextX <support@nextx.vn>",
21
+ "type": "module",
22
+ "bin": {
23
+ "nextx": "dist/bin/nextx.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ },
38
+ "scripts": {
39
+ "build": "rm -rf dist && tsc -p tsconfig.json",
40
+ "typecheck": "tsc -p tsconfig.json --noEmit",
41
+ "test": "tsx --test test/*.test.ts"
42
+ },
43
+ "dependencies": {
44
+ "commander": "14.0.3",
45
+ "ink": "6.3.1",
46
+ "ink-spinner": "5.0.0",
47
+ "ink-text-input": "6.0.0",
48
+ "open": "10.2.0",
49
+ "qrcode": "1.5.4",
50
+ "react": "19.1.10",
51
+ "smol-toml": "1.4.2",
52
+ "undici": "7.16.0"
53
+ },
54
+ "optionalDependencies": {
55
+ "@napi-rs/keyring": "1.3.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "22.20.5",
59
+ "@types/qrcode": "1.5.5",
60
+ "@types/react": "19.1.17",
61
+ "tsx": "4.20.6",
62
+ "typescript": "5.9.3"
63
+ }
64
+ }