@jiangzhx/adcli 0.1.1 → 0.1.2
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 +17 -1
- package/dist/cli.js +313 -46
- package/docs/commands.md +71 -10
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -36,12 +36,28 @@ adcli doc search "广告消耗"
|
|
|
36
36
|
https://adcli.jiangzhx.com/search-index.json
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
首次搜索会下载索引并缓存到系统标准缓存目录。也可以手动更新:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
adcli doc sync
|
|
43
|
+
adcli doc search "广告消耗" --refresh
|
|
44
|
+
```
|
|
45
|
+
|
|
39
46
|
本地调试可指定索引:
|
|
40
47
|
|
|
41
48
|
```bash
|
|
42
49
|
adcli doc search "广告消耗" --index public/search-index.json
|
|
43
50
|
```
|
|
44
51
|
|
|
52
|
+
给 AI / Agent 使用文档包:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
adcli list
|
|
56
|
+
adcli list kuaishou
|
|
57
|
+
adcli llms
|
|
58
|
+
adcli prompt
|
|
59
|
+
```
|
|
60
|
+
|
|
45
61
|
## 常用命令
|
|
46
62
|
|
|
47
63
|
```bash
|
|
@@ -67,7 +83,7 @@ bun run build:search-index
|
|
|
67
83
|
本地搜索已发布文档:
|
|
68
84
|
|
|
69
85
|
```bash
|
|
70
|
-
|
|
86
|
+
adcli doc search "广告消耗"
|
|
71
87
|
```
|
|
72
88
|
|
|
73
89
|
完整命令清单见 [docs/commands.md](docs/commands.md)。
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,159 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
import { readFile } from "fs/promises";
|
|
6
|
-
import
|
|
4
|
+
// src/lib/search/cache.ts
|
|
5
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
6
|
+
import path2 from "node:path";
|
|
7
|
+
|
|
8
|
+
// node_modules/env-paths/index.js
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import process2 from "node:process";
|
|
12
|
+
|
|
13
|
+
// node_modules/is-safe-filename/index.js
|
|
14
|
+
var unsafeFilenameFixtures = Object.freeze([
|
|
15
|
+
"",
|
|
16
|
+
" ",
|
|
17
|
+
".",
|
|
18
|
+
"..",
|
|
19
|
+
" .",
|
|
20
|
+
". ",
|
|
21
|
+
" ..",
|
|
22
|
+
".. ",
|
|
23
|
+
"../",
|
|
24
|
+
"../foo",
|
|
25
|
+
"foo/../bar",
|
|
26
|
+
"foo/bar",
|
|
27
|
+
"foo\\bar",
|
|
28
|
+
"foo\x00bar"
|
|
29
|
+
]);
|
|
30
|
+
function isSafeFilename(filename) {
|
|
31
|
+
if (typeof filename !== "string") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const trimmed = filename.trim();
|
|
35
|
+
return trimmed !== "" && trimmed !== "." && trimmed !== ".." && !filename.includes("/") && !filename.includes("\\") && !filename.includes("\x00");
|
|
36
|
+
}
|
|
37
|
+
function assertSafeFilename(filename) {
|
|
38
|
+
if (typeof filename !== "string") {
|
|
39
|
+
throw new TypeError("Expected a string");
|
|
40
|
+
}
|
|
41
|
+
if (!isSafeFilename(filename)) {
|
|
42
|
+
throw new Error(`Unsafe filename: ${JSON.stringify(filename)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// node_modules/env-paths/index.js
|
|
47
|
+
var homedir = os.homedir();
|
|
48
|
+
var tmpdir = os.tmpdir();
|
|
49
|
+
var { env } = process2;
|
|
50
|
+
var macos = (name) => {
|
|
51
|
+
const library = path.join(homedir, "Library");
|
|
52
|
+
return {
|
|
53
|
+
data: path.join(library, "Application Support", name),
|
|
54
|
+
config: path.join(library, "Preferences", name),
|
|
55
|
+
cache: path.join(library, "Caches", name),
|
|
56
|
+
log: path.join(library, "Logs", name),
|
|
57
|
+
temp: path.join(tmpdir, name)
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
var windows = (name) => {
|
|
61
|
+
const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
|
|
62
|
+
const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
|
|
63
|
+
return {
|
|
64
|
+
data: path.join(localAppData, name, "Data"),
|
|
65
|
+
config: path.join(appData, name, "Config"),
|
|
66
|
+
cache: path.join(localAppData, name, "Cache"),
|
|
67
|
+
log: path.join(localAppData, name, "Log"),
|
|
68
|
+
temp: path.join(tmpdir, name)
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
var linux = (name) => {
|
|
72
|
+
const username = path.basename(homedir);
|
|
73
|
+
return {
|
|
74
|
+
data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
|
|
75
|
+
config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
|
|
76
|
+
cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
|
|
77
|
+
log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
|
|
78
|
+
temp: path.join(tmpdir, username, name)
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
function envPaths(name, { suffix = "nodejs" } = {}) {
|
|
82
|
+
assertSafeFilename(name);
|
|
83
|
+
if (suffix) {
|
|
84
|
+
name += `-${suffix}`;
|
|
85
|
+
}
|
|
86
|
+
assertSafeFilename(name);
|
|
87
|
+
if (process2.platform === "darwin") {
|
|
88
|
+
return macos(name);
|
|
89
|
+
}
|
|
90
|
+
if (process2.platform === "win32") {
|
|
91
|
+
return windows(name);
|
|
92
|
+
}
|
|
93
|
+
return linux(name);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/lib/search/cache.ts
|
|
97
|
+
var defaultSearchIndexUrl = "https://adcli.jiangzhx.com/search-index.json";
|
|
98
|
+
function getSearchIndexCacheInfo(options = {}) {
|
|
99
|
+
const cacheDir = options.cacheDir ?? envPaths("adcli", { suffix: "" }).cache;
|
|
100
|
+
return {
|
|
101
|
+
cachePath: path2.join(cacheDir, "search-index.json"),
|
|
102
|
+
indexUrl: options.indexUrl ?? defaultSearchIndexUrl
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function loadSearchIndex(options = {}) {
|
|
106
|
+
if (options.index) {
|
|
107
|
+
return await loadExplicitSearchIndex(options.index, options.fetcher);
|
|
108
|
+
}
|
|
109
|
+
const cacheInfo = getSearchIndexCacheInfo(options);
|
|
110
|
+
if (!options.refresh) {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(await readFile(cacheInfo.cachePath, "utf8"));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!isNotFoundError(error)) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return await refreshSearchIndex(options);
|
|
120
|
+
}
|
|
121
|
+
async function refreshSearchIndex(options = {}) {
|
|
122
|
+
const cacheInfo = getSearchIndexCacheInfo(options);
|
|
123
|
+
const index = await fetchSearchIndex(cacheInfo.indexUrl, options.fetcher);
|
|
124
|
+
await writeCachedSearchIndex(cacheInfo.cachePath, index);
|
|
125
|
+
return index;
|
|
126
|
+
}
|
|
127
|
+
async function loadExplicitSearchIndex(index, fetcher = fetch) {
|
|
128
|
+
if (/^https?:\/\//i.test(index)) {
|
|
129
|
+
return await fetchSearchIndex(index, fetcher);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(await readFile(path2.resolve(index), "utf8"));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (isNotFoundError(error)) {
|
|
135
|
+
throw new Error(`missing search index: ${path2.relative(process.cwd(), path2.resolve(index))}`);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function fetchSearchIndex(indexUrl, fetcher = fetch) {
|
|
141
|
+
const response = await fetcher(indexUrl);
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`failed to fetch search index: ${indexUrl} (${response.status})`);
|
|
144
|
+
}
|
|
145
|
+
return await response.json();
|
|
146
|
+
}
|
|
147
|
+
async function writeCachedSearchIndex(cachePath, index) {
|
|
148
|
+
await mkdir(path2.dirname(cachePath), { recursive: true });
|
|
149
|
+
const tempPath = `${cachePath}.${process.pid}.tmp`;
|
|
150
|
+
await writeFile(tempPath, `${JSON.stringify(index)}
|
|
151
|
+
`, "utf8");
|
|
152
|
+
await rename(tempPath, cachePath);
|
|
153
|
+
}
|
|
154
|
+
function isNotFoundError(error) {
|
|
155
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
156
|
+
}
|
|
7
157
|
|
|
8
158
|
// node_modules/minisearch/dist/es/index.js
|
|
9
159
|
var ENTRIES = "ENTRIES";
|
|
@@ -131,9 +281,9 @@ class SearchableMap {
|
|
|
131
281
|
if (!prefix.startsWith(this._prefix)) {
|
|
132
282
|
throw new Error("Mismatched prefix");
|
|
133
283
|
}
|
|
134
|
-
const [node,
|
|
284
|
+
const [node, path3] = trackDown(this._tree, prefix.slice(this._prefix.length));
|
|
135
285
|
if (node === undefined) {
|
|
136
|
-
const [parentNode, key] = last(
|
|
286
|
+
const [parentNode, key] = last(path3);
|
|
137
287
|
for (const k of parentNode.keys()) {
|
|
138
288
|
if (k !== LEAF && k.startsWith(key)) {
|
|
139
289
|
const node2 = new Map;
|
|
@@ -231,18 +381,18 @@ class SearchableMap {
|
|
|
231
381
|
return SearchableMap.from(Object.entries(object));
|
|
232
382
|
}
|
|
233
383
|
}
|
|
234
|
-
var trackDown = (tree, key,
|
|
384
|
+
var trackDown = (tree, key, path3 = []) => {
|
|
235
385
|
if (key.length === 0 || tree == null) {
|
|
236
|
-
return [tree,
|
|
386
|
+
return [tree, path3];
|
|
237
387
|
}
|
|
238
388
|
for (const k of tree.keys()) {
|
|
239
389
|
if (k !== LEAF && key.startsWith(k)) {
|
|
240
|
-
|
|
241
|
-
return trackDown(tree.get(k), key.slice(k.length),
|
|
390
|
+
path3.push([tree, k]);
|
|
391
|
+
return trackDown(tree.get(k), key.slice(k.length), path3);
|
|
242
392
|
}
|
|
243
393
|
}
|
|
244
|
-
|
|
245
|
-
return trackDown(undefined, "",
|
|
394
|
+
path3.push([tree, key]);
|
|
395
|
+
return trackDown(undefined, "", path3);
|
|
246
396
|
};
|
|
247
397
|
var lookup = (tree, key) => {
|
|
248
398
|
if (key.length === 0 || tree == null) {
|
|
@@ -285,38 +435,38 @@ var createPath = (node, key) => {
|
|
|
285
435
|
return node;
|
|
286
436
|
};
|
|
287
437
|
var remove = (tree, key) => {
|
|
288
|
-
const [node,
|
|
438
|
+
const [node, path3] = trackDown(tree, key);
|
|
289
439
|
if (node === undefined) {
|
|
290
440
|
return;
|
|
291
441
|
}
|
|
292
442
|
node.delete(LEAF);
|
|
293
443
|
if (node.size === 0) {
|
|
294
|
-
cleanup(
|
|
444
|
+
cleanup(path3);
|
|
295
445
|
} else if (node.size === 1) {
|
|
296
446
|
const [key2, value] = node.entries().next().value;
|
|
297
|
-
merge(
|
|
447
|
+
merge(path3, key2, value);
|
|
298
448
|
}
|
|
299
449
|
};
|
|
300
|
-
var cleanup = (
|
|
301
|
-
if (
|
|
450
|
+
var cleanup = (path3) => {
|
|
451
|
+
if (path3.length === 0) {
|
|
302
452
|
return;
|
|
303
453
|
}
|
|
304
|
-
const [node, key] = last(
|
|
454
|
+
const [node, key] = last(path3);
|
|
305
455
|
node.delete(key);
|
|
306
456
|
if (node.size === 0) {
|
|
307
|
-
cleanup(
|
|
457
|
+
cleanup(path3.slice(0, -1));
|
|
308
458
|
} else if (node.size === 1) {
|
|
309
459
|
const [key2, value] = node.entries().next().value;
|
|
310
460
|
if (key2 !== LEAF) {
|
|
311
|
-
merge(
|
|
461
|
+
merge(path3.slice(0, -1), key2, value);
|
|
312
462
|
}
|
|
313
463
|
}
|
|
314
464
|
};
|
|
315
|
-
var merge = (
|
|
316
|
-
if (
|
|
465
|
+
var merge = (path3, key, value) => {
|
|
466
|
+
if (path3.length === 0) {
|
|
317
467
|
return;
|
|
318
468
|
}
|
|
319
|
-
const [node, nodeKey] = last(
|
|
469
|
+
const [node, nodeKey] = last(path3);
|
|
320
470
|
node.set(nodeKey + key, value);
|
|
321
471
|
node.delete(nodeKey);
|
|
322
472
|
};
|
|
@@ -1274,14 +1424,29 @@ function compact(value) {
|
|
|
1274
1424
|
}
|
|
1275
1425
|
|
|
1276
1426
|
// src/cli.ts
|
|
1277
|
-
var defaultSearchIndexUrl = "https://adcli.jiangzhx.com/search-index.json";
|
|
1278
1427
|
var help = `adcli
|
|
1279
1428
|
|
|
1280
1429
|
Usage:
|
|
1281
|
-
adcli
|
|
1430
|
+
adcli list [platform] [--json]
|
|
1431
|
+
adcli doc <command>
|
|
1432
|
+
adcli prompt
|
|
1433
|
+
adcli llms
|
|
1282
1434
|
|
|
1283
1435
|
Commands:
|
|
1284
|
-
|
|
1436
|
+
list List supported advertising platforms and capabilities
|
|
1437
|
+
doc Search and sync published advertising API docs
|
|
1438
|
+
prompt Print an AI/Agent instruction prompt for using the docs pack
|
|
1439
|
+
llms Print LLM-readable docs pack entry URLs
|
|
1440
|
+
`;
|
|
1441
|
+
var docHelp = `adcli doc
|
|
1442
|
+
|
|
1443
|
+
Usage:
|
|
1444
|
+
adcli doc search <query> [--platform tencent_ads] [--limit 10] [--json] [--refresh]
|
|
1445
|
+
adcli doc sync
|
|
1446
|
+
|
|
1447
|
+
Commands:
|
|
1448
|
+
search Search published advertising API docs
|
|
1449
|
+
sync Download and cache the latest search index
|
|
1285
1450
|
`;
|
|
1286
1451
|
async function main() {
|
|
1287
1452
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -1289,6 +1454,34 @@ async function main() {
|
|
|
1289
1454
|
console.log(help.trim());
|
|
1290
1455
|
return;
|
|
1291
1456
|
}
|
|
1457
|
+
if (args.domain === "list") {
|
|
1458
|
+
const index2 = await loadSearchIndex({ index: args.index, refresh: args.refresh });
|
|
1459
|
+
printDocList(index2, args);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (args.domain === "doc" && (!args.command || args.command === "--help" || args.command === "-h" || args.command === "help")) {
|
|
1463
|
+
console.log(docHelp.trim());
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (args.domain === "doc" && args.command === "sync") {
|
|
1467
|
+
const index2 = await refreshSearchIndex();
|
|
1468
|
+
const cache = getSearchIndexCacheInfo();
|
|
1469
|
+
console.log(`Synced ${index2.documents.length} docs to ${cache.cachePath}`);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
if (args.domain === "doc" && args.command === "list") {
|
|
1473
|
+
const index2 = await loadSearchIndex({ index: args.index, refresh: args.refresh });
|
|
1474
|
+
printDocList(index2, args);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (args.domain === "prompt") {
|
|
1478
|
+
printLlms({ ...args, domain: "llms", command: "prompt" });
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (args.domain === "llms") {
|
|
1482
|
+
printLlms(args);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1292
1485
|
if (args.domain !== "doc" || args.command !== "search") {
|
|
1293
1486
|
throw new Error(`unknown command: ${[args.domain, args.command].filter(Boolean).join(" ")}`);
|
|
1294
1487
|
}
|
|
@@ -1296,7 +1489,7 @@ async function main() {
|
|
|
1296
1489
|
if (!query) {
|
|
1297
1490
|
throw new Error("missing search query");
|
|
1298
1491
|
}
|
|
1299
|
-
const index = await loadSearchIndex(args.index);
|
|
1492
|
+
const index = await loadSearchIndex({ index: args.index, refresh: args.refresh });
|
|
1300
1493
|
const results = await searchDocuments({
|
|
1301
1494
|
query,
|
|
1302
1495
|
documents: index.documents,
|
|
@@ -1322,14 +1515,102 @@ async function main() {
|
|
|
1322
1515
|
console.log(` Score: ${result.score.toFixed(2)}`);
|
|
1323
1516
|
}
|
|
1324
1517
|
}
|
|
1518
|
+
function printDocList(index, args) {
|
|
1519
|
+
const platformFilter = args.command;
|
|
1520
|
+
const platforms = [...new Set(index.documents.map((document) => document.platform))].sort().filter((platform) => !platformFilter || platform === platformFilter).map((platform) => {
|
|
1521
|
+
const documents = index.documents.filter((document) => document.platform === platform);
|
|
1522
|
+
return {
|
|
1523
|
+
platform,
|
|
1524
|
+
capabilities: [
|
|
1525
|
+
{
|
|
1526
|
+
name: "doc",
|
|
1527
|
+
documents: documents.length,
|
|
1528
|
+
index_url: `https://adcli.jiangzhx.com/${platform}/index.md`,
|
|
1529
|
+
commands: [
|
|
1530
|
+
`adcli doc search <query> --platform ${platform}`
|
|
1531
|
+
]
|
|
1532
|
+
}
|
|
1533
|
+
]
|
|
1534
|
+
};
|
|
1535
|
+
});
|
|
1536
|
+
if (platformFilter && platforms.length === 0) {
|
|
1537
|
+
throw new Error(`unsupported platform: ${platformFilter}`);
|
|
1538
|
+
}
|
|
1539
|
+
if (args.json) {
|
|
1540
|
+
console.log(JSON.stringify({ platforms }, null, 2));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
for (const item of platforms) {
|
|
1544
|
+
console.log(item.platform);
|
|
1545
|
+
for (const capability of item.capabilities) {
|
|
1546
|
+
console.log(` ${capability.name}`);
|
|
1547
|
+
for (const command of capability.commands) {
|
|
1548
|
+
console.log(` ${command}`);
|
|
1549
|
+
}
|
|
1550
|
+
console.log(` docs: ${capability.documents}`);
|
|
1551
|
+
console.log(` index: ${capability.index_url}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function printLlms(args) {
|
|
1556
|
+
const payload = {
|
|
1557
|
+
name: "AdCLI Docs Pack",
|
|
1558
|
+
base_url: "https://adcli.jiangzhx.com",
|
|
1559
|
+
llms_txt: "https://adcli.jiangzhx.com/llms.txt",
|
|
1560
|
+
llms_full_txt: "https://adcli.jiangzhx.com/llms-full.txt",
|
|
1561
|
+
search_index: "https://adcli.jiangzhx.com/search-index.json",
|
|
1562
|
+
platform_indexes: [
|
|
1563
|
+
"https://adcli.jiangzhx.com/kuaishou/index.md",
|
|
1564
|
+
"https://adcli.jiangzhx.com/oceanengine/index.md",
|
|
1565
|
+
"https://adcli.jiangzhx.com/tencent_ads/index.md"
|
|
1566
|
+
]
|
|
1567
|
+
};
|
|
1568
|
+
if (args.json) {
|
|
1569
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (args.command === "prompt") {
|
|
1573
|
+
console.log([
|
|
1574
|
+
"Use the AdCLI advertising API docs pack when answering advertising platform API questions.",
|
|
1575
|
+
"",
|
|
1576
|
+
`Start with: ${payload.llms_txt}`,
|
|
1577
|
+
`Use full index when needed: ${payload.llms_full_txt}`,
|
|
1578
|
+
`Use search index for local/tool search: ${payload.search_index}`,
|
|
1579
|
+
"",
|
|
1580
|
+
"Rules:",
|
|
1581
|
+
"- Prefer the Markdown docs from this docs pack over memory.",
|
|
1582
|
+
"- Cite the specific platform document URL or source URL used.",
|
|
1583
|
+
"- If a query names a platform, keep that platform as the primary context but do not ignore relevant cross-platform differences.",
|
|
1584
|
+
"- For API testing or implementation, identify method, path, auth requirement, parameters, response fields, and known limits.",
|
|
1585
|
+
"- If the docs are incomplete or ambiguous, say what is missing instead of guessing."
|
|
1586
|
+
].join(`
|
|
1587
|
+
`));
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
console.log([
|
|
1591
|
+
"AdCLI Docs Pack",
|
|
1592
|
+
"",
|
|
1593
|
+
`llms.txt: ${payload.llms_txt}`,
|
|
1594
|
+
`llms-full.txt: ${payload.llms_full_txt}`,
|
|
1595
|
+
`search index: ${payload.search_index}`,
|
|
1596
|
+
"",
|
|
1597
|
+
"Platform indexes:",
|
|
1598
|
+
...payload.platform_indexes.map((url) => `- ${url}`),
|
|
1599
|
+
"",
|
|
1600
|
+
"Prompt for AI/Agent:",
|
|
1601
|
+
" adcli llms prompt"
|
|
1602
|
+
].join(`
|
|
1603
|
+
`));
|
|
1604
|
+
}
|
|
1325
1605
|
function parseArgs(argv) {
|
|
1326
1606
|
const args = {
|
|
1327
1607
|
domain: argv[0],
|
|
1328
1608
|
command: argv[1],
|
|
1329
1609
|
query: [],
|
|
1330
|
-
index: process.env.ADCLI_SEARCH_INDEX
|
|
1610
|
+
index: process.env.ADCLI_SEARCH_INDEX,
|
|
1331
1611
|
limit: 10,
|
|
1332
|
-
json: false
|
|
1612
|
+
json: false,
|
|
1613
|
+
refresh: false
|
|
1333
1614
|
};
|
|
1334
1615
|
for (let index = 2;index < argv.length; index += 1) {
|
|
1335
1616
|
const value = argv[index];
|
|
@@ -1342,6 +1623,10 @@ function parseArgs(argv) {
|
|
|
1342
1623
|
index += 1;
|
|
1343
1624
|
continue;
|
|
1344
1625
|
}
|
|
1626
|
+
if (value === "--refresh") {
|
|
1627
|
+
args.refresh = true;
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1345
1630
|
if (value === "--limit") {
|
|
1346
1631
|
args.limit = Number.parseInt(argv[index + 1] ?? "", 10);
|
|
1347
1632
|
index += 1;
|
|
@@ -1359,24 +1644,6 @@ function parseArgs(argv) {
|
|
|
1359
1644
|
}
|
|
1360
1645
|
return args;
|
|
1361
1646
|
}
|
|
1362
|
-
async function loadSearchIndex(index) {
|
|
1363
|
-
if (/^https?:\/\//i.test(index)) {
|
|
1364
|
-
const response = await fetch(index);
|
|
1365
|
-
if (!response.ok) {
|
|
1366
|
-
throw new Error(`failed to fetch search index: ${index} (${response.status})`);
|
|
1367
|
-
}
|
|
1368
|
-
return await response.json();
|
|
1369
|
-
}
|
|
1370
|
-
const indexPath = path.resolve(index);
|
|
1371
|
-
try {
|
|
1372
|
-
return JSON.parse(await readFile(indexPath, "utf8"));
|
|
1373
|
-
} catch (error) {
|
|
1374
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1375
|
-
throw new Error(`missing search index: ${path.relative(process.cwd(), indexPath)}. Use --index ${defaultSearchIndexUrl} or run: bun run build:search-index`);
|
|
1376
|
-
}
|
|
1377
|
-
throw error;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
1647
|
main().catch((error) => {
|
|
1381
1648
|
console.error(error instanceof Error ? error.message : error);
|
|
1382
1649
|
process.exit(1);
|
package/docs/commands.md
CHANGED
|
@@ -7,27 +7,43 @@
|
|
|
7
7
|
|
|
8
8
|
## 对外 CLI
|
|
9
9
|
|
|
10
|
-
### `adcli
|
|
10
|
+
### `adcli list`
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
列出当前支持的广告平台和能力。
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
adcli
|
|
15
|
+
adcli list
|
|
16
|
+
adcli list kuaishou
|
|
17
|
+
adcli list --json
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
当前能力:
|
|
21
|
+
|
|
22
|
+
- `doc`: 已发布的文档查询能力,对应命令形态是 `adcli doc search <query> --platform <platform>`。
|
|
23
|
+
|
|
24
|
+
这个命令会读取本地缓存;缓存不存在时会下载线上 `search-index.json`。
|
|
25
|
+
|
|
26
|
+
兼容命令:
|
|
19
27
|
|
|
20
28
|
```bash
|
|
21
|
-
|
|
29
|
+
adcli doc list
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `adcli doc search`
|
|
33
|
+
|
|
34
|
+
搜索已经发布到线上 docs pack 的广告平台文档。全局安装后直接使用:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
adcli doc search "广告消耗"
|
|
22
38
|
```
|
|
23
39
|
|
|
24
40
|
支持参数:
|
|
25
41
|
|
|
26
42
|
```bash
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
adcli doc search "广告消耗" --limit 5
|
|
44
|
+
adcli doc search "广告消耗" --json
|
|
45
|
+
adcli doc search "广告消耗" --index public/search-index.json
|
|
46
|
+
adcli doc search "广告消耗" --platform tencent_ads
|
|
31
47
|
```
|
|
32
48
|
|
|
33
49
|
全局 CLI 默认读取:
|
|
@@ -36,6 +52,19 @@ bun run adcli doc search "广告消耗" --platform tencent_ads
|
|
|
36
52
|
https://adcli.jiangzhx.com/search-index.json
|
|
37
53
|
```
|
|
38
54
|
|
|
55
|
+
第一次搜索会下载索引并写入系统标准缓存目录:
|
|
56
|
+
|
|
57
|
+
- macOS: `~/Library/Caches/adcli/search-index.json`
|
|
58
|
+
- Linux: `${XDG_CACHE_HOME:-~/.cache}/adcli/search-index.json`
|
|
59
|
+
- Windows: `%LOCALAPPDATA%/adcli/Cache/search-index.json`
|
|
60
|
+
|
|
61
|
+
主动更新缓存:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
adcli doc sync
|
|
65
|
+
adcli doc search "广告消耗" --refresh
|
|
66
|
+
```
|
|
67
|
+
|
|
39
68
|
也可以用环境变量覆盖:
|
|
40
69
|
|
|
41
70
|
```bash
|
|
@@ -54,6 +83,38 @@ ADCLI_SEARCH_INDEX=https://example.com/search-index.json adcli doc search "广
|
|
|
54
83
|
|
|
55
84
|
查询词中包含平台别名时会提升对应平台的排序权重,例如 `广点通`、`腾讯广告` 会提升 `tencent_ads` 文档,但不会硬过滤其他平台。需要明确限制平台时,使用 `--platform tencent_ads`。
|
|
56
85
|
|
|
86
|
+
### `adcli llms`
|
|
87
|
+
|
|
88
|
+
输出给 AI / Agent 使用的 docs pack 入口。
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
adcli llms
|
|
92
|
+
adcli llms --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
输出包括:
|
|
96
|
+
|
|
97
|
+
- `llms.txt`
|
|
98
|
+
- `llms-full.txt`
|
|
99
|
+
- `search-index.json`
|
|
100
|
+
- 各平台 index
|
|
101
|
+
|
|
102
|
+
### `adcli prompt`
|
|
103
|
+
|
|
104
|
+
输出一段可复制给 AI / Agent 的使用说明。
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
adcli prompt
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
这段 prompt 会要求 AI 优先使用 AdCLI 发布的 Markdown 文档,引用具体来源,并在文档不足时明确说明缺口。
|
|
111
|
+
|
|
112
|
+
兼容命令:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
adcli llms prompt
|
|
116
|
+
```
|
|
117
|
+
|
|
57
118
|
## 采集命令
|
|
58
119
|
|
|
59
120
|
### `bun run discover:sources`
|
|
@@ -195,7 +256,7 @@ bun run lint
|
|
|
195
256
|
|
|
196
257
|
## 当前边界
|
|
197
258
|
|
|
198
|
-
`adcli` CLI
|
|
259
|
+
`adcli` CLI 当前支持 `doc search`、`doc sync` 和 `llms` 入口。采集、构建和校验还停留在 `bun run ...` 工程命令层。
|
|
199
260
|
|
|
200
261
|
后续如果要把项目产品化成完整 CLI,可以逐步把这些能力收进 `adcli`:
|
|
201
262
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jiangzhx/adcli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI for searching hosted advertising platform API docs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.10",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"typescript-eslint": "^8.59.3"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"env-paths": "^4.0.0",
|
|
52
53
|
"minisearch": "^7.2.0"
|
|
53
54
|
}
|
|
54
55
|
}
|