@9000ai/cli 0.5.3 → 0.5.4
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/dist/client.js +4 -0
- package/dist/commands/config.js +8 -4
- package/dist/commands/init.js +5 -5
- package/dist/commands/search.js +78 -16
- package/dist/commands/skill.js +17 -15
- package/dist/commands/update.js +4 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -1
- package/dist/index.js +1 -1
- package/dist/output.d.ts +1 -0
- package/dist/output.js +10 -0
- package/dist/postinstall.js +2 -2
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -83,6 +83,10 @@ export function printJson(data, opts) {
|
|
|
83
83
|
function pickFields(data, fields) {
|
|
84
84
|
if (!data || typeof data !== "object")
|
|
85
85
|
return data;
|
|
86
|
+
// Bare array — pick from each element
|
|
87
|
+
if (Array.isArray(data)) {
|
|
88
|
+
return data.map((item) => pick(item, fields));
|
|
89
|
+
}
|
|
86
90
|
const obj = data;
|
|
87
91
|
// If response has code/message/data wrapper, dig into data
|
|
88
92
|
if ("code" in obj && "data" in obj) {
|
package/dist/commands/config.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { loadConfig, saveConfig, resolveBaseUrl } from "../config.js";
|
|
1
|
+
import { loadConfig, saveConfig, resolveBaseUrl, resolveSkillsPath } from "../config.js";
|
|
2
2
|
export function registerConfigCommands(parent) {
|
|
3
3
|
const cmd = parent.command("config").description("Manage CLI configuration");
|
|
4
4
|
cmd
|
|
5
5
|
.command("set")
|
|
6
|
-
.description("Set base URL and/or
|
|
6
|
+
.description("Set base URL, API key, and/or skills path")
|
|
7
7
|
.option("--base-url <url>", "Hub service address")
|
|
8
8
|
.option("--api-key <key>", "API key for authentication")
|
|
9
|
+
.option("--skills-path <dir>", "Directory where skills are installed")
|
|
9
10
|
.action((opts) => {
|
|
10
11
|
const patch = {};
|
|
11
12
|
if (opts.baseUrl)
|
|
12
13
|
patch.base_url = opts.baseUrl;
|
|
13
14
|
if (opts.apiKey)
|
|
14
15
|
patch.api_key = opts.apiKey;
|
|
16
|
+
if (opts.skillsPath)
|
|
17
|
+
patch.skills_path = opts.skillsPath;
|
|
15
18
|
if (Object.keys(patch).length === 0) {
|
|
16
|
-
console.error("Provide at least --base-url or --
|
|
19
|
+
console.error("Provide at least --base-url, --api-key, or --skills-path");
|
|
17
20
|
process.exit(1);
|
|
18
21
|
}
|
|
19
22
|
saveConfig(patch);
|
|
@@ -25,6 +28,7 @@ export function registerConfigCommands(parent) {
|
|
|
25
28
|
.action(() => {
|
|
26
29
|
const cfg = loadConfig();
|
|
27
30
|
const baseUrl = resolveBaseUrl();
|
|
28
|
-
|
|
31
|
+
const skillsPath = resolveSkillsPath();
|
|
32
|
+
console.log(JSON.stringify({ ...cfg, resolved_base_url: baseUrl, resolved_skills_path: skillsPath }, null, 2));
|
|
29
33
|
});
|
|
30
34
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, copyFileSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
2
|
import { join, relative } from "path";
|
|
4
|
-
|
|
3
|
+
import { resolveSkillsPath } from "../config.js";
|
|
5
4
|
/** User data directories — never overwrite even with --force */
|
|
6
5
|
const USER_DATA_DIRS = ["profile", "claims", "inbox", "projects"];
|
|
7
6
|
function filesEqual(a, b) {
|
|
@@ -72,9 +71,10 @@ export function registerInitCommand(program) {
|
|
|
72
71
|
.option("--force", "Update existing files (except profile/) to latest version")
|
|
73
72
|
.action((opts) => {
|
|
74
73
|
const dest = opts.path;
|
|
74
|
+
const templatesDir = join(resolveSkillsPath(), "9000AI-hub", "init", "templates");
|
|
75
75
|
// Check templates exist
|
|
76
|
-
if (!existsSync(
|
|
77
|
-
console.error(`Error: Templates not found at ${
|
|
76
|
+
if (!existsSync(templatesDir)) {
|
|
77
|
+
console.error(`Error: Templates not found at ${templatesDir}\n` +
|
|
78
78
|
`Run: npm install -g @9000ai/cli`);
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
|
@@ -82,7 +82,7 @@ export function registerInitCommand(program) {
|
|
|
82
82
|
if (!existsSync(dest)) {
|
|
83
83
|
mkdirSync(dest, { recursive: true });
|
|
84
84
|
}
|
|
85
|
-
const { created, updated, skipped, outdated } = copyTemplates(
|
|
85
|
+
const { created, updated, skipped, outdated } = copyTemplates(templatesDir, dest, dest, !!opts.force);
|
|
86
86
|
// Output summary
|
|
87
87
|
if (created.length > 0) {
|
|
88
88
|
console.log(`Created ${created.length} files:`);
|
package/dist/commands/search.js
CHANGED
|
@@ -1,30 +1,61 @@
|
|
|
1
1
|
import { request, printJson, pollUntilDone } from "../client.js";
|
|
2
|
-
import { writeJson, writeTsv, listOutputFiles, readOutputJson, timestampSlug } from "../output.js";
|
|
2
|
+
import { writeJson, writeTsv, listOutputFiles, readOutputJson, timestampSlug, formatCsv } from "../output.js";
|
|
3
|
+
const ALL_BOARD_TYPES = ["hot", "entertainment", "society", "seeding", "city", "challenge"];
|
|
3
4
|
export function registerSearchCommands(parent) {
|
|
4
5
|
const cmd = parent.command("search").description("Douyin topic discovery — trending boards and keyword search");
|
|
5
6
|
cmd
|
|
6
7
|
.command("hot")
|
|
7
8
|
.description("Fetch Douyin trending board")
|
|
8
|
-
.option("--type <type>", "Board type: hot|city|seeding|entertainment|society|challenge", "hot")
|
|
9
|
+
.option("--type <type>", "Board type: hot|city|seeding|entertainment|society|challenge or comma-separated or 'all'", "hot")
|
|
9
10
|
.option("--count <n>", "Number of items", "20")
|
|
10
11
|
.option("--city <city>", "City filter (only for type=city)")
|
|
12
|
+
.option("--format <fmt>", "Output format: csv|json", "csv")
|
|
13
|
+
.option("--fields <list>", "Comma-separated fields to extract")
|
|
14
|
+
.option("--compact", "One JSON object per line (only for --format json)")
|
|
11
15
|
.action(async (opts) => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const types = opts.type === "all" ? ALL_BOARD_TYPES : opts.type.split(",").map((t) => t.trim());
|
|
17
|
+
const allItems = [];
|
|
18
|
+
const results = [];
|
|
19
|
+
for (const boardType of types) {
|
|
20
|
+
const params = new URLSearchParams({ type: boardType, count: opts.count });
|
|
21
|
+
if (opts.city)
|
|
22
|
+
params.set("city", opts.city);
|
|
23
|
+
const data = await request({ method: "GET", path: `/api/v1/douyin/discovery/hot-board?${params}` });
|
|
24
|
+
const resp = data;
|
|
25
|
+
const inner = resp.data;
|
|
26
|
+
const items = (inner?.items ?? []);
|
|
27
|
+
// tag each item with board_type when fetching multiple boards
|
|
28
|
+
if (types.length > 1) {
|
|
29
|
+
items.forEach((item) => { item.board_type = boardType; });
|
|
30
|
+
}
|
|
31
|
+
results.push({ type: boardType, items });
|
|
32
|
+
allItems.push(...items);
|
|
33
|
+
}
|
|
34
|
+
// write files
|
|
19
35
|
const slug = timestampSlug();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
const mergedData = types.length === 1
|
|
37
|
+
? results[0].items
|
|
38
|
+
: { boards: results.map((r) => ({ type: r.type, count: r.items.length })), items: allItems };
|
|
39
|
+
writeJson("latest_hot.json", mergedData);
|
|
40
|
+
writeJson(`latest_hot_${slug}.json`, mergedData);
|
|
41
|
+
if (allItems.length > 0) {
|
|
42
|
+
writeTsv("latest_hot.tsv", allItems);
|
|
43
|
+
writeTsv(`latest_hot_${slug}.tsv`, allItems);
|
|
44
|
+
}
|
|
45
|
+
// output
|
|
46
|
+
const boardLabel = types.length === 1 ? types[0] : types.join(",");
|
|
47
|
+
console.error(`Fetched ${allItems.length} items from [${boardLabel}] → output/latest_hot.json`);
|
|
48
|
+
if (opts.format === "csv") {
|
|
49
|
+
const csvFields = opts.fields
|
|
50
|
+
? opts.fields.split(",").map((f) => f.trim())
|
|
51
|
+
: types.length > 1
|
|
52
|
+
? ["board_type", "rank", "word", "hot_value"]
|
|
53
|
+
: ["rank", "word", "hot_value"];
|
|
54
|
+
console.log(formatCsv(allItems, csvFields));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
printJson(allItems, { fields: opts.fields, compact: opts.compact });
|
|
25
58
|
}
|
|
26
|
-
console.log(`Fetched ${items.length} items → output/latest_hot.json`);
|
|
27
|
-
printJson(inner);
|
|
28
59
|
});
|
|
29
60
|
cmd
|
|
30
61
|
.command("keyword")
|
|
@@ -109,6 +140,37 @@ export function registerSearchCommands(parent) {
|
|
|
109
140
|
}
|
|
110
141
|
printJson(inner, { fields: opts.fields, compact: opts.compact });
|
|
111
142
|
});
|
|
143
|
+
cmd
|
|
144
|
+
.command("zhihu-hot")
|
|
145
|
+
.description("Fetch Zhihu (知乎) trending hot list")
|
|
146
|
+
.option("--count <n>", "Number of items", "50")
|
|
147
|
+
.option("--format <fmt>", "Output format: csv|json", "csv")
|
|
148
|
+
.option("--fields <list>", "Comma-separated fields to extract")
|
|
149
|
+
.option("--compact", "One JSON object per line (only for --format json)")
|
|
150
|
+
.action(async (opts) => {
|
|
151
|
+
const params = new URLSearchParams({ count: opts.count });
|
|
152
|
+
const data = await request({ method: "GET", path: `/api/v1/zhihu/discovery/hot-list?${params}` });
|
|
153
|
+
const resp = data;
|
|
154
|
+
const inner = resp.data;
|
|
155
|
+
const items = (inner?.items ?? []);
|
|
156
|
+
const slug = timestampSlug();
|
|
157
|
+
writeJson("latest_zhihu_hot.json", inner);
|
|
158
|
+
writeJson(`latest_zhihu_hot_${slug}.json`, inner);
|
|
159
|
+
if (items.length > 0) {
|
|
160
|
+
writeTsv("latest_zhihu_hot.tsv", items);
|
|
161
|
+
writeTsv(`latest_zhihu_hot_${slug}.tsv`, items);
|
|
162
|
+
}
|
|
163
|
+
console.error(`Fetched ${items.length} items from [知乎热榜] → output/latest_zhihu_hot.json`);
|
|
164
|
+
if (opts.format === "csv") {
|
|
165
|
+
const csvFields = opts.fields
|
|
166
|
+
? opts.fields.split(",").map((f) => f.trim())
|
|
167
|
+
: ["rank", "title", "detail_text", "answer_count"];
|
|
168
|
+
console.log(formatCsv(items, csvFields));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
printJson(items, { fields: opts.fields, compact: opts.compact });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
112
174
|
cmd
|
|
113
175
|
.command("list-output")
|
|
114
176
|
.description("List result files in output directory")
|
package/dist/commands/skill.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
2
|
import { join, resolve } from "path";
|
|
4
|
-
|
|
5
|
-
function getSkillPath(name) {
|
|
6
|
-
return resolve(
|
|
3
|
+
import { resolveSkillsPath } from "../config.js";
|
|
4
|
+
function getSkillPath(skillsDir, name) {
|
|
5
|
+
return resolve(skillsDir, name, "SKILL.md");
|
|
7
6
|
}
|
|
8
7
|
export function registerSkillCommands(program) {
|
|
9
8
|
const skill = program.command("skill").description("Skill management");
|
|
@@ -12,19 +11,20 @@ export function registerSkillCommands(program) {
|
|
|
12
11
|
.command("list")
|
|
13
12
|
.description("List all installed skills")
|
|
14
13
|
.action(() => {
|
|
15
|
-
|
|
14
|
+
const skillsDir = resolveSkillsPath();
|
|
15
|
+
if (!existsSync(skillsDir)) {
|
|
16
16
|
console.log("No skills installed. Run: npm install -g @9000ai/cli");
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
const dirs = readdirSync(
|
|
20
|
+
const dirs = readdirSync(skillsDir).filter((d) => existsSync(join(skillsDir, d, "SKILL.md")));
|
|
21
21
|
if (dirs.length === 0) {
|
|
22
22
|
console.log("No skills found.");
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
console.log(`Installed skills (${
|
|
25
|
+
console.log(`Installed skills (${skillsDir}):\n`);
|
|
26
26
|
dirs.forEach((d) => {
|
|
27
|
-
const path = join(
|
|
27
|
+
const path = join(skillsDir, d, "SKILL.md");
|
|
28
28
|
let description = "";
|
|
29
29
|
try {
|
|
30
30
|
const content = readFileSync(path, "utf-8");
|
|
@@ -44,18 +44,19 @@ export function registerSkillCommands(program) {
|
|
|
44
44
|
.command("load <name>")
|
|
45
45
|
.description("Load and print a skill's SKILL.md content to stdout")
|
|
46
46
|
.action((name) => {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const skillsDir = resolveSkillsPath();
|
|
48
|
+
const skillPath = getSkillPath(skillsDir, name);
|
|
49
|
+
if (!existsSync(skillsDir)) {
|
|
50
|
+
console.error(`Error: Skills directory not found at ${skillsDir}\n` +
|
|
50
51
|
`Run: npm install -g @9000ai/cli`);
|
|
51
52
|
process.exit(1);
|
|
52
53
|
}
|
|
53
54
|
if (!existsSync(skillPath)) {
|
|
54
55
|
// Try fuzzy match
|
|
55
|
-
const available = readdirSync(
|
|
56
|
+
const available = readdirSync(skillsDir).filter((d) => existsSync(join(skillsDir, d, "SKILL.md")));
|
|
56
57
|
const matches = available.filter((d) => d.toLowerCase().includes(name.toLowerCase()));
|
|
57
58
|
if (matches.length === 1) {
|
|
58
|
-
const realPath = getSkillPath(matches[0]);
|
|
59
|
+
const realPath = getSkillPath(skillsDir, matches[0]);
|
|
59
60
|
const content = readFileSync(realPath, "utf-8");
|
|
60
61
|
console.log(content);
|
|
61
62
|
return;
|
|
@@ -79,11 +80,12 @@ export function registerSkillCommands(program) {
|
|
|
79
80
|
.command("path [name]")
|
|
80
81
|
.description("Show skill directory path. No arg = skills root, name = specific skill path")
|
|
81
82
|
.action((name) => {
|
|
83
|
+
const skillsDir = resolveSkillsPath();
|
|
82
84
|
if (name) {
|
|
83
|
-
console.log(getSkillPath(name));
|
|
85
|
+
console.log(getSkillPath(skillsDir, name));
|
|
84
86
|
}
|
|
85
87
|
else {
|
|
86
|
-
console.log(
|
|
88
|
+
console.log(skillsDir);
|
|
87
89
|
}
|
|
88
90
|
});
|
|
89
91
|
}
|
package/dist/commands/update.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import { dirname, join, relative } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { resolveSkillsPath } from "../config.js";
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
export function registerUpdateCommand(program) {
|
|
7
7
|
program
|
|
8
8
|
.command("update")
|
|
9
9
|
.description("Sync skills to latest version after npm update")
|
|
10
|
-
.
|
|
10
|
+
.option("--path <dir>", "Target skills directory (one-time, does not save to config)")
|
|
11
|
+
.action((opts) => {
|
|
11
12
|
const src = join(__dirname, "..", "skills");
|
|
12
|
-
const dest =
|
|
13
|
+
const dest = resolveSkillsPath(opts.path);
|
|
13
14
|
if (!existsSync(src)) {
|
|
14
15
|
console.error("Error: Skills not found in package. Reinstall: npm install -g @9000ai/cli");
|
|
15
16
|
process.exit(1);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface AppConfig {
|
|
2
2
|
base_url?: string;
|
|
3
3
|
api_key?: string;
|
|
4
|
+
skills_path?: string;
|
|
4
5
|
}
|
|
5
6
|
export declare function loadConfig(): AppConfig;
|
|
6
7
|
export declare function saveConfig(patch: Partial<AppConfig>): void;
|
|
7
8
|
export declare function resolveBaseUrl(override?: string): string;
|
|
8
9
|
export declare function resolveApiKey(override?: string): string;
|
|
10
|
+
export declare function resolveSkillsPath(override?: string): string;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
const CONFIG_DIR = join(homedir(), ".9000ai");
|
|
5
5
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
6
6
|
export function loadConfig() {
|
|
@@ -35,3 +35,14 @@ export function resolveApiKey(override) {
|
|
|
35
35
|
}
|
|
36
36
|
return key;
|
|
37
37
|
}
|
|
38
|
+
export function resolveSkillsPath(override) {
|
|
39
|
+
if (override)
|
|
40
|
+
return resolve(override);
|
|
41
|
+
const env = process.env["9000AI_SKILLS_PATH"];
|
|
42
|
+
if (env)
|
|
43
|
+
return resolve(env);
|
|
44
|
+
const cfg = loadConfig();
|
|
45
|
+
if (cfg.skills_path)
|
|
46
|
+
return resolve(cfg.skills_path);
|
|
47
|
+
return join(homedir(), ".9000ai", "skills");
|
|
48
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const program = new Command();
|
|
|
14
14
|
program
|
|
15
15
|
.name("9000ai")
|
|
16
16
|
.description("9000AI Toolbox CLI — unified interface for 9000AI platform")
|
|
17
|
-
.version("0.5.
|
|
17
|
+
.version("0.5.4");
|
|
18
18
|
registerConfigCommands(program);
|
|
19
19
|
registerAuthCommands(program);
|
|
20
20
|
registerSearchCommands(program);
|
package/dist/output.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare function writeJson(filename: string, data: unknown): string;
|
|
|
3
3
|
export declare function writeTsv(filename: string, rows: Record<string, unknown>[]): string;
|
|
4
4
|
export declare function listOutputFiles(): string[];
|
|
5
5
|
export declare function readOutputJson(nameOrPath: string): unknown;
|
|
6
|
+
export declare function formatCsv(rows: Record<string, unknown>[], keys: string[]): string;
|
|
6
7
|
export declare function timestampSlug(): string;
|
package/dist/output.js
CHANGED
|
@@ -42,6 +42,16 @@ export function readOutputJson(nameOrPath) {
|
|
|
42
42
|
}
|
|
43
43
|
return JSON.parse(readFileSync(filepath, "utf-8"));
|
|
44
44
|
}
|
|
45
|
+
export function formatCsv(rows, keys) {
|
|
46
|
+
const header = keys.join(",");
|
|
47
|
+
const lines = rows.map((row) => keys.map((k) => {
|
|
48
|
+
const v = String(row[k] ?? "");
|
|
49
|
+
return v.includes(",") || v.includes('"') || v.includes("\n")
|
|
50
|
+
? `"${v.replace(/"/g, '""')}"`
|
|
51
|
+
: v;
|
|
52
|
+
}).join(","));
|
|
53
|
+
return [header, ...lines].join("\n");
|
|
54
|
+
}
|
|
45
55
|
export function timestampSlug() {
|
|
46
56
|
const now = new Date();
|
|
47
57
|
const pad = (n) => String(n).padStart(2, "0");
|
package/dist/postinstall.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, mkdirSync, cpSync, readFileSync, readdirSync, statSync } from "fs";
|
|
6
6
|
import { dirname, join, relative } from "path";
|
|
7
|
-
import { homedir } from "os";
|
|
8
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { resolveSkillsPath } from "./config.js";
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const src = join(__dirname, "..", "skills");
|
|
11
|
-
const dest =
|
|
11
|
+
const dest = resolveSkillsPath();
|
|
12
12
|
// Read version
|
|
13
13
|
let version = "unknown";
|
|
14
14
|
try {
|