@nextxai/cli 0.2.0 → 0.3.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/CHANGELOG.md +13 -1
- package/dist/ai/client.js +30 -20
- package/dist/auth/session.js +3 -3
- package/dist/commands/ai.js +62 -13
- package/dist/commands/ci-policy.js +98 -0
- package/dist/commands/ci.js +28 -4
- package/dist/commands/keys.js +2 -1
- package/dist/commands/mcp.js +3 -3
- package/dist/commands/tools.js +4 -24
- package/dist/commands/webhooks.js +17 -15
- package/dist/core/brand.js +2 -3
- package/dist/core/http.js +11 -3
- package/dist/mcp/client.js +1 -1
- package/dist/repl/App.js +0 -68
- package/dist/repl/slash.js +1 -4
- package/package.json +1 -1
- package/dist/ai/nextfarm.js +0 -387
- package/dist/commands/ai-nextfarm.js +0 -109
- package/dist/core/case.js +0 -23
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { brand } from "../core/brand.js";
|
|
2
|
-
import { CliError, Exit } from "../core/errors.js";
|
|
3
|
-
import { tr } from "../core/i18n.js";
|
|
4
|
-
import { c, emit, info, ok, table } from "../core/output.js";
|
|
5
|
-
import { get } from "../core/http.js";
|
|
6
|
-
import { hasTty, confirm } from "../core/prompt.js";
|
|
7
|
-
import { requirePerson } from "../auth/session.js";
|
|
8
|
-
import * as nf from "../ai/nextfarm.js";
|
|
9
|
-
import { feedback, usage } from "../ai/client.js";
|
|
10
|
-
import { markdown } from "../repl/markdown.js";
|
|
11
|
-
import { runRoot } from "./ai.js";
|
|
12
|
-
const when = (s) => s.slice(0, 16).replace("T", " ");
|
|
13
|
-
export function registerAiNextFarm(ai, program) {
|
|
14
|
-
ai.command("why [runId]")
|
|
15
|
-
.description(tr("vì sao trợ lý trả lời vậy: các bước, công cụ đã gọi", "why the assistant answered that way: steps and tools"))
|
|
16
|
-
.action(async (runId) => {
|
|
17
|
-
await requirePerson();
|
|
18
|
-
const id = runId ?? (await get("/ai/agent-runs", { query: { page_size: 1 } })).items?.[0];
|
|
19
|
-
const rid = typeof id === "string" ? id : id?.run_id ?? id?.id;
|
|
20
|
-
if (!rid)
|
|
21
|
-
throw new CliError(tr("Chưa có lượt chạy nào.", "No runs yet."), Exit.notFound);
|
|
22
|
-
const lines = await nf.runTrace(rid);
|
|
23
|
-
emit({ runId: rid, steps: lines }, () => info(lines.join("\n")));
|
|
24
|
-
});
|
|
25
|
-
ai.command("apply <proposalId>").description(tr("xác nhận một đề xuất ghi (ghi thật)", "approve a proposed write (real write)"))
|
|
26
|
-
.action(async (proposalId) => {
|
|
27
|
-
await requirePerson();
|
|
28
|
-
if (hasTty() && !(await confirm(tr(`Ghi đề xuất ${proposalId.slice(0, 8)}?`, `Write proposal ${proposalId.slice(0, 8)}?`))))
|
|
29
|
-
return;
|
|
30
|
-
// Device, automation and money lines are refused by the server for the CLI (403 proposal.requires_app).
|
|
31
|
-
const r = await nf.confirmAction("", { kind: "", label: proposalId.slice(0, 8), confirmPrompt: "", payloadJson: "{}", writes: true, nonce: proposalId });
|
|
32
|
-
emit(r, () => (r.success ? ok(r.message) : info(c.yellow(`⚠ ${r.message}`))));
|
|
33
|
-
if (!r.success)
|
|
34
|
-
process.exitCode = Exit.notConfirmed;
|
|
35
|
-
});
|
|
36
|
-
ai.command("reject <proposalId>").description(tr("từ chối một đề xuất ghi", "reject a proposed write"))
|
|
37
|
-
.action(async (proposalId) => {
|
|
38
|
-
await nf.rejectAction(proposalId);
|
|
39
|
-
ok(tr("Đã từ chối.", "Rejected."));
|
|
40
|
-
});
|
|
41
|
-
ai.command("undo <proposalId>").description(tr("hoàn tác đề xuất đã ghi (trong 24 giờ)", "undo a written proposal (within 24 hours)"))
|
|
42
|
-
.action(async (proposalId) => {
|
|
43
|
-
if (hasTty() && !(await confirm(tr("Hoàn tác?", "Undo?"))))
|
|
44
|
-
return;
|
|
45
|
-
ok(await nf.undo(nf.undoTarget(proposalId)));
|
|
46
|
-
});
|
|
47
|
-
const mem = ai.command("memory").description(tr("trợ lý đang nhớ gì về trang trại", "what the assistant remembers about the farm"));
|
|
48
|
-
mem.command("list", { isDefault: true }).action(async () => {
|
|
49
|
-
const m = await nf.memories();
|
|
50
|
-
emit(m.items, () => info(m.items.length ? table(["ID", tr("Điều nhớ", "Memory"), tr("Dùng", "Uses")], m.items.map((x) => [x.id.slice(0, 8), x.display, x.uses])) : tr("Trợ lý chưa nhớ gì.", "Nothing remembered yet.")));
|
|
51
|
-
});
|
|
52
|
-
mem.command("forget <id>").description(tr("quên một điều (chỉ chủ trại)", "forget one item (farm owner only)")).action(async (id) => {
|
|
53
|
-
const hit = (await nf.memories()).items.find((x) => x.id.startsWith(id));
|
|
54
|
-
if (!hit)
|
|
55
|
-
throw new CliError(tr("Không thấy điều nhớ này.", "Memory not found."), Exit.notFound);
|
|
56
|
-
await nf.forgetMemory(hit.id);
|
|
57
|
-
ok(tr("Đã quên.", "Forgotten."));
|
|
58
|
-
});
|
|
59
|
-
const sch = ai.command("schedule").description(tr("câu hỏi chạy định kỳ", "recurring questions"));
|
|
60
|
-
sch.command("list", { isDefault: true }).action(async () => {
|
|
61
|
-
const list = await nf.schedules();
|
|
62
|
-
emit(list, () => info(table(["ID", tr("Tên", "Title"), tr("Lịch", "When"), tr("Lần tới", "Next")], list.map((s) => [s.id.slice(0, 8), s.title, s.when, when(s.nextRunAt)]))));
|
|
63
|
-
});
|
|
64
|
-
sch.command("add <question>")
|
|
65
|
-
.requiredOption("--at <HH:mm>", tr("giờ chạy (giờ Việt Nam)", "time (Vietnam time)"))
|
|
66
|
-
.option("--weekday <1-7>", tr("chỉ một ngày trong tuần (1 = thứ Hai); bỏ trống = hằng ngày", "one weekday only (1 = Monday); omit = daily"))
|
|
67
|
-
.action(async (question, o) => {
|
|
68
|
-
const s = await nf.createSchedule(question, o.weekday ? "weekly" : "daily", o.at.padStart(5, "0"), o.weekday ? Number(o.weekday) : undefined);
|
|
69
|
-
emit(s, () => {
|
|
70
|
-
ok(tr(`Đã lên lịch · ${s.when}`, `Scheduled · ${s.when}`));
|
|
71
|
-
info(c.dim(tr(` Mở mục Trợ lý AI trong app ${brand().mobileApp} một lần để cho phép chạy nền.`, ` Open the AI assistant in the ${brand().mobileApp} app once to allow background runs.`)));
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
sch.command("run <id>").action(async (id) => {
|
|
75
|
-
const hit = (await nf.schedules()).find((s) => s.id.startsWith(id));
|
|
76
|
-
if (!hit)
|
|
77
|
-
throw new CliError(tr("Không thấy lịch.", "Schedule not found."), Exit.notFound);
|
|
78
|
-
await nf.runSchedule(hit.id);
|
|
79
|
-
ok(tr("Đã chạy.", "Run started."));
|
|
80
|
-
});
|
|
81
|
-
sch.command("delete <id>").action(async (id) => {
|
|
82
|
-
const hit = (await nf.schedules()).find((s) => s.id.startsWith(id));
|
|
83
|
-
if (!hit)
|
|
84
|
-
throw new CliError(tr("Không thấy lịch.", "Schedule not found."), Exit.notFound);
|
|
85
|
-
await nf.deleteSchedule(hit.id);
|
|
86
|
-
ok(tr("Đã xoá.", "Deleted."));
|
|
87
|
-
});
|
|
88
|
-
ai.command("brief").description(tr("bản tin sáng mới nhất", "latest morning brief"))
|
|
89
|
-
.action(async () => {
|
|
90
|
-
const b = await nf.latestBrief();
|
|
91
|
-
emit(b ?? null, () => info(b ? `${c.bold(b.title)} ${c.dim(when(b.createdAt))}\n${markdown(b.text)}` : tr("Chưa có bản tin sáng.", "No morning brief yet.")));
|
|
92
|
-
});
|
|
93
|
-
ai.command("pest <photo> [note...]").description(tr("chẩn đoán sâu bệnh từ ảnh", "diagnose pests and diseases from a photo"))
|
|
94
|
-
.action(async (photo, note) => {
|
|
95
|
-
const root = program.opts();
|
|
96
|
-
const q = note.length ? note.join(" ") : tr("Ảnh này cây bị sâu bệnh gì? Mức độ, cách xử lý và lô nào cần kiểm tra thêm.", "What pest or disease is in this photo? Severity, treatment and which plots to check.");
|
|
97
|
-
await runRoot({ ...root, print: q, file: [photo, ...(root.file ?? [])] }, []);
|
|
98
|
-
});
|
|
99
|
-
ai.command("usage").description(tr("câu hỏi AI còn lại tuần này", "AI questions left this week"))
|
|
100
|
-
.action(async () => {
|
|
101
|
-
const u = await usage();
|
|
102
|
-
emit(u, () => info(tr(`Tuần này: đã dùng ${u.used}/${u.limit} câu · còn ${u.remaining} · mở lại 00:00 thứ Hai`, `This week: ${u.used}/${u.limit} used · ${u.remaining} left · resets Monday 00:00`)));
|
|
103
|
-
});
|
|
104
|
-
ai.command("feedback <runId> <vote>").description(tr("báo câu trả lời tốt (up) / chưa tốt (down)", "rate an answer: up / down"))
|
|
105
|
-
.action(async (runId, vote) => {
|
|
106
|
-
await feedback(runId, vote === "up");
|
|
107
|
-
ok(tr("Cảm ơn bạn đã góp ý.", "Thanks for the feedback."));
|
|
108
|
-
});
|
|
109
|
-
}
|
package/dist/core/case.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// NextFarm services speak two JSON key styles: snake_case (iam, gis, iot, harvest, ai, report, hrm, connect, sales, einvoice)
|
|
2
|
-
// and camelCase (office, notification, operator, finance). The CLI works in camelCase like the NextFarm web app, so keys
|
|
3
|
-
// are converted on the wire for snake services only. Converting a camel service breaks writes silently: `task_id` does not
|
|
4
|
-
// bind to `TaskId`, the field takes its default and the server still answers 200.
|
|
5
|
-
import { brand } from "./brand.js";
|
|
6
|
-
const SNAKE_SERVICES = ["iam", "gis", "iot", "harvest", "ai", "report", "hrm", "connect", "sales", "einvoice", "webhook"];
|
|
7
|
-
const SNAKE_URL = new RegExp(`/v\\d+/(${SNAKE_SERVICES.join("|")})(/|$|\\?)`);
|
|
8
|
-
/** Snake services with camelCase routes: Excel import keyed by column titles, consent with [JsonPropertyName], billing forwarded to operator. */
|
|
9
|
-
const EXEMPT = [/\/v\d+\/gis\/import(\/|$|\?)/, /\/v\d+\/iam\/automation-rules(\/|$|\?)/, /\/v\d+\/iam\/billing(\/|$|\?)/];
|
|
10
|
-
export function snakeOnWire(url) {
|
|
11
|
-
return brand().platform === "nextfarm" && SNAKE_URL.test(url) && !EXEMPT.some((r) => r.test(url));
|
|
12
|
-
}
|
|
13
|
-
const toSnake = (k) => k.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
|
|
14
|
-
const toCamel = (k) => k.replace(/_([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
15
|
-
function convert(v, key) {
|
|
16
|
-
if (Array.isArray(v))
|
|
17
|
-
return v.map((x) => convert(x, key));
|
|
18
|
-
if (v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype)
|
|
19
|
-
return Object.fromEntries(Object.entries(v).map(([k, x]) => [key(k), convert(x, key)]));
|
|
20
|
-
return v;
|
|
21
|
-
}
|
|
22
|
-
export const snakeKeys = (v) => convert(v, toSnake);
|
|
23
|
-
export const camelKeys = (v) => convert(v, toCamel);
|