@thinkingai/ae-cli 1.0.18 → 1.0.21

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 (36) hide show
  1. package/dist/{auth-ECD46NBA.js → auth-T3ILGJKW.js} +2 -2
  2. package/dist/{auth-2UTBG5U3.js → auth-ZPA6O5KO.js} +25 -6
  3. package/dist/{chunk-7QBNU44L.js → chunk-5CCFSPAF.js} +8 -30
  4. package/dist/{chunk-LAAS6ITR.js → chunk-7G2F7IVO.js} +32 -2
  5. package/dist/{chunk-4KQ7H7DY.js → chunk-OVMQFFC2.js} +1 -1
  6. package/dist/{chunk-MR57UIKC.js → chunk-TMMUBSKW.js} +10 -0
  7. package/dist/{client-O56G2SNJ.js → client-NYAEJDQZ.js} +7 -3
  8. package/dist/{config-BU5FHGGE.js → config-QZIEYXQZ.js} +2 -2
  9. package/dist/index.js +18 -13
  10. package/dist/{raw-A3VN2B6I.js → raw-6ZPW3YII.js} +3 -3
  11. package/dist/{te-analysis-UGKESPBE.js → te-analysis-CFQBXUCO.js} +93 -7
  12. package/dist/{te-audience-THZNVHYB.js → te-audience-WPBBWADL.js} +3 -3
  13. package/dist/{te-common-QZOUME3S.js → te-common-GL3KBZPD.js} +3 -3
  14. package/dist/{te-community-AZVW3NFX.js → te-community-3XEJNSQL.js} +3 -3
  15. package/dist/{te-dataops-OE5PX2K6.js → te-dataops-H4WSMCG5.js} +3 -3
  16. package/dist/{te-engage-RYGEQTYK.js → te-engage-2VFM37ZM.js} +3 -3
  17. package/dist/te-kb-EHHYPYBN.js +407 -0
  18. package/dist/{te-meta-MGO5BCMG.js → te-meta-BO45GW32.js} +7 -7
  19. package/package.json +4 -1
  20. package/skills/ae-analysis/SKILL.md +69 -37
  21. package/skills/ae-analysis/references/build_event_analysis_qp.md +132 -0
  22. package/skills/ae-analysis/references/build_funnel_analysis_qp.md +143 -0
  23. package/skills/ae-analysis/references/build_prop_analysis_qp.md +122 -0
  24. package/skills/ae-analysis/references/build_retention_analysis_qp.md +141 -0
  25. package/skills/ae-analysis/references/get_analysis_query_schema.md +22 -11
  26. package/skills/ae-analysis/references/get_metric.md +7 -1
  27. package/skills/ae-analysis/references/get_report_definition.md +4 -0
  28. package/skills/ae-analysis/references/list_dashboards.md +2 -0
  29. package/skills/ae-analysis/references/list_events.md +4 -1
  30. package/skills/ae-analysis/references/list_metrics.md +5 -0
  31. package/skills/ae-analysis/references/list_properties.md +6 -1
  32. package/skills/ae-analysis/references/list_reports.md +2 -0
  33. package/skills/ae-analysis/references/query_adhoc.md +71 -31
  34. package/skills/ae-community/SKILL.md +14 -101
  35. package/skills/ae-dataops/SKILL.md +13 -58
  36. package/skills/ae-engage/SKILL.md +15 -98
@@ -0,0 +1,407 @@
1
+ import {
2
+ httpDelete,
3
+ httpUpload
4
+ } from "./chunk-7G2F7IVO.js";
5
+ import "./chunk-OVMQFFC2.js";
6
+ import "./chunk-TMMUBSKW.js";
7
+
8
+ // src/commands/te-kb/query.ts
9
+ var API_PATH = "/agent/api/external/knowledge-bases/query";
10
+ function buildBody(ctx) {
11
+ const body = {
12
+ query: ctx.str("query"),
13
+ sources: ctx.json("sources")
14
+ };
15
+ const modelId = ctx.str("model-id");
16
+ if (modelId) body.modelId = modelId;
17
+ const maxTurnsRaw = ctx.str("max-turns");
18
+ if (maxTurnsRaw !== "") body.maxTurns = ctx.num("max-turns");
19
+ return body;
20
+ }
21
+ var query = {
22
+ service: "kb",
23
+ command: "+query",
24
+ description: "Query knowledge.",
25
+ flags: [
26
+ { name: "query", type: "string", required: true, alias: "q", desc: "Natural language question to query against the knowledge bases" },
27
+ { name: "sources", type: "json", required: true, desc: 'JSON array of knowledge base refs, e.g. [{"scope":"company","name":"engineering-handbook"}]' },
28
+ { name: "model-id", type: "string", required: false, default: "AE-Auto", desc: "Model identifier (default: AE-Auto)" },
29
+ { name: "max-turns", type: "number", required: false, default: 6, desc: "Maximum reasoning turns (default: 6)" }
30
+ ],
31
+ risk: "read",
32
+ dryRun: (ctx) => ({
33
+ method: "POST",
34
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH}`,
35
+ body: buildBody(ctx)
36
+ }),
37
+ execute: async (ctx) => ctx.api("POST", API_PATH, {}, buildBody(ctx))
38
+ };
39
+
40
+ // src/commands/te-kb/add.ts
41
+ import { promises as fs } from "fs";
42
+ import * as path from "path";
43
+ import TurndownService from "turndown";
44
+ var API_PATH2 = "/agent/api/external/knowledge-bases/sources/upload";
45
+ var MD_EXT = /* @__PURE__ */ new Set([".md", ".markdown"]);
46
+ function normalizeFilesInput(raw) {
47
+ if (!Array.isArray(raw)) {
48
+ throw new Error(`--files must be a JSON array of strings, e.g. '["./a.md","./docs","https://example.com/page"]'`);
49
+ }
50
+ const items = [];
51
+ for (const v of raw) {
52
+ if (typeof v !== "string") {
53
+ throw new Error(`--files entries must be strings (got: ${JSON.stringify(v)})`);
54
+ }
55
+ const trimmed = v.trim();
56
+ if (trimmed) items.push(trimmed);
57
+ }
58
+ return items;
59
+ }
60
+ function isUrl(s) {
61
+ return /^https?:\/\//i.test(s);
62
+ }
63
+ function classifyInput(item) {
64
+ return isUrl(item) ? "url" : "path";
65
+ }
66
+ function sanitizeFilename(name) {
67
+ const cleaned = name.replace(/[\\/:*?"<>|\s]+/g, "-").replace(/^-+|-+$/g, "");
68
+ return cleaned || "document";
69
+ }
70
+ async function readLocalFile(filePath) {
71
+ const ext = path.extname(filePath).toLowerCase();
72
+ if (!MD_EXT.has(ext)) {
73
+ throw new Error(`Not a markdown file (only .md / .markdown allowed): ${filePath}`);
74
+ }
75
+ const content = await fs.readFile(filePath, "utf8");
76
+ return { filename: path.basename(filePath), content, origin: "file", source: filePath };
77
+ }
78
+ async function readDirectory(dir) {
79
+ const entries = await fs.readdir(dir, { withFileTypes: true });
80
+ const files = [];
81
+ for (const entry of entries) {
82
+ if (!entry.isFile()) continue;
83
+ const ext = path.extname(entry.name).toLowerCase();
84
+ if (!MD_EXT.has(ext)) continue;
85
+ const full = path.join(dir, entry.name);
86
+ const content = await fs.readFile(full, "utf8");
87
+ files.push({ filename: entry.name, content, origin: "dir", source: full });
88
+ }
89
+ if (files.length === 0) {
90
+ throw new Error(`No .md / .markdown files found in directory: ${dir}`);
91
+ }
92
+ return files;
93
+ }
94
+ function deriveFilenameFromUrl(rawUrl) {
95
+ try {
96
+ const u = new URL(rawUrl);
97
+ const lastSeg = u.pathname.split("/").filter(Boolean).pop();
98
+ const base = lastSeg ? lastSeg.replace(/\.[^.]+$/, "") : u.hostname;
99
+ return `${sanitizeFilename(base)}.md`;
100
+ } catch {
101
+ return `${sanitizeFilename(rawUrl)}.md`;
102
+ }
103
+ }
104
+ async function fetchAsMarkdown(rawUrl) {
105
+ const resp = await fetch(rawUrl);
106
+ if (!resp.ok) {
107
+ throw new Error(`Failed to fetch ${rawUrl}: HTTP ${resp.status}`);
108
+ }
109
+ const html = await resp.text();
110
+ const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
111
+ const markdown = turndown.turndown(html);
112
+ return {
113
+ filename: deriveFilenameFromUrl(rawUrl),
114
+ content: markdown,
115
+ origin: "url",
116
+ source: rawUrl
117
+ };
118
+ }
119
+ async function resolveLocalPath(p) {
120
+ const abs = path.resolve(p);
121
+ let stat;
122
+ try {
123
+ stat = await fs.stat(abs);
124
+ } catch {
125
+ throw new Error(`Path not found: ${p}`);
126
+ }
127
+ if (stat.isDirectory()) {
128
+ return readDirectory(abs);
129
+ }
130
+ if (stat.isFile()) {
131
+ return [await readLocalFile(abs)];
132
+ }
133
+ throw new Error(`Unsupported path (not file or directory): ${p}`);
134
+ }
135
+ async function collectFiles(ctx) {
136
+ const items = normalizeFilesInput(ctx.json("files"));
137
+ if (items.length === 0) {
138
+ throw new Error("--files must contain at least one entry");
139
+ }
140
+ const collected = [];
141
+ for (const item of items) {
142
+ if (classifyInput(item) === "url") {
143
+ collected.push(await fetchAsMarkdown(item));
144
+ } else {
145
+ collected.push(...await resolveLocalPath(item));
146
+ }
147
+ }
148
+ if (collected.length === 0) {
149
+ throw new Error("No markdown files collected from the given inputs");
150
+ }
151
+ return dedupeByFilename(collected);
152
+ }
153
+ function dedupeByFilename(files) {
154
+ const used = /* @__PURE__ */ new Map();
155
+ return files.map((f) => {
156
+ const count = used.get(f.filename) ?? 0;
157
+ used.set(f.filename, count + 1);
158
+ if (count === 0) return f;
159
+ const ext = path.extname(f.filename);
160
+ const stem = f.filename.slice(0, f.filename.length - ext.length);
161
+ return { ...f, filename: `${stem}-${count}${ext}` };
162
+ });
163
+ }
164
+ function buildForm(name, files) {
165
+ const form = new FormData();
166
+ form.append("name", name);
167
+ for (const f of files) {
168
+ form.append("files", new Blob([f.content], { type: "text/markdown" }), f.filename);
169
+ }
170
+ return form;
171
+ }
172
+ var add = {
173
+ service: "kb",
174
+ command: "+add",
175
+ description: "Upload markdown sources to a knowledge. --files accepts a JSON array; each entry can be a .md/.markdown file path, a directory path (all .md/.markdown inside, non-recursive), or an http(s) URL (HTML auto-converted to markdown).",
176
+ flags: [
177
+ { name: "name", type: "string", required: true, desc: "Knowledge base name" },
178
+ {
179
+ name: "files",
180
+ type: "json",
181
+ required: true,
182
+ desc: `JSON array of strings. Each entry can be a .md/.markdown file path, a directory path, or an http(s) URL. Example: '["./a.md","./docs","https://example.com/page"]'`
183
+ }
184
+ ],
185
+ risk: "write",
186
+ validate: (ctx) => {
187
+ normalizeFilesInput(ctx.json("files"));
188
+ },
189
+ dryRun: (ctx) => {
190
+ const items = normalizeFilesInput(ctx.json("files"));
191
+ return {
192
+ method: "POST",
193
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH2}`,
194
+ body: {
195
+ name: ctx.str("name"),
196
+ files: items.map((item) => ({ value: item, type: classifyInput(item) })),
197
+ contentType: "multipart/form-data"
198
+ }
199
+ };
200
+ },
201
+ execute: async (ctx) => {
202
+ const name = ctx.str("name");
203
+ const files = await collectFiles(ctx);
204
+ const form = buildForm(name, files);
205
+ const result = await httpUpload(API_PATH2, form, {}, ctx.host());
206
+ return {
207
+ uploaded: files.map((f) => ({ filename: f.filename, origin: f.origin, source: f.source })),
208
+ result
209
+ };
210
+ }
211
+ };
212
+
213
+ // src/commands/te-kb/compile.ts
214
+ var API_PATH3 = "/agent/api/external/knowledge-bases/compile";
215
+ var VALID_MODES = /* @__PURE__ */ new Set(["incremental", "full"]);
216
+ function getMode(ctx) {
217
+ const mode = ctx.str("mode") || "incremental";
218
+ if (!VALID_MODES.has(mode)) {
219
+ throw new Error(`Invalid --mode: ${mode}. Must be one of: incremental | full`);
220
+ }
221
+ return mode;
222
+ }
223
+ function buildBody2(ctx) {
224
+ return {
225
+ name: ctx.str("name"),
226
+ mode: getMode(ctx)
227
+ };
228
+ }
229
+ var compile = {
230
+ service: "kb",
231
+ command: "+compile",
232
+ description: "Compile a knowledge.",
233
+ flags: [
234
+ { name: "name", type: "string", required: true, desc: "Knowledge base name" },
235
+ { name: "mode", type: "string", required: false, default: "incremental", desc: "Compile mode: incremental | full (default: incremental)" }
236
+ ],
237
+ risk: "write",
238
+ validate: (ctx) => {
239
+ getMode(ctx);
240
+ },
241
+ dryRun: (ctx) => ({
242
+ method: "POST",
243
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH3}`,
244
+ body: buildBody2(ctx)
245
+ }),
246
+ execute: async (ctx) => ctx.api("POST", API_PATH3, {}, buildBody2(ctx))
247
+ };
248
+
249
+ // src/commands/te-kb/remove.ts
250
+ var API_PATH4 = "/agent/api/external/knowledge-bases";
251
+ function buildBody3(ctx) {
252
+ return { name: ctx.str("name") };
253
+ }
254
+ var remove = {
255
+ service: "kb",
256
+ command: "+remove",
257
+ description: "Delete an entire knowledge.",
258
+ flags: [
259
+ { name: "name", type: "string", required: true, desc: "Knowledge base name to delete" }
260
+ ],
261
+ risk: "write",
262
+ dryRun: (ctx) => ({
263
+ method: "DELETE",
264
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH4}`,
265
+ body: buildBody3(ctx)
266
+ }),
267
+ execute: async (ctx) => httpDelete(API_PATH4, {}, buildBody3(ctx), ctx.host())
268
+ };
269
+
270
+ // src/commands/te-kb/create.ts
271
+ var API_PATH5 = "/agent/api/external/knowledge-bases/create";
272
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["personal", "company"]);
273
+ function validateScope(scope) {
274
+ if (!VALID_SCOPES.has(scope)) {
275
+ throw new Error(`Invalid --scope: ${scope}. Must be one of: personal | company`);
276
+ }
277
+ }
278
+ function normalizeTags(raw) {
279
+ if (raw === void 0 || raw === null) return void 0;
280
+ if (!Array.isArray(raw)) {
281
+ throw new Error(`--tags must be a JSON array of strings, e.g. '["t1","t2"]'`);
282
+ }
283
+ const tags = [];
284
+ for (const v of raw) {
285
+ if (typeof v !== "string") {
286
+ throw new Error(`--tags entries must be strings (got: ${JSON.stringify(v)})`);
287
+ }
288
+ const t = v.trim();
289
+ if (t) tags.push(t);
290
+ }
291
+ return tags;
292
+ }
293
+ function buildBody4(ctx) {
294
+ const body = {
295
+ scope: ctx.str("scope"),
296
+ name: ctx.str("name")
297
+ };
298
+ const description = ctx.str("description");
299
+ if (description) body.description = description;
300
+ const tags = normalizeTags(ctx.json("tags"));
301
+ if (tags && tags.length > 0) body.tags = tags;
302
+ const projectId = ctx.str("project-id");
303
+ if (projectId) body.projectId = projectId;
304
+ const projectName = ctx.str("project-name");
305
+ if (projectName) body.projectName = projectName;
306
+ return body;
307
+ }
308
+ var create = {
309
+ service: "kb",
310
+ command: "+new",
311
+ description: "Create a new knowledge.",
312
+ flags: [
313
+ { name: "scope", type: "string", required: true, desc: "Knowledge base scope: personal | company" },
314
+ { name: "name", type: "string", required: true, desc: "Knowledge base name (\u226430 chars, unique per scope)" },
315
+ { name: "description", type: "string", required: false, desc: "Optional description (\u2264200 chars)" },
316
+ { name: "tags", type: "json", required: false, desc: `Optional JSON array of tags (max 2, each \u226415 chars). Example: '["t1","t2"]'` },
317
+ { name: "project-id", type: "string", required: false, desc: "Optional project ID to bind" },
318
+ { name: "project-name", type: "string", required: false, desc: "Optional project display name" }
319
+ ],
320
+ risk: "write",
321
+ validate: (ctx) => {
322
+ validateScope(ctx.str("scope"));
323
+ normalizeTags(ctx.json("tags"));
324
+ },
325
+ dryRun: (ctx) => ({
326
+ method: "POST",
327
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH5}`,
328
+ body: buildBody4(ctx)
329
+ }),
330
+ execute: async (ctx) => ctx.api("POST", API_PATH5, {}, buildBody4(ctx))
331
+ };
332
+
333
+ // src/commands/te-kb/rm-source.ts
334
+ var API_PATH6 = "/agent/api/external/knowledge-bases/sources";
335
+ function buildBody5(ctx) {
336
+ return {
337
+ name: ctx.str("name"),
338
+ displayName: ctx.str("display-name")
339
+ };
340
+ }
341
+ var rmSource = {
342
+ service: "kb",
343
+ command: "+rm-source",
344
+ description: "Delete a single source file inside a knowledge.",
345
+ flags: [
346
+ { name: "name", type: "string", required: true, desc: "Knowledge base name (looked up personal \u2192 company)" },
347
+ { name: "display-name", type: "string", required: true, desc: "Source file display name as uploaded (e.g. kb-1780046712-foo.md)" }
348
+ ],
349
+ risk: "write",
350
+ dryRun: (ctx) => ({
351
+ method: "DELETE",
352
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH6}`,
353
+ body: buildBody5(ctx)
354
+ }),
355
+ execute: async (ctx) => httpDelete(API_PATH6, {}, buildBody5(ctx), ctx.host())
356
+ };
357
+
358
+ // src/commands/te-kb/schema.ts
359
+ var API_PATH7 = "/agent/api/external/knowledge-bases/schema";
360
+ function buildBody6(ctx) {
361
+ const body = {
362
+ name: ctx.str("name")
363
+ };
364
+ if (ctx.bool("force")) body.force = true;
365
+ const model = ctx.str("model");
366
+ if (model) body.model = model;
367
+ return body;
368
+ }
369
+ var schema = {
370
+ service: "kb",
371
+ command: "+schema",
372
+ description: "Generate the compile schema for a knowledge base via POST /agent/api/external/knowledge-bases/schema.",
373
+ flags: [
374
+ { name: "name", type: "string", required: true, desc: "Knowledge base name (looked up personal \u2192 company)" },
375
+ { name: "force", type: "boolean", required: false, desc: "Preempt generation even when status is `generating` (use only for stuck recovery)" },
376
+ { name: "model", type: "string", required: false, desc: "Optional model displayName to use for schema generation" }
377
+ ],
378
+ risk: "write",
379
+ dryRun: (ctx) => ({
380
+ method: "POST",
381
+ url: `${ctx.host().replace(/\/$/, "")}${API_PATH7}`,
382
+ body: buildBody6(ctx)
383
+ }),
384
+ execute: async (ctx) => ctx.api("POST", API_PATH7, {}, buildBody6(ctx))
385
+ };
386
+
387
+ // src/commands/te-kb/index.ts
388
+ var commands = [
389
+ query,
390
+ add,
391
+ compile,
392
+ remove,
393
+ create,
394
+ rmSource,
395
+ schema
396
+ ];
397
+ var te_kb_default = commands;
398
+ export {
399
+ add,
400
+ compile,
401
+ create,
402
+ te_kb_default as default,
403
+ query,
404
+ remove,
405
+ rmSource,
406
+ schema
407
+ };
@@ -2,9 +2,9 @@ import {
2
2
  callMcpTool,
3
3
  parseMcpResult,
4
4
  resolveMcpUrl
5
- } from "./chunk-7QBNU44L.js";
6
- import "./chunk-4KQ7H7DY.js";
7
- import "./chunk-MR57UIKC.js";
5
+ } from "./chunk-5CCFSPAF.js";
6
+ import "./chunk-OVMQFFC2.js";
7
+ import "./chunk-TMMUBSKW.js";
8
8
 
9
9
  // src/commands/te-meta/shared.ts
10
10
  function createMcpCommand(config) {
@@ -58,7 +58,7 @@ function requiredJsonString(ctx, name) {
58
58
  // src/commands/te-meta/meta/list-events.ts
59
59
  var listEvents = createMcpCommand({
60
60
  command: "+list_events",
61
- description: "List events in the project. Query performs fuzzy matching on eventName, eventDesc and aiRemark. Supports fields/limit/offset payload governance.",
61
+ description: "List events in the project. Use for explicit metadata inspection, not as a pre-step for event/retention/funnel/prop_analysis ad-hoc builders; the builders resolve event names internally. Query performs fuzzy matching on eventName, eventDesc and aiRemark. Supports fields/limit/offset payload governance.",
62
62
  flags: [
63
63
  { name: "project_id", type: "number", required: true, desc: "Project ID", alias: "p" },
64
64
  { name: "query", type: "string", required: false, desc: "Optional keyword filter. Fuzzy match is applied to eventName, eventDesc, and aiRemark; if omitted, all events are returned.", alias: "q" },
@@ -79,7 +79,7 @@ var listEvents = createMcpCommand({
79
79
  // src/commands/te-meta/meta/list-properties.ts
80
80
  var listProperties = createMcpCommand({
81
81
  command: "+list_properties",
82
- description: "List properties in the project. Query performs fuzzy matching on propName, propDesc and aiRemark. Supports fields/limit/offset payload governance.",
82
+ description: "List properties in the project. Use for explicit metadata inspection, not as a pre-step for event/retention/funnel/prop_analysis ad-hoc builders; the builders resolve property names internally. Query performs fuzzy matching on propName, propDesc and aiRemark. Supports fields/limit/offset payload governance.",
83
83
  flags: [
84
84
  { name: "project_id", type: "number", required: true, desc: "Project ID", alias: "p" },
85
85
  { name: "scope", type: "string", required: false, desc: "Property scope: event or user" },
@@ -198,7 +198,7 @@ var createVirtualProperty = createMcpCommand({
198
198
  // src/commands/te-meta/meta/list-metrics.ts
199
199
  var listMetrics = createMcpCommand({
200
200
  command: "+list_metrics",
201
- description: "List metric metadata in the project. Query performs fuzzy matching on metricName, metricDesc, and metricRemark. Supports fields/limit/offset pagination.",
201
+ description: "List metric metadata in the project. Use for metric metadata inspection/management, not as a pre-step for event/retention/funnel/prop_analysis ad-hoc builders. For event saved metric queries, pass the metric name directly to +build_event_analysis_qp in metrics[].event. Query performs fuzzy matching on metricName, metricDesc, and metricRemark. Supports fields/limit/offset pagination.",
202
202
  flags: [
203
203
  { name: "project_id", type: "number", required: true, alias: "p", desc: "Project ID" },
204
204
  { name: "query", type: "string", required: false, alias: "q", desc: "Optional keyword filter. Fuzzy match on metricName, metricDesc, metricRemark." },
@@ -220,7 +220,7 @@ var listMetrics = createMcpCommand({
220
220
  // src/commands/te-meta/meta/get-metric.ts
221
221
  var getMetric = createMcpCommand({
222
222
  command: "+get_metric",
223
- description: "Get the definition details of a single metric",
223
+ description: "Get the definition details of a single metric. Use for metric metadata inspection/management, not as a pre-step for event ad-hoc builder; pass saved metric names directly to +build_event_analysis_qp in metrics[].event.",
224
224
  flags: [
225
225
  { name: "project_id", type: "number", required: true, alias: "p", desc: "Project ID" },
226
226
  { name: "metric_id", type: "number", required: true, alias: "m", desc: "Metric ID" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkingai/ae-cli",
3
- "version": "1.0.18",
3
+ "version": "1.0.21",
4
4
  "description": "CLI tool for ThinkingAI (AE) analytics platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "dev": "tsx src/index.ts",
18
18
  "build": "tsup src/index.ts --format esm --outDir dist",
19
19
  "verify:analysis-tools": "node scripts/verify-te-analysis-tools.mjs",
20
+ "verify:analysis-builder-dry-run": "node scripts/verify-te-analysis-builder-dry-run.mjs",
20
21
  "verify:analysis-audience-tools": "node scripts/verify-te-audience-tools.mjs",
21
22
  "verify:analysis-meta-tools": "node scripts/verify-te-meta-tools.mjs",
22
23
  "verify:analysis-common-tools": "node scripts/verify-te-common-tools.mjs",
@@ -43,10 +44,12 @@
43
44
  "cli-table3": "^0.6.5",
44
45
  "commander": "^12.1.0",
45
46
  "json-bigint": "^1.0.0",
47
+ "turndown": "^7.2.4",
46
48
  "ws": "^8.18.0"
47
49
  },
48
50
  "devDependencies": {
49
51
  "@types/node": "^22.0.0",
52
+ "@types/turndown": "^5.0.6",
50
53
  "@types/ws": "^8.5.0",
51
54
  "tsup": "^8.0.0",
52
55
  "tsx": "^4.0.0",
@@ -2,10 +2,6 @@
2
2
  name: ae-analysis
3
3
  version: 3.0.0
4
4
  description: "AE/TE/ThinkingEngine/ThinkingAI ae-cli manual for analysis-side tasks in the AE system or analysis platform: reports, dashboards, alerts, ad hoc analysis, drilldown, audience clusters, tags, tag members, metrics and metric definitions, events, properties, virtual events, virtual properties, metadata, project configuration, tracking plans, event tracking, mark times, project lists, and resource links. Use when the user asks to query, create, update, refresh, inspect, troubleshoot, govern, or manage these AE analysis assets. Must use ae-cli, read the matching references/<tool_name>.md command manual before composing commands, and never guess command names, flags, JSON payloads, project_id, resource IDs, or parameter formats."
5
- metadata:
6
- requires:
7
- bins: ["ae-cli"]
8
- cliHelp: "ae-cli --help"
9
5
  ---
10
6
 
11
7
  # ae-analysis
@@ -19,49 +15,23 @@ metadata:
19
15
 
20
16
  AE CLI (`ae-cli`) is the command-line tool for the AE / TE / ThinkingEngine analysis platform. For AE analysis-side requests, prefer `ae-cli` and this skill's reference docs over model memory.
21
17
 
22
- Authentication priority:
23
- 1. `TE_TOKEN` environment variable.
24
- 2. Cached token in `~/.ae-cli/tokens.json`, usually valid for 20 hours.
25
- 3. macOS Chrome token extraction via `ae-cli auth login`.
26
-
27
- Useful authentication commands:
28
-
29
- ```bash
30
- ae-cli auth login
31
- ae-cli auth status
32
- ae-cli auth logout
33
- ```
34
-
35
18
  Global parameters:
36
19
 
37
20
  | Parameter | Description |
38
21
  |---|---|
39
- | `--host <host>` | Target AE host. Use when the environment is not the default host. |
40
- | `--format <json|table>` | Output format. Default is JSON. |
22
+ | `--format <json\|table>` | Output format. Default is JSON. |
41
23
  | `--jq <expr>` | jq filter expression for JSON output. |
42
- | `--dry-run` | Preview the request without executing it. |
43
- | `--yes` | Skip confirmation for write operations. Use only when the user intent is explicit or automation requires it. |
44
24
 
45
25
  Output and errors:
46
26
  - Successful commands return machine-readable JSON by default.
47
27
  - Failed commands return `{ "ok": false, "error": { "type": "...", "message": "...", "hint": "..." } }` and exit non-zero.
48
- - On auth/config errors, check `ae-cli auth status`, host configuration, and the target environment before retrying.
49
28
 
50
29
  Safety constraints:
51
30
  - Read commands can execute directly after required IDs and references are verified.
52
31
  - Write commands require explicit user intent and normally keep the confirmation prompt.
53
- - Use `--dry-run` before risky or complex writes.
54
- - Never invent command names, flags, JSON payloads, `project_id`, resource IDs, field names, event names, property names, metric definitions, or date formats. Read the matching command reference and discover real project metadata first.
32
+ - Never invent command names, flags, JSON payloads, `project_id`, resource IDs, field names, event names, property names, metric definitions, or date formats. For builder-supported ad-hoc models (`event`, `retention`, `funnel`, `prop_analysis`), do not pre-discover metadata; pass the user's event/property/metric wording to the matching QP builder and let the builder resolve metadata or return clarification. For non-builder/manual workflows, read the matching command reference and discover real project metadata first.
55
33
  - **NEVER fabricate or guess resource names** (reports, dashboards, events, properties, metrics, clusters, tags, alerts). Always use list commands to discover real resources first. If a resource is not found after fuzzy search and full list fallback, explicitly tell the user "resource not found" and stop - do not proceed with fabricated names.
56
34
 
57
- Language consistency:
58
- - **Input-output language matching**: Always respond in the same language as the user's input.
59
- - User asks in Chinese → Respond in Chinese, including result interpretation and error messages
60
- - User asks in English → Respond in English
61
- - **CLI output handling**: CLI returns JSON in English, but your interpretation and summary MUST match user's language
62
- - **Resource names**: Keep original resource names as-is (don't translate), but descriptions and explanations should match user's language
63
- - **Error messages**: Translate CLI error messages to user's language when presenting results
64
-
65
35
  ## When to Use
66
36
 
67
37
  Use `ae-analysis` for all AE analysis-side work below:
@@ -147,7 +117,7 @@ Before executing ad-hoc queries (`query_adhoc`), MUST check for existing reports
147
117
  2. **Search existing reports** - Use `list_reports --query <keyword>` to find matching reports
148
118
  3. **Search existing dashboards** - Use `list_dashboards --query <keyword>` to find matching dashboards
149
119
  4. **If found** - Use `query_report_data` or `query_dashboard_report_data` to get data from existing assets
150
- 5. **If not found** - Only then use `query_adhoc` for ad-hoc analysis
120
+ 5. **If not found** - For QP builder-supported models (`event`, `retention`, `funnel`, `prop_analysis`), call the matching builder first and then call `query_adhoc` with builder `qp`; do not call schema or metadata tools between the report/dashboard miss and the builder. For all other `query_adhoc` models, use the legacy schema/metadata path.
151
121
 
152
122
  **Rationale:**
153
123
  - **Performance**: Existing reports are pre-computed and faster
@@ -167,9 +137,67 @@ Before executing ad-hoc queries (`query_adhoc`), MUST check for existing reports
167
137
  - User is exploring data for new insights (exploratory analysis)
168
138
  - No matching reports found after search + fallback
169
139
 
170
- ## Tool Groups (69)
140
+ ### E. QP_BUILDER_SUPPORTED_MODELS_ONLY
141
+
142
+ QP builder supports exactly four ad-hoc model types: `event`, `retention`, `funnel`, and `prop_analysis`.
143
+
144
+ For these four model types, QP builder is mandatory before `query_adhoc`. Do not handcraft QP from `get_analysis_query_schema`, examples, or prior knowledge.
145
+
146
+ 1. `event` -> `+build_event_analysis_qp`
147
+ 2. `retention` -> `+build_retention_analysis_qp`
148
+ 3. `funnel` -> `+build_funnel_analysis_qp`
149
+ 4. `prop_analysis` -> `+build_prop_analysis_qp`
150
+
151
+ Builder-supported model routing is:
152
+ 1. Search existing reports/dashboards if `QUERY_EXISTING_FIRST` applies.
153
+ 2. If no existing asset is used, read the matching builder reference.
154
+ 3. Call the matching builder with complete required parameters.
155
+ 4. Call `query_adhoc` only when builder returns `status=generated`.
156
+
157
+ Do not insert metadata/schema calls between steps 2 and 3 for builder-supported models. Specifically, do not call `get_analysis_query_schema`, `list_events`, `list_properties`, `list_metrics`, `get_metric`, or `get_report_definition` to prepare the builder payload. The builder resolves event/property/metric metadata internally and returns `need_clarification` when it cannot.
158
+
159
+ Event metric shortcut:
160
+ - If the user asks to query a saved/business metric through event analysis, pass the metric name/display name/remark directly as an event metric target: `--metrics '[{"event":"<metric name>"}]'`.
161
+ - Do not call `analysis_meta +list_metrics` or `analysis_meta +get_metric` first just to expand the metric definition.
162
+ - If the user provides an explicit formula, pass the formula and dependencies to `+build_event_analysis_qp`; do not convert it by reading schema or metric metadata first.
163
+
164
+ Metric result vs metric metadata:
165
+ - "Query metric result/value/trend over a time range" is an ad-hoc analysis request. Use report/dashboard search first when applicable, then builder -> `query_adhoc`.
166
+ - "Inspect/search/update/create metric definition" is metadata/governance. Only then use `analysis_meta +list_metrics`, `+get_metric`, `+create_metric`, or `+update_metric`.
167
+
168
+ For all other `query_adhoc` model types (`distribution`, `attribution`, `heat_map`, `interval`, `path`, `rank_list`, `sql`), QP builder is not supported. Use the legacy path: read `query_adhoc.md`, fetch schema/metadata as required, then construct QP manually according to the relevant schema.
169
+
170
+ Execution rule:
171
+ - If builder result `status=generated`, call `+query_adhoc` with the same `model_type` and the returned `qp`.
172
+ - If builder returns non-generated status (`need_clarification`, `invalid_argument`, `unsupported_feature`, `validation_error`), stop and ask the user for clarification instead of calling `query_adhoc`.
173
+ - Never bypass a failed builder by manually assembling QP for `event`, `retention`, `funnel`, or `prop_analysis`.
174
+
175
+ Builder payload rules:
176
+ - Before composing any builder JSON, read the matching builder reference doc. The builder references contain the required JSON shape and model-specific field differences.
177
+ - CLI flag names use snake_case, but nested JSON keys use the service DTO field names in camelCase. Correct: `startTime`, `endTime`, `relationEventPropertyName`, `eventPropertyName`. Wrong: `start_date`, `start_time`, `relation_property`, `fieldName`.
178
+ - Builder dry-run still requires the normal required flags. Do not run builder dry-run by itself.
179
+ - Do not pass placeholder `{}` or `[]` for required nested structures except when intentionally checking CLI validation. Fill required inner fields before calling the command.
180
+ - Use enum values exactly as documented. Examples: `mode=start_to_yesterday`, `operator=exists`, `relation=or`, `field.type=event_property`.
181
+ - For `exists`, `not_exists`, `is_true`, and `is_false`, omit `values`. For `between`, provide exactly two values. For other value-based operators, provide a non-empty `values` array.
182
+ - Builder tools do not execute the query and do not take `zone_offset`. Pass time zone to `+query_adhoc` with `--zone_offset` after the builder returns `status=generated`.
183
+ - If the user's request lacks a required business element such as time range, event, metric, funnel window, property, or relation field, stop and ask for clarification. If the request supplies a name but it may be a metadata ambiguity, call the builder and let it return candidates. Do not invent names or handcraft QP.
184
+
185
+ Supported builder chain:
186
+ 1. Read the builder reference for the target model.
187
+ 2. Build structured JSON from the user's request. Use user-provided event/property/metric names as-is; do not pre-query metadata for these names.
188
+ 3. Run the matching `+build_*_analysis_qp` command.
189
+ 4. If `status=generated`, call `+query_adhoc --model_type <same_model> --qp '<response.qp>'`.
190
+ 5. If status is not `generated`, report the structured error or ask the user for the missing information; do not continue to `query_adhoc`.
191
+
192
+ Legacy query_adhoc chain:
193
+ 1. Use only for `distribution`, `attribution`, `heat_map`, `interval`, `path`, `rank_list`, or `sql`.
194
+ 2. Read `references/query_adhoc.md` and required schema/metadata references.
195
+ 3. Build QP manually from the documented schema and verified project metadata.
196
+ 4. Call `+query_adhoc`.
197
+
198
+ ## Tool Groups (73)
171
199
 
172
- ### analysis (31)
200
+ ### analysis (35)
173
201
 
174
202
  Alerts (5):
175
203
  - `+get_alert_definition_schema` ([doc](references/get_alert_definition_schema.md))
@@ -193,7 +221,11 @@ Reports and Dashboards (13):
193
221
  - `+create_public_access_link` ([doc](references/create_public_access_link.md))
194
222
  - `+update_public_access_link` ([doc](references/update_public_access_link.md))
195
223
 
196
- Model Analysis (6):
224
+ Model Analysis (10):
225
+ - `+build_event_analysis_qp` ([doc](references/build_event_analysis_qp.md))
226
+ - `+build_retention_analysis_qp` ([doc](references/build_retention_analysis_qp.md))
227
+ - `+build_funnel_analysis_qp` ([doc](references/build_funnel_analysis_qp.md))
228
+ - `+build_prop_analysis_qp` ([doc](references/build_prop_analysis_qp.md))
197
229
  - `+query_adhoc` ([doc](references/query_adhoc.md))
198
230
  - `+drilldown_users` ([doc](references/drilldown_users.md))
199
231
  - `+drilldown_user_events` ([doc](references/drilldown_user_events.md))
@@ -284,4 +316,4 @@ npm run verify:analysis-common-tools
284
316
 
285
317
  ## Reference Docs
286
318
 
287
- See the unified `references/` directory (69 command docs total).
319
+ See the unified `references/` directory (73 command docs total).