@kesflow/kf 0.1.11 → 0.1.13

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/SKILL.md CHANGED
@@ -16,13 +16,13 @@ description: Thao tác nội bộ KesFlow qua CLI `kf` thay cho bấm màn ops,
16
16
  ## Cách dùng
17
17
  - Danh sách thao tác (lấy trực tiếp từ server, luôn đúng): `kf --help`. Tham số một thao tác: `kf <op> --help`, `kf schema <op>`.
18
18
  - Gọi: `kf <op> --<tham-so> <giá trị>` hoặc `kf <op> --json '{...}'`. Kết quả: một dòng JSON (`--pretty` để đọc).
19
- - Không chắc thì chạy thử trước: thêm `--dry-run` (chỉ kiểm, không ghi gì).
19
+ - Không chắc thì chạy thử trước: thêm `--dry-run`: chỉ kiểm khuôn tham số, không kiểm quyền hay điều kiện nghiệp vụ, không ghi gì. Thao tác `[high]` chạy thử không cần `--yes`.
20
20
  - Tải tệp từ máy lên buổi học: `kf lms upload <tệp> --buoi-id <id> [--loai slide_pdf|slide_html|record|video_xem_truoc] [--ten ...]`.
21
21
  - Tải tệp tài nguyên lên sản phẩm: `kf tn upload <tệp> --ma <SP>` (tối đa 256 MiB, gửi tới `https://ops.kesflow.vn`; dùng `KF_NB_URL` cho host nội bộ thử/cục bộ).
22
22
  - Tìm `buoi-id`: `kf status` (lớp tôi phụ trách) → `kf lop-chi-tiet --ma <MÃ LỚP>`.
23
23
 
24
24
  ## Thao tác rủi ro cao (`[high]`, cần `--yes`)
25
- - Ví dụ `lms-dang-buoi --gui-thu true` gửi thư cho **cả lớp thật**. `lms-dang-buoi` luôn cần `--yes`, kể cả khi không gửi thư.
25
+ - Ví dụ `lms-dang-buoi --gui-thu true` gửi thư cho **cả lớp thật**. `lms-dang-buoi` luôn cần `--yes` (trừ `--dry-run`), kể cả khi không gửi thư.
26
26
  - Chỉ thêm `--yes` **sau khi người dùng đồng ý rõ trong chat** với đúng nội dung: buổi nào, lớp nào, có gửi thư không. Không tự suy ra ý định gửi thư.
27
27
  - `--yes` không thay người duyệt; người dùng nên để Claude Code hỏi quyền trước lệnh `kf … --yes`.
28
28
  - `sp-gia`/`sp-trang-thai` đổi giá và mở bán thật: chỉ dùng `--yes` sau khi người dùng nêu đúng mã sản phẩm và con số trong chat.
@@ -75,7 +75,7 @@ description: Thao tác nội bộ KesFlow qua CLI `kf` thay cho bấm màn ops,
75
75
 
76
76
  ## Xoá dữ liệu (chỉ CEO — cực kỳ nguy hiểm, không hoàn tác)
77
77
  - Luôn `kf xoa-xem --loai <san_pham|voucher|ghi_danh> --khoa <mã/id>` trước. Trình cho người dùng **nguyên văn**: từng loại dữ liệu và số dòng sẽ mất, các tín hiệu "dữ liệu thật", số dòng Base Tiền phải xoá tay, và câu "không hoàn tác được". Bị chặn (`chan`) thì dừng, báo lý do.
78
- - Dữ liệu rác: một lệnh `kf xoa … --yes` sau khi người dùng đồng ý. Dữ liệu thật: lệnh đầu trả `NB_XOA_CAN_XAC_NHAN_2` kèm mã — **dừng, hỏi người dùng lần hai**, chỉ khi họ đồng ý mới gọi lại với mã đó. Không bao giờ gửi hai xác nhận trong một lượt.
78
+ - Dữ liệu rác: một lệnh `kf xoa … --yes` sau khi người dùng đồng ý. Dữ liệu thật: lệnh đầu trả `NB_XOA_CAN_XAC_NHAN_2` kèm mã ở trường `ly_do` — **dừng, hỏi người dùng lần hai**, chỉ khi họ đồng ý mới gọi lại với `--xac-nhan-2 <mã đó>`. Không bao giờ gửi hai xác nhận trong một lượt.
79
79
  - `NB_XOA_DA_DOI`: dữ liệu đã đổi từ lúc xem — xem lại từ đầu. `NB_XOA_CHAN`: bị chặn (vd sổ chi trả affiliate, SP quản lý ở Base Tiền) — dừng, báo lý do. Giao dịch ngân hàng của đơn bị xoá không mất, quay về "Chờ xử lý".
80
80
 
81
81
  ## Hoàn tiền và hoá đơn (CEO, kế toán; admin đọc trong phạm vi)
@@ -93,9 +93,9 @@ description: Thao tác nội bộ KesFlow qua CLI `kf` thay cho bấm màn ops,
93
93
 
94
94
  ## Landing (CEO, marketing, admin loại sản phẩm)
95
95
  - Landing của sản phẩm nằm ở `kesflow.vn/<khoa-hoc|skills|challenge>/<slug>` (tiền tố theo loại sản phẩm); biến thể `b`, `c`… ở `/<tiền-tố>/<slug>/<biến-thể>`, `a` là link chính. Định dạng và các `data-*` có sẵn: handbook Marketing.
96
- - Nguồn sự thật là thư mục HTML trong repo marketing (kho tệp landing không sao lưu); pull chỉ là bản chụp.
96
+ - Nguồn sự thật là thư mục HTML trong repo `kescyz/kesflow-landings` (kho tệp landing không sao lưu); pull chỉ là bản chụp. Quy trình sửa landing: skill `kf-edit-landing`.
97
97
  - `kf landing-pull <thư-mục-trống> --ma-sp <SP> [--bien-the a] [--so N]` tải bộ đang chạy (hoặc đúng phiên bản N) về thư mục; không kèm thư mục thì chỉ in danh sách tệp.
98
- - `kf landing-push <thư-mục> --ma-sp <SP> [--bien-the a]` nén thư mục (phải có `index.html`) và đẩy thành **bản nháp**; trả `xem_truoc` (link xem trước, không đoán được, noindex) và lệnh phát hành. Cổng từ chối thì in hết lỗi (`CONG_TU_CHOI: …`): sửa tất cả rồi đẩy lại.
98
+ - `kf landing-push <thư-mục> --ma-sp <SP> [--bien-the a]` nén thư mục (phải có `index.html`) và đẩy thành **bản nháp**; trả `xem_truoc` (link xem trước, không đoán được, noindex, sống 7 ngày tới `het_han`) và lệnh phát hành. Link hết hạn hoặc lỡ gửi ra ngoài: `kf landing-preview --ma-sp <SP> --bien-the a --so N` cấp link mới, link cũ chết ngay; pull một nháp quá hạn cũng phải cấp link mới trước. Cổng từ chối thì in hết lỗi (`CONG_TU_CHOI: …`): sửa tất cả rồi đẩy lại.
99
99
  - `kf landing-publish --ma-sp <SP> --bien-the a --so N --yes` `[high]`: trước `--yes` nêu sản phẩm, biến thể, số phiên bản và đã xem link xem trước chưa. Biến thể `a` chỉ đổi `landing_url` (link affiliate trỏ landing) khi tiền tố đã định tuyến về web — kết quả có `tien_to_da_dinh_tuyen`. Lỗi thì `kf landing-rollback --ma-sp <SP> [--bien-the a] --yes` `[high]`: về bản chạy ngay trước; không còn bản trước thì GỠ landing biến thể đó (`go: true`) và trả `landing_url` cũ.
100
100
  - `kf landing-history --ma-sp <SP>` (ai đẩy/phát hành, lúc nào) · `kf landing-results --ma-sp <SP> [--tu-ngay YYYY-MM-DD] [--den-ngay …]` (đơn tạo/trả theo biến thể; đọc kèm luật thắng/hoà in trong kết quả, không kết luận sớm).
101
101
 
package/dist/api.d.ts CHANGED
@@ -2,7 +2,8 @@ export declare class CliError extends Error {
2
2
  readonly exitCode: number;
3
3
  readonly code: string;
4
4
  readonly http: number | null;
5
- constructor(message: string, exitCode: number, code: string, http?: number | null);
5
+ readonly detail: Record<string, unknown>;
6
+ constructor(message: string, exitCode: number, code: string, http?: number | null, detail?: Record<string, unknown>);
6
7
  }
7
8
  export declare function httpExit(status: number): number;
8
9
  export declare function request(baseUrl: string, token: string, path: string, body?: Record<string, unknown>): Promise<unknown>;
package/dist/api.js CHANGED
@@ -2,11 +2,13 @@ export class CliError extends Error {
2
2
  exitCode;
3
3
  code;
4
4
  http;
5
- constructor(message, exitCode, code, http = null) {
5
+ detail;
6
+ constructor(message, exitCode, code, http = null, detail = {}) {
6
7
  super(message);
7
8
  this.exitCode = exitCode;
8
9
  this.code = code;
9
10
  this.http = http;
11
+ this.detail = detail;
10
12
  }
11
13
  }
12
14
  export function httpExit(status) {
@@ -44,7 +46,15 @@ export async function request(baseUrl, token, path, body) {
44
46
  const obj = data && typeof data === 'object' ? data : {};
45
47
  const candidate = typeof obj.ma === 'string' ? obj.ma : typeof obj.loai === 'string' ? obj.loai : '';
46
48
  const code = /^[A-Za-z][A-Za-z0-9_]{0,79}$/.test(candidate) ? candidate : `HTTP_${response.status}`;
47
- throw new CliError(code, httpExit(response.status), code, response.status);
49
+ // Server error envelope (thanLoiAgent) carries what the agent must act on next, e.g. the second-confirmation
50
+ // code of NB_XOA_CAN_XAC_NHAN_2 in ly_do. Pass through only these keys, as strings or string lists.
51
+ const detail = {};
52
+ for (const key of ['ly_do', 'chi_tiet', 'khoa', 'ket_qua_khop']) {
53
+ const value = obj[key];
54
+ if (typeof value === 'string' || (Array.isArray(value) && value.every((x) => typeof x === 'string')))
55
+ detail[key] = value;
56
+ }
57
+ throw new CliError(code, httpExit(response.status), code, response.status, detail);
48
58
  }
49
59
  return data;
50
60
  }
package/dist/cli.js CHANGED
@@ -253,6 +253,6 @@ stdout.on('error', (error) => { if (error.code === 'EPIPE')
253
253
  process.exit(0); throw error; });
254
254
  run(process.argv.slice(2)).catch((error) => {
255
255
  const e = error instanceof CliError ? error : new CliError('Lỗi hệ thống', 5, 'INTERNAL');
256
- stderr.write(`${redact(JSON.stringify({ error: e.message, code: e.code, http: e.http }))}\n`);
256
+ stderr.write(`${redact(JSON.stringify({ error: e.message, code: e.code, http: e.http, ...e.detail }))}\n`);
257
257
  process.exitCode = e.exitCode;
258
258
  });
package/dist/ops.js CHANGED
@@ -83,9 +83,11 @@ export function parseBody(op, args) {
83
83
  throw new CliError(`Thiếu giá trị: --${flag}`, 2, 'USAGE');
84
84
  body[key] = scalar(props[key], raw);
85
85
  }
86
- if (op.risk === 'high' && !yes)
86
+ // --dry-run needs no --yes: the server parses the body and returns before running the op, in a rolled-back
87
+ // transaction (apps/web/lib/agent/http.ts). xac_nhan is still set so the dry run checks the same body.
88
+ if (op.risk === 'high' && !yes && !dryRun)
87
89
  throw new CliError('Thao tác rủi ro cao cần --yes', 2, 'USAGE');
88
- if (yes && op.risk === 'high')
90
+ if ((yes || dryRun) && op.risk === 'high')
89
91
  body.xac_nhan = true;
90
92
  if (dryRun)
91
93
  body.dry_run = true;
@@ -97,5 +99,5 @@ export function operationHelp(op) {
97
99
  const type = schema.type || schema.anyOf?.map((s) => s.type).filter(Boolean).join('|') || 'JSON';
98
100
  return ` --${name.replaceAll('_', '-')} <${type}>${required}`;
99
101
  }).join('\n');
100
- return `${op.id} [${op.risk}] — ${op.tom_tat}\nRoles: ${op.roles.join(', ')}\n${props}\n --json '<object>' --dry-run --pretty${op.risk === 'high' ? ' --yes (required)' : ''}`;
102
+ return `${op.id} [${op.risk}] — ${op.tom_tat}\nRoles: ${op.roles.join(', ')}\n${props}\n --json '<object>' --dry-run --pretty${op.risk === 'high' ? ' --yes (required, trừ khi --dry-run)' : ''}`;
101
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kesflow/kf",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "KesFlow agent CLI",
5
5
  "type": "module",
6
6
  "bin": {