Skip to content

Commit ffc4601

Browse files
authored
Merge pull request #165 from modelstudioai/feat/cli-skill-sync
Feat/cli skill sync
2 parents 5ed15d3 + 3909a17 commit ffc4601

60 files changed

Lines changed: 3023 additions & 971 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Poke the FC publish-skills flow after skills/ changes land.
2+
# The FC side reconciles this repo's skills/ directory against OSS
3+
# (bailian-wiki/skills/) using the repo HEAD snapshot as the only
4+
# source of truth — the request itself carries no content. Both the
5+
# repo and branch params are validated against FC-side whitelists
6+
# (PUBLISH_REPOS / PUBLISH_BRANCHES).
7+
#
8+
# feat/cli-skill-sync is temporary for end-to-end testing; remove it
9+
# (here and from the FC PUBLISH_BRANCHES whitelist) once the sync
10+
# link is verified on main.
11+
name: Publish skills to OSS
12+
13+
on:
14+
push:
15+
branches:
16+
- main
17+
- feat/cli-skill-sync
18+
paths:
19+
- "skills/**"
20+
21+
jobs:
22+
poke:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Trigger FC publish-skills
26+
run: |
27+
curl -sf -X POST "${{ vars.FC_TRIGGER_URL }}/publish-skills?repo=modelstudioai/cli&branch=${{ github.ref_name }}"

packages/cli/src/commands.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
finetuneTextCreate,
6868
finetuneAudioCreate,
6969
finetuneImageCreate,
70+
finetuneVideoCreate,
7071
finetuneList,
7172
finetuneGet,
7273
finetuneCancel,
@@ -76,6 +77,7 @@ import {
7677
finetuneExport,
7778
finetuneWatch,
7879
finetuneCapability,
80+
finetunePrice,
7981
deployTextCreate,
8082
deployAudioCreate,
8183
deployImageCreate,
@@ -85,6 +87,8 @@ import {
8587
deployScale,
8688
deployUpdate,
8789
deployDelete,
90+
deployPause,
91+
deployResume,
8892
tokenPlanListSeats,
8993
tokenPlanCreateKey,
9094
tokenPlanAssignSeats,
@@ -191,6 +195,7 @@ export const commands: Record<string, AnyCommand> = {
191195
"finetune text create": finetuneTextCreate,
192196
"finetune audio create": finetuneAudioCreate,
193197
"finetune image create": finetuneImageCreate,
198+
"finetune video create": finetuneVideoCreate,
194199
"finetune list": finetuneList,
195200
"finetune get": finetuneGet,
196201
"finetune cancel": finetuneCancel,
@@ -200,6 +205,7 @@ export const commands: Record<string, AnyCommand> = {
200205
"finetune export": finetuneExport,
201206
"finetune watch": finetuneWatch,
202207
"finetune capability": finetuneCapability,
208+
"finetune price": finetunePrice,
203209
"deploy text create": deployTextCreate,
204210
"deploy audio create": deployAudioCreate,
205211
"deploy image create": deployImageCreate,
@@ -209,6 +215,8 @@ export const commands: Record<string, AnyCommand> = {
209215
"deploy scale": deployScale,
210216
"deploy update": deployUpdate,
211217
"deploy delete": deployDelete,
218+
"deploy pause": deployPause,
219+
"deploy resume": deployResume,
212220
"token-plan list-seats": tokenPlanListSeats,
213221
"token-plan create-key": tokenPlanCreateKey,
214222
"token-plan assign-seats": tokenPlanAssignSeats,

packages/commands/src/commands/dataset/delete.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineCommand, detectOutputFormat, deleteDataset, type FlagsDef } from "bailian-cli-core";
2-
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
1+
import { defineCommand, deleteDataset, type FlagsDef } from "bailian-cli-core";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
33

44
const DELETE_FLAGS = {
55
fileId: {
@@ -19,20 +19,18 @@ export default defineCommand({
1919
async run(ctx) {
2020
const { settings, flags } = ctx;
2121
const fileId = flags.fileId;
22-
const format = detectOutputFormat(settings.output);
2322

2423
if (settings.dryRun) {
25-
emitResult({ action: "dataset.delete", file_id: fileId }, format);
24+
emitResult({ action: "dataset.delete", file_id: fileId }, "json");
2625
return;
2726
}
2827

2928
const response = await deleteDataset(ctx.client, fileId);
3029

31-
if (settings.quiet || format === "text") {
32-
emitBare(`Deleted ${fileId}.`);
33-
emitRequestId(response.request_id, settings.quiet);
30+
if (settings.quiet) {
31+
emitBare(fileId);
3432
} else {
35-
emitResult(response, format);
33+
emitResult(response, "json");
3634
}
3735
},
3836
});

packages/commands/src/commands/dataset/get.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineCommand, detectOutputFormat, getDataset, type FlagsDef } from "bailian-cli-core";
2-
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
1+
import { defineCommand, getDataset, type FlagsDef } from "bailian-cli-core";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
33

44
const GET_FLAGS = {
55
fileId: {
@@ -19,10 +19,9 @@ export default defineCommand({
1919
async run(ctx) {
2020
const { settings, flags } = ctx;
2121
const fileId = flags.fileId;
22-
const format = detectOutputFormat(settings.output);
2322

2423
if (settings.dryRun) {
25-
emitResult({ action: "dataset.get", file_id: fileId }, format);
24+
emitResult({ action: "dataset.get", file_id: fileId }, "json");
2625
return;
2726
}
2827

@@ -45,19 +44,10 @@ export default defineCommand({
4544
description: file.description ?? "",
4645
};
4746

48-
if (format === "json") {
49-
emitResult({ ...item, request_id: response.request_id }, format);
50-
return;
47+
if (settings.quiet) {
48+
emitBare(item.file_id);
49+
} else {
50+
emitResult({ ...item, request_id: response.request_id }, "json");
5151
}
52-
53-
// text / quiet
54-
emitBare(`file_id: ${item.file_id}`);
55-
emitBare(`name: ${item.name}`);
56-
emitBare(`size: ${item.size}`);
57-
if (item.md5) emitBare(`md5: ${item.md5}`);
58-
if (item.purpose) emitBare(`purpose: ${item.purpose}`);
59-
if (item.created_at) emitBare(`created_at: ${item.created_at}`);
60-
if (item.description) emitBare(`description: ${item.description}`);
61-
emitRequestId(response.request_id, settings.quiet);
6252
},
6353
});

packages/commands/src/commands/dataset/list.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineCommand, detectOutputFormat, listDatasets, type FlagsDef } from "bailian-cli-core";
2-
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
1+
import { defineCommand, listDatasets, type FlagsDef } from "bailian-cli-core";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
33

44
const LIST_FLAGS = {
55
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
@@ -23,7 +23,6 @@ export default defineCommand({
2323
exampleArgs: ["", "--purpose fine-tune", "--purpose evaluation --page-size 20", "--output json"],
2424
async run(ctx) {
2525
const { settings, flags } = ctx;
26-
const format = detectOutputFormat(settings.output);
2726

2827
if (settings.dryRun) {
2928
emitResult(
@@ -33,7 +32,7 @@ export default defineCommand({
3332
page_size: flags.pageSize,
3433
purpose: flags.purpose,
3534
},
36-
format,
35+
"json",
3736
);
3837
return;
3938
}
@@ -46,28 +45,17 @@ export default defineCommand({
4645
const files = response.data?.files ?? [];
4746
const total = response.data?.total;
4847

49-
// Normalize to consistent structure for both text/json output.
5048
const items = files.map((item) => ({
5149
file_id: item.file_id ?? "",
5250
name: item.name ?? "",
5351
size: item.size !== undefined ? `${(item.size / 1024).toFixed(1)} KB` : "?",
5452
purpose: item.purpose ?? "",
5553
}));
5654

57-
if (format === "json") {
58-
emitResult({ items, total, request_id: response.request_id }, format);
59-
return;
60-
}
61-
62-
// text / quiet
63-
if (items.length === 0) {
64-
emitBare("No dataset files found.");
65-
return;
55+
if (settings.quiet) {
56+
for (const item of items) emitBare(item.file_id);
57+
} else {
58+
emitResult({ items, total, request_id: response.request_id }, "json");
6659
}
67-
const headers = ["FILE_ID", "NAME", "SIZE", "PURPOSE"];
68-
const rows = items.map((i) => [i.file_id, i.name, i.size, i.purpose]);
69-
for (const line of formatTable(headers, rows)) emitBare(line);
70-
if (total !== undefined) emitBare(`\nTotal: ${total}`);
71-
emitRequestId(response.request_id, settings.quiet);
7260
},
7361
});

packages/commands/src/commands/dataset/upload.ts

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import {
22
defineCommand,
3-
detectOutputFormat,
43
uploadDataset,
54
validateDataset,
65
parseDatasetSchemaFlag,
76
formatIssue,
87
MAX_DATASET_BYTES,
8+
MAX_CPT_BYTES,
99
MAX_MEDIA_ZIP_BYTES,
1010
BailianError,
1111
ExitCode,
1212
type FlagsDef,
1313
} from "bailian-cli-core";
14-
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
14+
import { emitResult, emitBare } from "bailian-cli-runtime";
1515

1616
const UPLOAD_FLAGS = {
1717
file: {
1818
type: "string",
1919
valueHint: "<path>",
20-
description: "Local dataset file (.jsonl or .zip; ≤300MB text, ≤1GB image)",
20+
description: "Local dataset file (.jsonl or .zip; ≤200MB SFT/DPO, ≤300MB CPT, ≤2GB media zip)",
2121
required: true,
2222
},
2323
purpose: {
@@ -29,7 +29,7 @@ const UPLOAD_FLAGS = {
2929
type: "string",
3030
valueHint: "<s>",
3131
description:
32-
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), or "image" (image generation). Default auto-detects per record.',
32+
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.',
3333
},
3434
noValidate: {
3535
type: "switch",
@@ -45,7 +45,7 @@ export default defineCommand({
4545
description: "Upload a dataset file (.jsonl or .zip) to Bailian",
4646
auth: "apiKey",
4747
usageArgs:
48-
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt|tts|image>] [--no-validate] [--full-validate]",
48+
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt|tts|image|video>] [--no-validate] [--full-validate]",
4949
flags: UPLOAD_FLAGS,
5050
exampleArgs: [
5151
"--file train.jsonl",
@@ -58,13 +58,14 @@ export default defineCommand({
5858
],
5959
notes: [
6060
"Supports .jsonl (text) and .zip (audio/image archives with a data.jsonl",
61-
"manifest). Five record schemas are recognized: chatml = {messages:[...]}",
61+
"manifest). Six record schemas are recognized: chatml = {messages:[...]}",
6262
'(SFT); dpo = {messages:[...], chosen, rejected}; cpt = {text:"..."}',
6363
'(continual pre-training, raw text); tts = {wav_fn:"train/xxx.wav",',
6464
'text:"..."} (audio fine-tuning); image = {img_path:"..."} (image',
65-
"generation). With no --schema, a record carrying wav_fn is validated as",
66-
"TTS, img_path as image, chosen/rejected as DPO, text (no messages) as CPT,",
67-
"otherwise ChatML. Upload cap: 300MB text, 1GB image. Upload uses the",
65+
"generation); video = {first_frame_path:...} (video generation). With no",
66+
"--schema, a record carrying wav_fn is validated as TTS, img_path as image,",
67+
"chosen/rejected as DPO, text (no messages) as CPT, otherwise ChatML.",
68+
"Upload cap: 200MB SFT/DPO text, 300MB CPT, 2GB media zip. Upload uses the",
6869
"OpenAI-compatible /compatible-mode/v1/files endpoint so the purpose tag is",
6970
"persisted (the DashScope-native /api/v1/files drops it).",
7071
],
@@ -73,19 +74,15 @@ export default defineCommand({
7374
const filePath = flags.file;
7475
const purpose = flags.purpose || "fine-tune";
7576
const schema = parseDatasetSchemaFlag(flags.schema);
76-
if (schema === "video") {
77-
throw new BailianError(
78-
`--schema video is not supported.`,
79-
ExitCode.USAGE,
80-
`Supported schemas: chatml, dpo, cpt, tts, image.`,
81-
);
82-
}
83-
const format = detectOutputFormat(settings.output);
84-
// Image schema allows larger ZIPs (1 GB vs 300 MB for text).
85-
const isMediaSchema = schema === "image";
77+
// Size caps differ per training type: SFT/DPO 200MB, CPT 300MB, media ZIP 2GB.
78+
const isMediaSchema = schema === "image" || schema === "video";
79+
const maxBytes = isMediaSchema
80+
? MAX_MEDIA_ZIP_BYTES
81+
: schema === "cpt"
82+
? MAX_CPT_BYTES
83+
: MAX_DATASET_BYTES;
8684

8785
if (!flags.noValidate) {
88-
const maxBytes = isMediaSchema ? MAX_MEDIA_ZIP_BYTES : MAX_DATASET_BYTES;
8986
const result = await validateDataset(filePath, {
9087
fullValidate: flags.fullValidate,
9188
schema,
@@ -125,11 +122,11 @@ export default defineCommand({
125122
action: "dataset.upload",
126123
file: filePath,
127124
purpose,
128-
max_bytes: isMediaSchema ? MAX_MEDIA_ZIP_BYTES : MAX_DATASET_BYTES,
125+
max_bytes: maxBytes,
129126
validate: !flags.noValidate,
130127
schema: schema ?? "auto",
131128
},
132-
format,
129+
"json",
133130
);
134131
return;
135132
}
@@ -142,11 +139,8 @@ export default defineCommand({
142139

143140
if (settings.quiet) {
144141
emitBare(file.file_id);
145-
} else if (format === "text") {
146-
emitBare(`Uploaded ${file.name} → file_id=${file.file_id}`);
147-
emitRequestId(request_id, settings.quiet);
148142
} else {
149-
emitResult({ ...file, request_id }, format);
143+
emitResult({ ...file, request_id }, "json");
150144
}
151145
},
152146
});

0 commit comments

Comments
 (0)