@9000ai/cli 0.1.0
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.d.ts +10 -0
- package/dist/client.js +45 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +18 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +30 -0
- package/dist/commands/feedback.d.ts +2 -0
- package/dist/commands/feedback.js +48 -0
- package/dist/commands/monitor.d.ts +2 -0
- package/dist/commands/monitor.js +101 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +135 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +20 -0
- package/dist/commands/transcribe.d.ts +2 -0
- package/dist/commands/transcribe.js +59 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/output.d.ts +6 -0
- package/dist/output.js +49 -0
- package/dist/postinstall.d.ts +5 -0
- package/dist/postinstall.js +17 -0
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/format.js +16 -0
- package/package.json +31 -0
- package/skills/9000AI-hub/SKILL.md +195 -0
- package/skills/9000AI-hub/configure.py +56 -0
- package/skills/9000AI-hub/init/SKILL.md +130 -0
- package/skills/9000AI-hub/init/templates/CLAUDE.md +24 -0
- package/skills/9000AI-hub/init/templates/agents/README.md +7 -0
- package/skills/9000AI-hub/init/templates/agents/content-agent.md +181 -0
- package/skills/9000AI-hub/init/templates/claims/README.md +91 -0
- package/skills/9000AI-hub/init/templates/claims/claims.json +7 -0
- package/skills/9000AI-hub/init/templates/guide.md +185 -0
- package/skills/9000AI-hub/init/templates/inbox/README.md +26 -0
- package/skills/9000AI-hub/init/templates/profile/identity.md +8 -0
- package/skills/9000AI-hub/init/templates/profile/product.md +26 -0
- package/skills/9000AI-hub/init/templates/profile/topics.md +7 -0
- package/skills/9000AI-hub/init/templates/profile/voice.md +8 -0
- package/skills/9000AI-hub/init/templates/projects/README.md +5 -0
- package/skills/9000AI-hub/references/env.example +5 -0
- package/skills/9000AI-hub/references/runner-spec-v1.md +138 -0
- package/skills/9000AI-hub/shared/__init__.py +1 -0
- package/skills/9000AI-hub/shared/runner.py +135 -0
- package/skills/douyin-monitor/SKILL.md +112 -0
- package/skills/douyin-monitor/agents/openai.yaml +3 -0
- package/skills/douyin-monitor/references/endpoints.md +104 -0
- package/skills/douyin-monitor/scripts/douyin_monitor_api.py +273 -0
- package/skills/douyin-topic-discovery/SKILL.md +146 -0
- package/skills/douyin-topic-discovery/agents/openai.yaml +3 -0
- package/skills/douyin-topic-discovery/references/endpoints.md +127 -0
- package/skills/douyin-topic-discovery/scripts/douyin_topic_discovery_api.py +497 -0
- package/skills/douyin-topic-discovery/workflow/topic-research.md +216 -0
- package/skills/feedback/SKILL.md +69 -0
- package/skills/feedback/references/endpoints.md +46 -0
- package/skills/feedback/scripts/feedback_api.py +93 -0
- package/skills/video-transcription/SKILL.md +108 -0
- package/skills/video-transcription/agents/openai.yaml +3 -0
- package/skills/video-transcription/references/endpoints.md +82 -0
- package/skills/video-transcription/scripts/video_transcription_api.py +183 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RequestOptions {
|
|
2
|
+
method: "GET" | "POST" | "PATCH" | "DELETE";
|
|
3
|
+
path: string;
|
|
4
|
+
payload?: unknown;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function request(opts: RequestOptions): Promise<unknown>;
|
|
10
|
+
export declare function printJson(data: unknown): void;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolveBaseUrl, resolveApiKey } from "./config.js";
|
|
2
|
+
export async function request(opts) {
|
|
3
|
+
const baseUrl = resolveBaseUrl(opts.baseUrl).replace(/\/+$/, "");
|
|
4
|
+
const apiKey = resolveApiKey(opts.apiKey);
|
|
5
|
+
const url = `${baseUrl}${opts.path}`;
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timer = setTimeout(() => controller.abort(), opts.timeout ?? 300_000);
|
|
8
|
+
const headers = {
|
|
9
|
+
"X-API-Key": apiKey,
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
};
|
|
12
|
+
const fetchOpts = {
|
|
13
|
+
method: opts.method,
|
|
14
|
+
headers,
|
|
15
|
+
signal: controller.signal,
|
|
16
|
+
};
|
|
17
|
+
if (opts.payload !== undefined && opts.method !== "GET") {
|
|
18
|
+
fetchOpts.body = JSON.stringify(opts.payload);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(url, fetchOpts);
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
const msg = data?.message ?? res.statusText;
|
|
25
|
+
console.error(`Error ${res.status}: ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
32
|
+
console.error("Error: request timed out");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
36
|
+
}
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function printJson(data) {
|
|
44
|
+
console.log(JSON.stringify(data, null, 2));
|
|
45
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
export function registerAuthCommands(parent) {
|
|
3
|
+
const cmd = parent.command("auth").description("Authentication and permissions");
|
|
4
|
+
cmd
|
|
5
|
+
.command("whoami")
|
|
6
|
+
.description("Display current API key identity")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
const data = await request({ method: "GET", path: "/api/v1/auth/me" });
|
|
9
|
+
printJson(data);
|
|
10
|
+
});
|
|
11
|
+
cmd
|
|
12
|
+
.command("capabilities")
|
|
13
|
+
.description("List capabilities available to current API key")
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const data = await request({ method: "GET", path: "/api/v1/auth/capability-permissions" });
|
|
16
|
+
printJson(data);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { loadConfig, saveConfig, resolveBaseUrl } from "../config.js";
|
|
2
|
+
export function registerConfigCommands(parent) {
|
|
3
|
+
const cmd = parent.command("config").description("Manage CLI configuration");
|
|
4
|
+
cmd
|
|
5
|
+
.command("set")
|
|
6
|
+
.description("Set base URL and/or API key")
|
|
7
|
+
.option("--base-url <url>", "Hub service address")
|
|
8
|
+
.option("--api-key <key>", "API key for authentication")
|
|
9
|
+
.action((opts) => {
|
|
10
|
+
const patch = {};
|
|
11
|
+
if (opts.baseUrl)
|
|
12
|
+
patch.base_url = opts.baseUrl;
|
|
13
|
+
if (opts.apiKey)
|
|
14
|
+
patch.api_key = opts.apiKey;
|
|
15
|
+
if (Object.keys(patch).length === 0) {
|
|
16
|
+
console.error("Provide at least --base-url or --api-key");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
saveConfig(patch);
|
|
20
|
+
console.log("Configuration saved.");
|
|
21
|
+
});
|
|
22
|
+
cmd
|
|
23
|
+
.command("show")
|
|
24
|
+
.description("Display current configuration")
|
|
25
|
+
.action(() => {
|
|
26
|
+
const cfg = loadConfig();
|
|
27
|
+
const baseUrl = resolveBaseUrl();
|
|
28
|
+
console.log(JSON.stringify({ ...cfg, resolved_base_url: baseUrl }, null, 2));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
import { loadJsonFile } from "../utils/format.js";
|
|
3
|
+
export function registerFeedbackCommands(parent) {
|
|
4
|
+
const cmd = parent.command("feedback").description("Submit feedback to 9000AI platform");
|
|
5
|
+
cmd
|
|
6
|
+
.command("submit")
|
|
7
|
+
.description("Submit feedback (bug, feature, workflow suggestion)")
|
|
8
|
+
.option("--title <title>", "Feedback title")
|
|
9
|
+
.option("--content <content>", "Feedback details")
|
|
10
|
+
.option("--type <type>", "Category: workflow / bug / feature / other")
|
|
11
|
+
.option("--context-json <json>", "Additional context as JSON string")
|
|
12
|
+
.option("--json-file <path>", "Read feedback from JSON file")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
let payload;
|
|
15
|
+
if (opts.jsonFile) {
|
|
16
|
+
payload = loadJsonFile(opts.jsonFile);
|
|
17
|
+
}
|
|
18
|
+
else if (opts.title && opts.content) {
|
|
19
|
+
payload = { title: opts.title, content: opts.content };
|
|
20
|
+
if (opts.type)
|
|
21
|
+
payload.type = opts.type;
|
|
22
|
+
if (opts.contextJson)
|
|
23
|
+
payload.context = JSON.parse(opts.contextJson);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.error("Need --title + --content, or --json-file");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const data = await request({
|
|
30
|
+
method: "POST",
|
|
31
|
+
path: "/api/v1/feedback",
|
|
32
|
+
payload,
|
|
33
|
+
});
|
|
34
|
+
printJson(data);
|
|
35
|
+
});
|
|
36
|
+
cmd
|
|
37
|
+
.command("list")
|
|
38
|
+
.description("List my submitted feedback")
|
|
39
|
+
.option("--page <n>", "Page number", "1")
|
|
40
|
+
.option("--page-size <n>", "Items per page", "20")
|
|
41
|
+
.action(async (opts) => {
|
|
42
|
+
const data = await request({
|
|
43
|
+
method: "GET",
|
|
44
|
+
path: `/api/v1/feedback?page=${opts.page}&page_size=${opts.pageSize}`,
|
|
45
|
+
});
|
|
46
|
+
printJson(data);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
import { loadJsonFile } from "../utils/format.js";
|
|
3
|
+
export function registerMonitorCommands(parent) {
|
|
4
|
+
const cmd = parent.command("monitor").description("Douyin creator homepage monitoring");
|
|
5
|
+
cmd
|
|
6
|
+
.command("list-creators")
|
|
7
|
+
.description("List all monitoring targets")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const data = await request({ method: "GET", path: "/api/v1/douyin/monitor/creators" });
|
|
10
|
+
printJson(data);
|
|
11
|
+
});
|
|
12
|
+
cmd
|
|
13
|
+
.command("create-creator")
|
|
14
|
+
.description("Create new monitoring target")
|
|
15
|
+
.requiredOption("--json-file <path>", "JSON file with creator details")
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const payload = loadJsonFile(opts.jsonFile);
|
|
18
|
+
const data = await request({ method: "POST", path: "/api/v1/douyin/monitor/creators", payload });
|
|
19
|
+
printJson(data);
|
|
20
|
+
});
|
|
21
|
+
cmd
|
|
22
|
+
.command("get-creator")
|
|
23
|
+
.description("Get monitoring target details")
|
|
24
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
25
|
+
.action(async (opts) => {
|
|
26
|
+
const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}` });
|
|
27
|
+
printJson(data);
|
|
28
|
+
});
|
|
29
|
+
cmd
|
|
30
|
+
.command("update-creator")
|
|
31
|
+
.description("Update monitoring target")
|
|
32
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
33
|
+
.requiredOption("--json-file <path>", "JSON file with updated data")
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const payload = loadJsonFile(opts.jsonFile);
|
|
36
|
+
const data = await request({ method: "PATCH", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}`, payload });
|
|
37
|
+
printJson(data);
|
|
38
|
+
});
|
|
39
|
+
cmd
|
|
40
|
+
.command("delete-creator")
|
|
41
|
+
.description("Delete monitoring target")
|
|
42
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
43
|
+
.action(async (opts) => {
|
|
44
|
+
const data = await request({ method: "DELETE", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}` });
|
|
45
|
+
printJson(data);
|
|
46
|
+
});
|
|
47
|
+
cmd
|
|
48
|
+
.command("enable-creator")
|
|
49
|
+
.description("Enable monitoring for a creator")
|
|
50
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
51
|
+
.action(async (opts) => {
|
|
52
|
+
const data = await request({ method: "POST", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}/enable` });
|
|
53
|
+
printJson(data);
|
|
54
|
+
});
|
|
55
|
+
cmd
|
|
56
|
+
.command("disable-creator")
|
|
57
|
+
.description("Disable monitoring for a creator")
|
|
58
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
59
|
+
.action(async (opts) => {
|
|
60
|
+
const data = await request({ method: "POST", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}/disable` });
|
|
61
|
+
printJson(data);
|
|
62
|
+
});
|
|
63
|
+
cmd
|
|
64
|
+
.command("reset-creator")
|
|
65
|
+
.description("Reset creator monitoring status")
|
|
66
|
+
.requiredOption("--creator-id <id>", "Creator ID")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
const data = await request({ method: "POST", path: `/api/v1/douyin/monitor/creators/${opts.creatorId}/reset` });
|
|
69
|
+
printJson(data);
|
|
70
|
+
});
|
|
71
|
+
cmd
|
|
72
|
+
.command("submit")
|
|
73
|
+
.description("Submit homepage monitoring task")
|
|
74
|
+
.requiredOption("--json-file <path>", "JSON file with monitoring task details")
|
|
75
|
+
.action(async (opts) => {
|
|
76
|
+
const payload = loadJsonFile(opts.jsonFile);
|
|
77
|
+
const data = await request({ method: "POST", path: "/api/v1/douyin/monitor/run", payload });
|
|
78
|
+
printJson(data);
|
|
79
|
+
});
|
|
80
|
+
cmd
|
|
81
|
+
.command("list-runs")
|
|
82
|
+
.description("View execution history")
|
|
83
|
+
.option("--page <n>", "Page number", "1")
|
|
84
|
+
.option("--page-size <n>", "Items per page", "20")
|
|
85
|
+
.option("--status <status>", "Filter by status")
|
|
86
|
+
.action(async (opts) => {
|
|
87
|
+
const params = new URLSearchParams({ page: opts.page, page_size: opts.pageSize });
|
|
88
|
+
if (opts.status)
|
|
89
|
+
params.set("status", opts.status);
|
|
90
|
+
const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/runs?${params}` });
|
|
91
|
+
printJson(data);
|
|
92
|
+
});
|
|
93
|
+
cmd
|
|
94
|
+
.command("run-detail")
|
|
95
|
+
.description("View single run details")
|
|
96
|
+
.requiredOption("--run-id <id>", "Run ID")
|
|
97
|
+
.action(async (opts) => {
|
|
98
|
+
const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/runs/${opts.runId}` });
|
|
99
|
+
printJson(data);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
import { writeJson, writeTsv, listOutputFiles, readOutputJson, timestampSlug } from "../output.js";
|
|
3
|
+
export function registerSearchCommands(parent) {
|
|
4
|
+
const cmd = parent.command("search").description("Douyin topic discovery — trending boards and keyword search");
|
|
5
|
+
cmd
|
|
6
|
+
.command("hot")
|
|
7
|
+
.description("Fetch Douyin trending board")
|
|
8
|
+
.option("--type <type>", "Board type: hot|city|seeding|entertainment|society|challenge", "hot")
|
|
9
|
+
.option("--count <n>", "Number of items", "20")
|
|
10
|
+
.option("--city <city>", "City filter (only for type=city)")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const params = new URLSearchParams({ type: opts.type, count: opts.count });
|
|
13
|
+
if (opts.city)
|
|
14
|
+
params.set("city", opts.city);
|
|
15
|
+
const data = await request({ method: "GET", path: `/api/v1/douyin/discovery/hot-board?${params}` });
|
|
16
|
+
const resp = data;
|
|
17
|
+
const inner = resp.data;
|
|
18
|
+
const items = (inner?.items ?? []);
|
|
19
|
+
const slug = timestampSlug();
|
|
20
|
+
writeJson("latest_hot.json", inner);
|
|
21
|
+
writeJson(`latest_hot_${slug}.json`, inner);
|
|
22
|
+
if (items.length > 0) {
|
|
23
|
+
writeTsv("latest_hot.tsv", items);
|
|
24
|
+
writeTsv(`latest_hot_${slug}.tsv`, items);
|
|
25
|
+
}
|
|
26
|
+
console.log(`Fetched ${items.length} items → output/latest_hot.json`);
|
|
27
|
+
printJson(inner);
|
|
28
|
+
});
|
|
29
|
+
cmd
|
|
30
|
+
.command("keyword")
|
|
31
|
+
.description("Search Douyin by keywords (async batch)")
|
|
32
|
+
.argument("<keywords...>", "One or more search keywords")
|
|
33
|
+
.option("--sort <n>", "Sort order", "0")
|
|
34
|
+
.option("--time <n>", "Time filter", "0")
|
|
35
|
+
.option("--content-type <n>", "Content type: 0=all 1=video 2=user", "0")
|
|
36
|
+
.option("--filter-duration <v>", "Duration filter", "")
|
|
37
|
+
.option("--min-likes <n>", "Minimum likes", "0")
|
|
38
|
+
.option("--min-comments <n>", "Minimum comments", "0")
|
|
39
|
+
.action(async (keywords, opts) => {
|
|
40
|
+
const payload = {
|
|
41
|
+
keywords,
|
|
42
|
+
count: 30,
|
|
43
|
+
sort: parseInt(opts.sort),
|
|
44
|
+
time: parseInt(opts.time),
|
|
45
|
+
content_type: parseInt(opts.contentType),
|
|
46
|
+
filter_duration: opts.filterDuration,
|
|
47
|
+
min_likes: parseInt(opts.minLikes),
|
|
48
|
+
min_comments: parseInt(opts.minComments),
|
|
49
|
+
max_concurrent: 3,
|
|
50
|
+
};
|
|
51
|
+
const data = await request({
|
|
52
|
+
method: "POST",
|
|
53
|
+
path: "/api/v1/douyin/discovery/search-videos/batch",
|
|
54
|
+
payload,
|
|
55
|
+
});
|
|
56
|
+
const resp = data;
|
|
57
|
+
const inner = resp.data;
|
|
58
|
+
console.log(`Batch submitted → batch_id: ${inner?.batch_id}`);
|
|
59
|
+
console.log(`Use: 9000ai search batch-result --batch-id ${inner?.batch_id}`);
|
|
60
|
+
printJson(inner);
|
|
61
|
+
});
|
|
62
|
+
cmd
|
|
63
|
+
.command("batch-result")
|
|
64
|
+
.description("Query async search batch results")
|
|
65
|
+
.requiredOption("--batch-id <id>", "Batch ID")
|
|
66
|
+
.action(async (opts) => {
|
|
67
|
+
const data = await request({
|
|
68
|
+
method: "GET",
|
|
69
|
+
path: `/api/v1/douyin/discovery/search-videos/batch/${opts.batchId}/results`,
|
|
70
|
+
});
|
|
71
|
+
const resp = data;
|
|
72
|
+
const inner = resp.data;
|
|
73
|
+
const items = (inner?.items ?? []);
|
|
74
|
+
if (items.length > 0) {
|
|
75
|
+
const slug = timestampSlug();
|
|
76
|
+
writeJson("latest_search.json", inner);
|
|
77
|
+
writeJson(`latest_search_${slug}.json`, inner);
|
|
78
|
+
writeTsv("latest_search.tsv", items);
|
|
79
|
+
writeTsv(`latest_search_${slug}.tsv`, items);
|
|
80
|
+
console.log(`${items.length} items → output/latest_search.json`);
|
|
81
|
+
}
|
|
82
|
+
printJson(inner);
|
|
83
|
+
});
|
|
84
|
+
cmd
|
|
85
|
+
.command("list-output")
|
|
86
|
+
.description("List result files in output directory")
|
|
87
|
+
.action(() => {
|
|
88
|
+
const files = listOutputFiles();
|
|
89
|
+
if (files.length === 0) {
|
|
90
|
+
console.log("No output files yet.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
files.forEach((f) => console.log(f));
|
|
94
|
+
});
|
|
95
|
+
cmd
|
|
96
|
+
.command("show-result")
|
|
97
|
+
.description("Show single row from result file")
|
|
98
|
+
.option("--input <name>", "File name or alias (latest_search, latest_hot)", "latest_search")
|
|
99
|
+
.requiredOption("--row <n>", "Row number to display")
|
|
100
|
+
.action((opts) => {
|
|
101
|
+
const bundle = readOutputJson(opts.input);
|
|
102
|
+
const items = (bundle?.items ?? []);
|
|
103
|
+
const idx = parseInt(opts.row);
|
|
104
|
+
const item = items.find((i) => i.row_no === idx) ?? items[idx - 1];
|
|
105
|
+
if (!item) {
|
|
106
|
+
console.error(`Row ${opts.row} not found (total: ${items.length})`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
printJson(item);
|
|
110
|
+
});
|
|
111
|
+
cmd
|
|
112
|
+
.command("export-media")
|
|
113
|
+
.description("Extract media reference from result row")
|
|
114
|
+
.option("--input <name>", "File name or alias", "latest_search")
|
|
115
|
+
.requiredOption("--row <n>", "Row number to export")
|
|
116
|
+
.action((opts) => {
|
|
117
|
+
const bundle = readOutputJson(opts.input);
|
|
118
|
+
const items = (bundle?.items ?? []);
|
|
119
|
+
const idx = parseInt(opts.row);
|
|
120
|
+
const item = items.find((i) => i.row_no === idx) ?? items[idx - 1];
|
|
121
|
+
if (!item) {
|
|
122
|
+
console.error(`Row ${opts.row} not found`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const media = {
|
|
126
|
+
video_url: item.video_url,
|
|
127
|
+
play_url: item.play_url,
|
|
128
|
+
download_url: item.download_url,
|
|
129
|
+
title: item.title ?? item.desc,
|
|
130
|
+
author_name: item.author_name,
|
|
131
|
+
duration: item.duration,
|
|
132
|
+
};
|
|
133
|
+
printJson(media);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
export function registerTaskCommands(parent) {
|
|
3
|
+
const cmd = parent.command("task").description("Query task status and results");
|
|
4
|
+
cmd
|
|
5
|
+
.command("status")
|
|
6
|
+
.description("Check task status")
|
|
7
|
+
.requiredOption("--task-id <id>", "Task ID")
|
|
8
|
+
.action(async (opts) => {
|
|
9
|
+
const data = await request({ method: "GET", path: `/api/v1/tasks/${opts.taskId}` });
|
|
10
|
+
printJson(data);
|
|
11
|
+
});
|
|
12
|
+
cmd
|
|
13
|
+
.command("results")
|
|
14
|
+
.description("Get task results")
|
|
15
|
+
.requiredOption("--task-id <id>", "Task ID")
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const data = await request({ method: "GET", path: `/api/v1/tasks/${opts.taskId}/results` });
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { request, printJson } from "../client.js";
|
|
2
|
+
import { loadJsonFile } from "../utils/format.js";
|
|
3
|
+
export function registerTranscribeCommands(parent) {
|
|
4
|
+
const cmd = parent.command("transcribe").description("Video / audio transcription");
|
|
5
|
+
cmd
|
|
6
|
+
.command("submit")
|
|
7
|
+
.description("Submit batch video-to-text task")
|
|
8
|
+
.requiredOption("--json-file <path>", "JSON file with video task details")
|
|
9
|
+
.action(async (opts) => {
|
|
10
|
+
const payload = loadJsonFile(opts.jsonFile);
|
|
11
|
+
const data = await request({
|
|
12
|
+
method: "POST",
|
|
13
|
+
path: "/api/v1/media/batch-video-to-text",
|
|
14
|
+
payload,
|
|
15
|
+
});
|
|
16
|
+
printJson(data);
|
|
17
|
+
});
|
|
18
|
+
cmd
|
|
19
|
+
.command("text")
|
|
20
|
+
.description("Extract transcription text from completed task")
|
|
21
|
+
.requiredOption("--task-id <id>", "Task ID")
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
const taskData = await request({ method: "GET", path: `/api/v1/tasks/${opts.taskId}` });
|
|
24
|
+
const inner = taskData.data;
|
|
25
|
+
if (!inner || inner.status !== "SUCCESS") {
|
|
26
|
+
console.log(`Task status: ${inner?.status ?? "unknown"}`);
|
|
27
|
+
if (inner?.status !== "SUCCESS") {
|
|
28
|
+
printJson(inner);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const output = (typeof inner.output === "string" ? JSON.parse(inner.output) : inner.output);
|
|
33
|
+
const jsonUrl = output?.json_url;
|
|
34
|
+
if (!jsonUrl) {
|
|
35
|
+
console.error("No json_url in task output");
|
|
36
|
+
printJson(inner);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timer = setTimeout(() => controller.abort(), 60_000);
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(jsonUrl, { signal: controller.signal });
|
|
43
|
+
const transcript = await res.json();
|
|
44
|
+
const text = transcript.text ?? "";
|
|
45
|
+
const sentences = (transcript.timecodes?.sentences ?? []);
|
|
46
|
+
printJson({
|
|
47
|
+
task_id: opts.taskId,
|
|
48
|
+
text_field: "text",
|
|
49
|
+
sentence_field: "timecodes.sentences[*].text",
|
|
50
|
+
text,
|
|
51
|
+
sentence_count: sentences.length,
|
|
52
|
+
duration_ms: transcript.duration_ms,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AppConfig {
|
|
2
|
+
base_url?: string;
|
|
3
|
+
api_key?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function loadConfig(): AppConfig;
|
|
6
|
+
export declare function saveConfig(patch: Partial<AppConfig>): void;
|
|
7
|
+
export declare function resolveBaseUrl(override?: string): string;
|
|
8
|
+
export declare function resolveApiKey(override?: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const CONFIG_DIR = join(homedir(), ".9000ai");
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
6
|
+
export function loadConfig() {
|
|
7
|
+
if (!existsSync(CONFIG_FILE))
|
|
8
|
+
return {};
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveConfig(patch) {
|
|
17
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
18
|
+
const current = loadConfig();
|
|
19
|
+
const merged = { ...current, ...patch };
|
|
20
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
export function resolveBaseUrl(override) {
|
|
23
|
+
return (override ||
|
|
24
|
+
process.env["9000AI_BASE_URL"] ||
|
|
25
|
+
loadConfig().base_url ||
|
|
26
|
+
"http://127.0.0.1:8025");
|
|
27
|
+
}
|
|
28
|
+
export function resolveApiKey(override) {
|
|
29
|
+
const key = override ||
|
|
30
|
+
process.env["9000AI_API_KEY"] ||
|
|
31
|
+
loadConfig().api_key;
|
|
32
|
+
if (!key) {
|
|
33
|
+
console.error("Error: API key not configured. Run: 9000ai config set --api-key <key>");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return key;
|
|
37
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerConfigCommands } from "./commands/config.js";
|
|
4
|
+
import { registerAuthCommands } from "./commands/auth.js";
|
|
5
|
+
import { registerSearchCommands } from "./commands/search.js";
|
|
6
|
+
import { registerMonitorCommands } from "./commands/monitor.js";
|
|
7
|
+
import { registerTranscribeCommands } from "./commands/transcribe.js";
|
|
8
|
+
import { registerTaskCommands } from "./commands/task.js";
|
|
9
|
+
import { registerFeedbackCommands } from "./commands/feedback.js";
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program
|
|
12
|
+
.name("9000ai")
|
|
13
|
+
.description("9000AI Toolbox CLI — unified interface for 9000AI platform")
|
|
14
|
+
.version("0.1.0");
|
|
15
|
+
registerConfigCommands(program);
|
|
16
|
+
registerAuthCommands(program);
|
|
17
|
+
registerSearchCommands(program);
|
|
18
|
+
registerMonitorCommands(program);
|
|
19
|
+
registerTranscribeCommands(program);
|
|
20
|
+
registerTaskCommands(program);
|
|
21
|
+
registerFeedbackCommands(program);
|
|
22
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
23
|
+
console.error(err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function ensureOutputDir(): string;
|
|
2
|
+
export declare function writeJson(filename: string, data: unknown): string;
|
|
3
|
+
export declare function writeTsv(filename: string, rows: Record<string, unknown>[]): string;
|
|
4
|
+
export declare function listOutputFiles(): string[];
|
|
5
|
+
export declare function readOutputJson(nameOrPath: string): unknown;
|
|
6
|
+
export declare function timestampSlug(): string;
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readdirSync, readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
const OUTPUT_DIR = resolve("output");
|
|
4
|
+
export function ensureOutputDir() {
|
|
5
|
+
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
6
|
+
return OUTPUT_DIR;
|
|
7
|
+
}
|
|
8
|
+
export function writeJson(filename, data) {
|
|
9
|
+
const dir = ensureOutputDir();
|
|
10
|
+
const filepath = join(dir, filename);
|
|
11
|
+
writeFileSync(filepath, JSON.stringify(data, null, 2), "utf-8");
|
|
12
|
+
return filepath;
|
|
13
|
+
}
|
|
14
|
+
export function writeTsv(filename, rows) {
|
|
15
|
+
if (rows.length === 0)
|
|
16
|
+
return "";
|
|
17
|
+
const dir = ensureOutputDir();
|
|
18
|
+
const filepath = join(dir, filename);
|
|
19
|
+
const keys = Object.keys(rows[0]);
|
|
20
|
+
const header = keys.join("\t");
|
|
21
|
+
const lines = rows.map((row) => keys.map((k) => String(row[k] ?? "")).join("\t"));
|
|
22
|
+
writeFileSync(filepath, [header, ...lines].join("\n"), "utf-8");
|
|
23
|
+
return filepath;
|
|
24
|
+
}
|
|
25
|
+
export function listOutputFiles() {
|
|
26
|
+
if (!existsSync(OUTPUT_DIR))
|
|
27
|
+
return [];
|
|
28
|
+
return readdirSync(OUTPUT_DIR).sort();
|
|
29
|
+
}
|
|
30
|
+
export function readOutputJson(nameOrPath) {
|
|
31
|
+
const aliases = {
|
|
32
|
+
latest_hot: "latest_hot.json",
|
|
33
|
+
latest_search: "latest_search.json",
|
|
34
|
+
};
|
|
35
|
+
const filename = aliases[nameOrPath] ?? nameOrPath;
|
|
36
|
+
const filepath = filename.includes("/") || filename.includes("\\")
|
|
37
|
+
? resolve(filename)
|
|
38
|
+
: join(OUTPUT_DIR, filename);
|
|
39
|
+
if (!existsSync(filepath)) {
|
|
40
|
+
console.error(`File not found: ${filepath}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
return JSON.parse(readFileSync(filepath, "utf-8"));
|
|
44
|
+
}
|
|
45
|
+
export function timestampSlug() {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
48
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall: copy bundled skills to ~/.9000ai/skills/
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, cpSync } from "fs";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const src = join(__dirname, "..", "skills");
|
|
11
|
+
const dest = join(homedir(), ".9000ai", "skills");
|
|
12
|
+
if (!existsSync(src)) {
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
mkdirSync(dest, { recursive: true });
|
|
16
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
17
|
+
console.log(`9000AI skills installed → ${dest}`);
|