@302ai/media-studio-cli 0.1.0-beta.6 → 0.1.0-beta.7
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 +26 -2
- package/dist/index.js +307 -82
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,6 @@ msc chat "hi" --session <id> # 切换到指定会话并设为默认
|
|
|
53
53
|
msc sessions # 列出所有会话(标记当前默认 `(current)`)
|
|
54
54
|
msc sessions --json # 机器可读的原始 JSON 数组
|
|
55
55
|
msc sessions use <id> # 纯本地切换默认会话(零请求、不校验)
|
|
56
|
-
msc sessions rm <id> # 删除服务端会话(交互确认,或加 -y/--yes 跳过)
|
|
57
56
|
msc skills # 列出技能库(--plaza 浏览技能广场)
|
|
58
57
|
msc skills add <slug> # 从广场安装技能(如 brainstorming,见 --plaza --json 的 slug)
|
|
59
58
|
msc skills rm <command> # 从技能库移除(-y/--yes 跳过确认)
|
|
@@ -61,6 +60,9 @@ msc skills enable <command> # 启用 / disable 停用
|
|
|
61
60
|
msc --help # all flags
|
|
62
61
|
```
|
|
63
62
|
|
|
63
|
+
Session deletion is not available in the CLI. To delete a session, open the
|
|
64
|
+
corresponding session page in the Media Studio web app and delete it manually.
|
|
65
|
+
|
|
64
66
|
`msc --help` carries an agent-oriented quickstart — the auth prerequisite, the
|
|
65
67
|
three canonical calls, and the `--json` contract. `msc guide` prints the full
|
|
66
68
|
manual: every command, both JSON schemas, the `nextStep` values, exit codes,
|
|
@@ -180,6 +182,29 @@ alone triggers nothing.
|
|
|
180
182
|
flag on your account (a persistent change), is reported as
|
|
181
183
|
`Auto-enabled for this turn: …`, and a failed enable aborts the turn.
|
|
182
184
|
|
|
185
|
+
### Attachments
|
|
186
|
+
|
|
187
|
+
Attach local files to a chat turn using `@path` anywhere in the prompt:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
msc chat "看看 @cover.png 这张图" # bare path (ends at whitespace)
|
|
191
|
+
msc chat "总结 @/abs/path/notes.pdf" # absolute path
|
|
192
|
+
msc chat '对比 @"my docs/report.docx" 内容' # quoted path with spaces
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The CLI mirrors the web composer's upload flow:
|
|
196
|
+
|
|
197
|
+
1. Validates each referenced file locally (exists, readable, supported extension, under 50 MB) before any network call.
|
|
198
|
+
2. Uploads each file via the web app's `POST /api/chat/attachments` endpoint with a terminal spinner (`Uploading attachment 1/2: cover.png`).
|
|
199
|
+
3. Rewrites the prompt to inline the remote URL using the web's `&&<url>&&` marker, which the upstream model and web session history natively understand.
|
|
200
|
+
4. Up to 5 unique attachments per turn (same as the web composer); referencing the same file twice uploads it once.
|
|
201
|
+
5. An unattachable reference (missing file, unsupported format) fails the turn before any session is provisioned.
|
|
202
|
+
6. Prose mentions (`@team 辛苦了`, email addresses) stay literal text — only tokens with a file extension or path slash are treated as attachments.
|
|
203
|
+
7. `@path` is local-filesystem-only: `@https://example.com/a.png` fails with `no such file`. There is no `@url` form — remote URLs are not attachments.
|
|
204
|
+
|
|
205
|
+
In `--json` mode the uploaded attachments are surfaced under the `attachments` array:
|
|
206
|
+
`[{ name, type, size, remoteUrl }]`.
|
|
207
|
+
|
|
183
208
|
### Staging server (expired TLS certificate)
|
|
184
209
|
|
|
185
210
|
The staging server's certificate is expired (ops declines renewal), so the
|
|
@@ -301,7 +326,6 @@ msc chat "刚才的数字是几?" # answers 42
|
|
|
301
326
|
msc sessions # shows session list with (current) marker
|
|
302
327
|
msc chat "hi" --session <id> # switch to specific session
|
|
303
328
|
msc sessions use <id> # switch default pointer locally
|
|
304
|
-
msc sessions rm <id> -y # delete session upstream (-y skips prompt)
|
|
305
329
|
```
|
|
306
330
|
|
|
307
331
|
Without the local server, point at production with
|
package/dist/index.js
CHANGED
|
@@ -4284,7 +4284,7 @@ var consola = createConsola2();
|
|
|
4284
4284
|
// package.json
|
|
4285
4285
|
var package_default = {
|
|
4286
4286
|
name: "@302ai/media-studio-cli",
|
|
4287
|
-
version: "0.1.0-beta.
|
|
4287
|
+
version: "0.1.0-beta.7",
|
|
4288
4288
|
description: "CLI for AI Media Studio — let external agents drive the core media-studio features",
|
|
4289
4289
|
author: "302.AI",
|
|
4290
4290
|
license: "Apache-2.0",
|
|
@@ -9092,6 +9092,9 @@ var ZodURL = /* @__PURE__ */ $constructor("ZodURL", (inst, def) => {
|
|
|
9092
9092
|
$ZodURL.init(inst, def);
|
|
9093
9093
|
ZodStringFormat.init(inst, def);
|
|
9094
9094
|
});
|
|
9095
|
+
function url(params) {
|
|
9096
|
+
return _url(ZodURL, params);
|
|
9097
|
+
}
|
|
9095
9098
|
var ZodEmoji = /* @__PURE__ */ $constructor("ZodEmoji", (inst, def) => {
|
|
9096
9099
|
$ZodEmoji.init(inst, def);
|
|
9097
9100
|
ZodStringFormat.init(inst, def);
|
|
@@ -9643,6 +9646,83 @@ var ArtifactItemSchema = object({
|
|
|
9643
9646
|
productIdx: number2().optional()
|
|
9644
9647
|
});
|
|
9645
9648
|
var ArtifactItemsSchema = array(ArtifactItemSchema);
|
|
9649
|
+
|
|
9650
|
+
// ../media-studio-contract/src/attachments/constants.ts
|
|
9651
|
+
var MAX_ATTACHMENT_COUNT = 5;
|
|
9652
|
+
var MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
|
|
9653
|
+
var ATTACHMENT_MIME_BY_EXTENSION = {
|
|
9654
|
+
png: "image/png",
|
|
9655
|
+
jpg: "image/jpeg",
|
|
9656
|
+
jpeg: "image/jpeg",
|
|
9657
|
+
gif: "image/gif",
|
|
9658
|
+
webp: "image/webp",
|
|
9659
|
+
avif: "image/avif",
|
|
9660
|
+
bmp: "image/bmp",
|
|
9661
|
+
svg: "image/svg+xml",
|
|
9662
|
+
ico: "image/x-icon",
|
|
9663
|
+
tif: "image/tiff",
|
|
9664
|
+
tiff: "image/tiff",
|
|
9665
|
+
heic: "image/heic",
|
|
9666
|
+
mp4: "video/mp4",
|
|
9667
|
+
webm: "video/webm",
|
|
9668
|
+
mov: "video/quicktime",
|
|
9669
|
+
m4v: "video/mp4",
|
|
9670
|
+
avi: "video/x-msvideo",
|
|
9671
|
+
mkv: "video/x-matroska",
|
|
9672
|
+
mp3: "audio/mpeg",
|
|
9673
|
+
wav: "audio/wav",
|
|
9674
|
+
m4a: "audio/mp4",
|
|
9675
|
+
aac: "audio/aac",
|
|
9676
|
+
ogg: "audio/ogg",
|
|
9677
|
+
flac: "audio/flac",
|
|
9678
|
+
opus: "audio/opus",
|
|
9679
|
+
pdf: "application/pdf",
|
|
9680
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
9681
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
9682
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
9683
|
+
txt: "text/plain",
|
|
9684
|
+
log: "text/plain",
|
|
9685
|
+
srt: "text/plain",
|
|
9686
|
+
vtt: "text/plain",
|
|
9687
|
+
ini: "text/plain",
|
|
9688
|
+
toml: "text/plain",
|
|
9689
|
+
jsonl: "text/plain",
|
|
9690
|
+
md: "text/markdown",
|
|
9691
|
+
markdown: "text/markdown",
|
|
9692
|
+
html: "text/html",
|
|
9693
|
+
htm: "text/html",
|
|
9694
|
+
csv: "text/csv",
|
|
9695
|
+
tsv: "text/tab-separated-values",
|
|
9696
|
+
json: "application/json",
|
|
9697
|
+
yaml: "text/yaml",
|
|
9698
|
+
yml: "text/yaml",
|
|
9699
|
+
xml: "text/xml"
|
|
9700
|
+
};
|
|
9701
|
+
|
|
9702
|
+
// ../media-studio-contract/src/attachments/markers.ts
|
|
9703
|
+
var ATTACHMENT_MARKER = "&&";
|
|
9704
|
+
function formatAttachmentMarker(remoteUrl) {
|
|
9705
|
+
return `${ATTACHMENT_MARKER}${remoteUrl}${ATTACHMENT_MARKER}`;
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9708
|
+
// ../media-studio-contract/src/attachments/wire.ts
|
|
9709
|
+
var AttachmentKindSchema = _enum([
|
|
9710
|
+
"image",
|
|
9711
|
+
"video",
|
|
9712
|
+
"audio",
|
|
9713
|
+
"text",
|
|
9714
|
+
"file"
|
|
9715
|
+
]);
|
|
9716
|
+
var UploadedAttachmentItemSchema = object({
|
|
9717
|
+
id: string2(),
|
|
9718
|
+
name: string2(),
|
|
9719
|
+
type: AttachmentKindSchema,
|
|
9720
|
+
size: number2(),
|
|
9721
|
+
remoteUrl: url()
|
|
9722
|
+
});
|
|
9723
|
+
var AttachmentUploadResponseSchema = object({
|
|
9724
|
+
files: array(UploadedAttachmentItemSchema)
|
|
9725
|
+
});
|
|
9646
9726
|
// ../../node_modules/es-toolkit/dist/compat/predicate/isObject.mjs
|
|
9647
9727
|
function isObject2(value) {
|
|
9648
9728
|
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
@@ -10279,6 +10359,41 @@ function extractUpstreamErrorMessage(body) {
|
|
|
10279
10359
|
}
|
|
10280
10360
|
return M(body).with({ error: { message: z2.select(z2.string) } }, (msg) => msg.trim() || null).with({ message: z2.select(z2.string) }, (msg) => msg !== ERR_SESSION_EXPIRED ? msg.trim() || null : null).with({ error: z2.select(z2.string) }, (msg) => msg !== ERR_SESSION_EXPIRED ? msg.trim() || null : null).with(z2._, () => null).exhaustive();
|
|
10281
10361
|
}
|
|
10362
|
+
|
|
10363
|
+
// src/client/http.ts
|
|
10364
|
+
async function requestJson(fetchFn, url, init, schema, fallbackMessage) {
|
|
10365
|
+
const response = await fetchFn(url, init);
|
|
10366
|
+
if (!response.ok) {
|
|
10367
|
+
const rawBody = await response.text();
|
|
10368
|
+
throw new ChatUpstreamError(extractUpstreamErrorMessage(rawBody) || fallbackMessage(response.status), response.status, rawBody);
|
|
10369
|
+
}
|
|
10370
|
+
return schema.parse(await response.json());
|
|
10371
|
+
}
|
|
10372
|
+
|
|
10373
|
+
// src/client/attachments.ts
|
|
10374
|
+
class ClientAttachments {
|
|
10375
|
+
baseUrl;
|
|
10376
|
+
apiKey;
|
|
10377
|
+
customFetch;
|
|
10378
|
+
constructor(baseUrl, apiKey, customFetch) {
|
|
10379
|
+
this.baseUrl = baseUrl;
|
|
10380
|
+
this.apiKey = apiKey;
|
|
10381
|
+
this.customFetch = customFetch ?? fetch;
|
|
10382
|
+
}
|
|
10383
|
+
async upload(items) {
|
|
10384
|
+
const formData = new FormData;
|
|
10385
|
+
for (const item of items) {
|
|
10386
|
+
formData.append("files", item.file, item.file.name);
|
|
10387
|
+
formData.append("attachmentIds", item.id);
|
|
10388
|
+
}
|
|
10389
|
+
const response = await requestJson(this.customFetch, `${this.baseUrl}/api/chat/attachments`, {
|
|
10390
|
+
method: "POST",
|
|
10391
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
10392
|
+
body: formData
|
|
10393
|
+
}, AttachmentUploadResponseSchema, (status) => `Attachment upload failed with status ${status}`);
|
|
10394
|
+
return response.files;
|
|
10395
|
+
}
|
|
10396
|
+
}
|
|
10282
10397
|
// ../../node_modules/eventsource-parser/dist/errors.js
|
|
10283
10398
|
class ParseError extends Error {
|
|
10284
10399
|
constructor(message, options) {
|
|
@@ -16670,15 +16785,6 @@ function readUpdateCheck() {
|
|
|
16670
16785
|
function saveUpdateCheck(check) {
|
|
16671
16786
|
writeConfigFile({ updateCheck: check });
|
|
16672
16787
|
}
|
|
16673
|
-
function clearLastSessionId(ifMatchesId) {
|
|
16674
|
-
const current = readConfigFile();
|
|
16675
|
-
if (!current?.lastSessionId)
|
|
16676
|
-
return;
|
|
16677
|
-
if (ifMatchesId && current.lastSessionId !== ifMatchesId)
|
|
16678
|
-
return;
|
|
16679
|
-
const { lastSessionId: _removed, ...rest } = current;
|
|
16680
|
-
saveConfigFile(rest);
|
|
16681
|
-
}
|
|
16682
16788
|
function resolveApiKey() {
|
|
16683
16789
|
const token = readAuthFile()?.token;
|
|
16684
16790
|
if (!token?.trim()) {
|
|
@@ -16698,16 +16804,6 @@ function resolveApiBaseUrl(flag) {
|
|
|
16698
16804
|
return getDefaultApiBaseUrl();
|
|
16699
16805
|
}
|
|
16700
16806
|
|
|
16701
|
-
// src/client/http.ts
|
|
16702
|
-
async function requestJson(fetchFn, url, init, schema, fallbackMessage) {
|
|
16703
|
-
const response = await fetchFn(url, init);
|
|
16704
|
-
if (!response.ok) {
|
|
16705
|
-
const rawBody = await response.text();
|
|
16706
|
-
throw new ChatUpstreamError(extractUpstreamErrorMessage(rawBody) || fallbackMessage(response.status), response.status, rawBody);
|
|
16707
|
-
}
|
|
16708
|
-
return schema.parse(await response.json());
|
|
16709
|
-
}
|
|
16710
|
-
|
|
16711
16807
|
// src/client/session-types.ts
|
|
16712
16808
|
var SessionSourceSchema = _enum2(["web", "cli"]);
|
|
16713
16809
|
var ApiResponseBaseSchema = object2({
|
|
@@ -16796,7 +16892,6 @@ var ListArtifactsQuerySchema = object2({
|
|
|
16796
16892
|
var ListArtifactsResponseSchema = ApiResponseBaseSchema.extend({
|
|
16797
16893
|
data: ArtifactItemsSchema
|
|
16798
16894
|
});
|
|
16799
|
-
var DeleteSessionResponseSchema = ApiResponseBaseSchema;
|
|
16800
16895
|
var PrewarmSessionReadySchema = object2({
|
|
16801
16896
|
session_id: string4(),
|
|
16802
16897
|
note: string4().nullable(),
|
|
@@ -16872,10 +16967,6 @@ class ClientSessions {
|
|
|
16872
16967
|
const url = `${this.baseUrl}/api/sessions/tasks?${params.toString()}`;
|
|
16873
16968
|
return requestJson(this.customFetch, url, { method: "GET", headers: this.authHeaders() }, ListArtifactsResponseSchema, (status) => `Failed to fetch session artifacts with status ${status}`);
|
|
16874
16969
|
}
|
|
16875
|
-
async remove(sessionId) {
|
|
16876
|
-
const url = `${this.baseUrl}/api/sessions?sessionId=${encodeURIComponent(sessionId)}`;
|
|
16877
|
-
await requestJson(this.customFetch, url, { method: "DELETE", headers: this.authHeaders() }, DeleteSessionResponseSchema, (status) => `Failed to delete session with status ${status}`);
|
|
16878
|
-
}
|
|
16879
16970
|
async prewarm(options) {
|
|
16880
16971
|
const { onStageChange, onSessionReady } = options;
|
|
16881
16972
|
const timeoutController = new AbortController;
|
|
@@ -17144,6 +17235,7 @@ class MediaStudioClient {
|
|
|
17144
17235
|
chat;
|
|
17145
17236
|
sessions;
|
|
17146
17237
|
skills;
|
|
17238
|
+
attachments;
|
|
17147
17239
|
customFetch;
|
|
17148
17240
|
get apiKey() {
|
|
17149
17241
|
return this._apiKey;
|
|
@@ -17182,6 +17274,7 @@ class MediaStudioClient {
|
|
|
17182
17274
|
this.chat = new ClientChat(this.baseUrl, this._apiKey, interceptingFetch);
|
|
17183
17275
|
this.sessions = new ClientSessions(this.baseUrl, this._apiKey, interceptingFetch);
|
|
17184
17276
|
this.skills = new ClientSkills(this.baseUrl, this._apiKey, interceptingFetch);
|
|
17277
|
+
this.attachments = new ClientAttachments(this.baseUrl, this._apiKey, interceptingFetch);
|
|
17185
17278
|
}
|
|
17186
17279
|
async getMe() {
|
|
17187
17280
|
return requestJson(this.customFetch, `${this.baseUrl}/api/auth/me`, {
|
|
@@ -17575,6 +17668,144 @@ class AgentSpinner {
|
|
|
17575
17668
|
}
|
|
17576
17669
|
}
|
|
17577
17670
|
|
|
17671
|
+
// src/services/attachments.ts
|
|
17672
|
+
import { readFile, stat } from "node:fs/promises";
|
|
17673
|
+
import { basename, extname, resolve } from "node:path";
|
|
17674
|
+
|
|
17675
|
+
// src/lib/attachment-refs.ts
|
|
17676
|
+
var PATH_LIKE_RE = /[/\\]|\.[A-Za-z0-9]{1,10}$/;
|
|
17677
|
+
var REF_RE = /(^|(?<=\s))@(?:"([^"]+)"|([^\s@]+))/g;
|
|
17678
|
+
function findAttachmentRefs(prompt) {
|
|
17679
|
+
const refs = [];
|
|
17680
|
+
for (const match of prompt.matchAll(REF_RE)) {
|
|
17681
|
+
const start = match.index;
|
|
17682
|
+
const quoted = match[2];
|
|
17683
|
+
const bare = match[3];
|
|
17684
|
+
const path = quoted ?? bare;
|
|
17685
|
+
if (path === undefined || path.length === 0)
|
|
17686
|
+
continue;
|
|
17687
|
+
if (!PATH_LIKE_RE.test(path))
|
|
17688
|
+
continue;
|
|
17689
|
+
refs.push({ start, end: start + match[0].length, path });
|
|
17690
|
+
}
|
|
17691
|
+
return refs;
|
|
17692
|
+
}
|
|
17693
|
+
function composeAttachmentPrompt(prompt, urlByPath) {
|
|
17694
|
+
const refs = findAttachmentRefs(prompt);
|
|
17695
|
+
if (refs.length === 0)
|
|
17696
|
+
return prompt;
|
|
17697
|
+
const segments = [];
|
|
17698
|
+
let cursor = 0;
|
|
17699
|
+
for (const ref of refs) {
|
|
17700
|
+
segments.push(prompt.slice(cursor, ref.start));
|
|
17701
|
+
const url = urlByPath[ref.path];
|
|
17702
|
+
segments.push(url ? formatAttachmentMarker(url) : prompt.slice(ref.start, ref.end));
|
|
17703
|
+
cursor = ref.end;
|
|
17704
|
+
}
|
|
17705
|
+
segments.push(prompt.slice(cursor));
|
|
17706
|
+
return segments.join("");
|
|
17707
|
+
}
|
|
17708
|
+
|
|
17709
|
+
// src/services/attachments.ts
|
|
17710
|
+
class AttachmentResolutionError extends Error {
|
|
17711
|
+
}
|
|
17712
|
+
function formatBytes(size) {
|
|
17713
|
+
if (size < 1024)
|
|
17714
|
+
return `${size} B`;
|
|
17715
|
+
if (size < 1024 * 1024)
|
|
17716
|
+
return `${(size / 1024).toFixed(1)} KB`;
|
|
17717
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
17718
|
+
}
|
|
17719
|
+
async function readAttachment(typedPath, absPath) {
|
|
17720
|
+
let stats;
|
|
17721
|
+
try {
|
|
17722
|
+
stats = await stat(absPath);
|
|
17723
|
+
} catch {
|
|
17724
|
+
throw new AttachmentResolutionError(`@${typedPath}: no such file (${absPath})`);
|
|
17725
|
+
}
|
|
17726
|
+
if (!stats.isFile()) {
|
|
17727
|
+
throw new AttachmentResolutionError(`@${typedPath}: not a regular file`);
|
|
17728
|
+
}
|
|
17729
|
+
const ext = extname(absPath).slice(1).toLowerCase();
|
|
17730
|
+
const mime = ext ? ATTACHMENT_MIME_BY_EXTENSION[ext] : undefined;
|
|
17731
|
+
if (!mime) {
|
|
17732
|
+
throw new AttachmentResolutionError(`@${typedPath}: unsupported attachment type${ext ? ` .${ext}` : ""}`);
|
|
17733
|
+
}
|
|
17734
|
+
if (stats.size === 0) {
|
|
17735
|
+
throw new AttachmentResolutionError(`@${typedPath}: file is empty`);
|
|
17736
|
+
}
|
|
17737
|
+
if (stats.size > MAX_ATTACHMENT_BYTES) {
|
|
17738
|
+
throw new AttachmentResolutionError(`@${typedPath}: ${formatBytes(stats.size)} exceeds the ` + `${formatBytes(MAX_ATTACHMENT_BYTES)} attachment limit`);
|
|
17739
|
+
}
|
|
17740
|
+
let file;
|
|
17741
|
+
try {
|
|
17742
|
+
const buffer = await readFile(absPath);
|
|
17743
|
+
const data = new Uint8Array(buffer.byteLength);
|
|
17744
|
+
data.set(buffer);
|
|
17745
|
+
file = new File([data], basename(absPath), { type: mime });
|
|
17746
|
+
} catch {
|
|
17747
|
+
throw new AttachmentResolutionError(`@${typedPath}: file is not readable (${absPath})`);
|
|
17748
|
+
}
|
|
17749
|
+
return file;
|
|
17750
|
+
}
|
|
17751
|
+
async function resolvePromptAttachments(options) {
|
|
17752
|
+
const refs = findAttachmentRefs(options.prompt);
|
|
17753
|
+
if (refs.length === 0) {
|
|
17754
|
+
return { text: options.prompt, attachments: [] };
|
|
17755
|
+
}
|
|
17756
|
+
const typedByResolved = {};
|
|
17757
|
+
for (const ref of refs) {
|
|
17758
|
+
const abs = resolve(ref.path);
|
|
17759
|
+
if (!(abs in typedByResolved))
|
|
17760
|
+
typedByResolved[abs] = ref.path;
|
|
17761
|
+
}
|
|
17762
|
+
const resolvedPaths = Object.keys(typedByResolved);
|
|
17763
|
+
if (resolvedPaths.length > MAX_ATTACHMENT_COUNT) {
|
|
17764
|
+
throw new AttachmentResolutionError(`Too many attachments: ${resolvedPaths.length} unique files referenced, ` + `the limit is ${MAX_ATTACHMENT_COUNT} (same as the web composer)`);
|
|
17765
|
+
}
|
|
17766
|
+
const prepared = await Promise.all(resolvedPaths.map(async (abs) => ({
|
|
17767
|
+
abs,
|
|
17768
|
+
typed: typedByResolved[abs] ?? abs,
|
|
17769
|
+
file: await readAttachment(typedByResolved[abs] ?? abs, abs)
|
|
17770
|
+
})));
|
|
17771
|
+
const client = new MediaStudioClient({
|
|
17772
|
+
baseUrl: options.apiBaseUrl,
|
|
17773
|
+
apiKey: options.apiKey
|
|
17774
|
+
});
|
|
17775
|
+
const urlByResolved = {};
|
|
17776
|
+
const attachments = [];
|
|
17777
|
+
const total = prepared.length;
|
|
17778
|
+
for (let i = 0;i < prepared.length; i++) {
|
|
17779
|
+
const item = prepared[i];
|
|
17780
|
+
if (!item)
|
|
17781
|
+
continue;
|
|
17782
|
+
options.onProgress?.(i + 1, total, item.file.name);
|
|
17783
|
+
const [uploaded] = await client.attachments.upload([
|
|
17784
|
+
{ id: nanoid3(), file: item.file }
|
|
17785
|
+
]);
|
|
17786
|
+
if (!uploaded) {
|
|
17787
|
+
throw new AttachmentResolutionError(`@${item.typed}: upload returned no file`);
|
|
17788
|
+
}
|
|
17789
|
+
urlByResolved[item.abs] = uploaded.remoteUrl;
|
|
17790
|
+
attachments.push({
|
|
17791
|
+
name: uploaded.name,
|
|
17792
|
+
type: uploaded.type,
|
|
17793
|
+
size: uploaded.size,
|
|
17794
|
+
remoteUrl: uploaded.remoteUrl
|
|
17795
|
+
});
|
|
17796
|
+
}
|
|
17797
|
+
const urlByTyped = {};
|
|
17798
|
+
for (const ref of refs) {
|
|
17799
|
+
const url = urlByResolved[resolve(ref.path)];
|
|
17800
|
+
if (url !== undefined)
|
|
17801
|
+
urlByTyped[ref.path] = url;
|
|
17802
|
+
}
|
|
17803
|
+
return {
|
|
17804
|
+
text: composeAttachmentPrompt(options.prompt, urlByTyped),
|
|
17805
|
+
attachments
|
|
17806
|
+
};
|
|
17807
|
+
}
|
|
17808
|
+
|
|
17578
17809
|
// src/services/session.ts
|
|
17579
17810
|
function isSessionGoneError(err) {
|
|
17580
17811
|
if (!(err instanceof ChatUpstreamError))
|
|
@@ -17757,7 +17988,7 @@ async function resolveChatSession(options) {
|
|
|
17757
17988
|
return { sessionId, recreateOnGone: false };
|
|
17758
17989
|
}
|
|
17759
17990
|
async function streamTurn(options) {
|
|
17760
|
-
const { args, sessionId, spinner, autoEnabled } = options;
|
|
17991
|
+
const { args, sessionId, spinner, autoEnabled, attachments } = options;
|
|
17761
17992
|
if (!args.json) {
|
|
17762
17993
|
spinner.start("Connecting to Media Studio...");
|
|
17763
17994
|
}
|
|
@@ -17838,6 +18069,7 @@ async function streamTurn(options) {
|
|
|
17838
18069
|
text: fullText,
|
|
17839
18070
|
steps,
|
|
17840
18071
|
media: mediaAssets,
|
|
18072
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
17841
18073
|
...autoEnabled.length > 0 ? { autoEnabled } : {},
|
|
17842
18074
|
...args["show-reasoning"] && reasoning ? { reasoning } : {}
|
|
17843
18075
|
};
|
|
@@ -17859,6 +18091,7 @@ async function streamTurn(options) {
|
|
|
17859
18091
|
text: fullText,
|
|
17860
18092
|
steps,
|
|
17861
18093
|
media: mediaAssets,
|
|
18094
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
17862
18095
|
...autoEnabled.length > 0 ? { autoEnabled } : {},
|
|
17863
18096
|
...args["show-reasoning"] && reasoning ? { reasoning } : {}
|
|
17864
18097
|
};
|
|
@@ -17900,7 +18133,7 @@ var chatCommand = defineCommand({
|
|
|
17900
18133
|
prompt: {
|
|
17901
18134
|
type: "positional",
|
|
17902
18135
|
required: true,
|
|
17903
|
-
description:
|
|
18136
|
+
description: 'The message to send. Reference a skill by leading the prompt with /name or %%/name%% (see `msc skills`); attach a local file with @path or @"path with spaces"'
|
|
17904
18137
|
},
|
|
17905
18138
|
json: {
|
|
17906
18139
|
type: "boolean",
|
|
@@ -17929,6 +18162,14 @@ var chatCommand = defineCommand({
|
|
|
17929
18162
|
if (autoEnabled.length > 0 && args.json !== true) {
|
|
17930
18163
|
consola.info(`Auto-enabled for this turn: ${autoEnabled.join(", ")}`);
|
|
17931
18164
|
}
|
|
18165
|
+
const { text: promptWithAttachments, attachments } = await resolvePromptAttachments({
|
|
18166
|
+
prompt: text,
|
|
18167
|
+
apiBaseUrl,
|
|
18168
|
+
apiKey: resolveApiKey(),
|
|
18169
|
+
...args.json ? {} : {
|
|
18170
|
+
onProgress: (index, total, name) => spinner.start(`Uploading attachment ${index}/${total}: ${name}`)
|
|
18171
|
+
}
|
|
18172
|
+
});
|
|
17932
18173
|
const resolved = await resolveChatSession({
|
|
17933
18174
|
sessionFlag: args.session,
|
|
17934
18175
|
apiBaseUrl,
|
|
@@ -17938,13 +18179,14 @@ var chatCommand = defineCommand({
|
|
|
17938
18179
|
});
|
|
17939
18180
|
await runTurn({
|
|
17940
18181
|
args,
|
|
17941
|
-
prompt:
|
|
18182
|
+
prompt: promptWithAttachments,
|
|
17942
18183
|
sessionId: resolved.sessionId,
|
|
17943
18184
|
recreateOnGone: resolved.recreateOnGone,
|
|
17944
18185
|
apiBaseUrl,
|
|
17945
18186
|
apiKey: resolveApiKey(),
|
|
17946
18187
|
spinner,
|
|
17947
18188
|
skillUrls,
|
|
18189
|
+
attachments,
|
|
17948
18190
|
autoEnabled
|
|
17949
18191
|
});
|
|
17950
18192
|
} catch (err) {
|
|
@@ -18016,11 +18258,25 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
|
|
|
18016
18258
|
Pass \`-r\` or \`--show-reasoning\` to stream the model's planning and
|
|
18017
18259
|
thinking process before the final generation steps.
|
|
18018
18260
|
|
|
18261
|
+
Attaching Files (@path):
|
|
18262
|
+
Attach local images, videos, audio, or documents by typing \`@path\` or
|
|
18263
|
+
\`@"path with spaces"\` anywhere in the prompt:
|
|
18264
|
+
msc chat "analyze @cover.png"
|
|
18265
|
+
msc chat "summarize @/abs/path/notes.pdf"
|
|
18266
|
+
msc chat 'compare @"my docs/report.docx" with @"my docs/summary.docx"'
|
|
18267
|
+
|
|
18268
|
+
The CLI validates files locally (existence, supported format, under 50 MB),
|
|
18269
|
+
uploads them with progress indication, and inlines remote URLs into the prompt.
|
|
18270
|
+
Up to 5 attachments per turn. Referencing the same file multiple times
|
|
18271
|
+
uploads it only once. Plain prose mentions (@team) and emails are ignored.
|
|
18272
|
+
|
|
18273
|
+
@path is local-filesystem-only: @https://example.com/a.png fails with
|
|
18274
|
+
"no such file". Remote URLs are not attachments and there is no @url form.
|
|
18275
|
+
|
|
18019
18276
|
Session Switching:
|
|
18020
18277
|
Pass \`--session <id>\` to direct a prompt to a specific existing session
|
|
18021
18278
|
and make it your new active session.
|
|
18022
18279
|
|
|
18023
|
-
|
|
18024
18280
|
4. USING SKILLS
|
|
18025
18281
|
|
|
18026
18282
|
Skills allow prompts to trigger specialized models, pipelines, and workflows
|
|
@@ -18062,9 +18318,12 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
|
|
|
18062
18318
|
msc sessions use <session-id>
|
|
18063
18319
|
Switch your active session pointer without sending a prompt.
|
|
18064
18320
|
|
|
18065
|
-
|
|
18066
|
-
|
|
18321
|
+
Session deletion is not available in the CLI. Open the corresponding session
|
|
18322
|
+
in the Media Studio web app and delete it there manually.
|
|
18067
18323
|
|
|
18324
|
+
Web app (open the region matching your CLI login):
|
|
18325
|
+
${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
|
|
18326
|
+
`)}
|
|
18068
18327
|
|
|
18069
18328
|
6. AUTOMATION & SCRIPTING (--json)
|
|
18070
18329
|
|
|
@@ -18087,6 +18346,14 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
|
|
|
18087
18346
|
"taskId": "string"
|
|
18088
18347
|
}
|
|
18089
18348
|
],
|
|
18349
|
+
"attachments": [ // present if local files were attached
|
|
18350
|
+
{
|
|
18351
|
+
"name": "string",
|
|
18352
|
+
"type": "string", // "image" | "video" | "audio" | "file"
|
|
18353
|
+
"size": 1024,
|
|
18354
|
+
"remoteUrl": "https://..."
|
|
18355
|
+
}
|
|
18356
|
+
],
|
|
18090
18357
|
"reasoning": "string?" // present if --show-reasoning was passed
|
|
18091
18358
|
}
|
|
18092
18359
|
|
|
@@ -19967,11 +20234,6 @@ var logoutCommand = defineCommand({
|
|
|
19967
20234
|
}
|
|
19968
20235
|
});
|
|
19969
20236
|
|
|
19970
|
-
// src/lib/confirm.ts
|
|
19971
|
-
function needsConfirmationRefusal(yes) {
|
|
19972
|
-
return !yes && !process.stdin.isTTY && true;
|
|
19973
|
-
}
|
|
19974
|
-
|
|
19975
20237
|
// src/commands/sessions.ts
|
|
19976
20238
|
function formatLastActive(iso) {
|
|
19977
20239
|
return iso.replace("T", " ").slice(0, 19);
|
|
@@ -20113,65 +20375,28 @@ var useSessionCommand = defineCommand({
|
|
|
20113
20375
|
consola.success(`Switched default session to ${args.sessionId}`);
|
|
20114
20376
|
}
|
|
20115
20377
|
});
|
|
20116
|
-
var rmSessionCommand = defineCommand({
|
|
20117
|
-
meta: {
|
|
20118
|
-
name: "rm",
|
|
20119
|
-
description: "Delete a session server-side"
|
|
20120
|
-
},
|
|
20121
|
-
args: {
|
|
20122
|
-
sessionId: {
|
|
20123
|
-
type: "positional",
|
|
20124
|
-
required: true,
|
|
20125
|
-
description: "Session id to delete"
|
|
20126
|
-
},
|
|
20127
|
-
yes: {
|
|
20128
|
-
type: "boolean",
|
|
20129
|
-
alias: "y",
|
|
20130
|
-
description: "Skip confirmation prompt"
|
|
20131
|
-
}
|
|
20132
|
-
},
|
|
20133
|
-
run: async ({ args }) => {
|
|
20134
|
-
try {
|
|
20135
|
-
if (needsConfirmationRefusal(args.yes === true)) {
|
|
20136
|
-
consola.error("Confirmation required. Pass --yes to confirm deletion non-interactively.");
|
|
20137
|
-
process.exit(1);
|
|
20138
|
-
return;
|
|
20139
|
-
}
|
|
20140
|
-
if (!args.yes) {
|
|
20141
|
-
const confirmed = await confirm({
|
|
20142
|
-
message: `Delete session "${args.sessionId}"? Upstream conversation memory will be destroyed.`,
|
|
20143
|
-
initialValue: false
|
|
20144
|
-
});
|
|
20145
|
-
if (isCancel(confirmed) || !confirmed) {
|
|
20146
|
-
consola.info("Session deletion cancelled.");
|
|
20147
|
-
return;
|
|
20148
|
-
}
|
|
20149
|
-
}
|
|
20150
|
-
const client = resolveClient();
|
|
20151
|
-
await client.sessions.remove(args.sessionId);
|
|
20152
|
-
clearLastSessionId(args.sessionId);
|
|
20153
|
-
consola.success(`Session "${args.sessionId}" deleted.`);
|
|
20154
|
-
} catch (err) {
|
|
20155
|
-
reportCommandError(err);
|
|
20156
|
-
}
|
|
20157
|
-
}
|
|
20158
|
-
});
|
|
20159
20378
|
var sessionsCommand = defineCommand({
|
|
20160
20379
|
meta: {
|
|
20161
20380
|
name: "sessions",
|
|
20162
|
-
description: "Manage Media Studio chat sessions"
|
|
20381
|
+
description: "Manage Media Studio chat sessions; deletion is available in the web app"
|
|
20163
20382
|
},
|
|
20164
20383
|
default: "list",
|
|
20165
20384
|
subCommands: {
|
|
20166
20385
|
list: listSessionsCommand,
|
|
20167
20386
|
artifacts: artifactsCommand,
|
|
20168
|
-
use: useSessionCommand
|
|
20169
|
-
rm: rmSessionCommand
|
|
20387
|
+
use: useSessionCommand
|
|
20170
20388
|
}
|
|
20171
20389
|
});
|
|
20172
20390
|
|
|
20173
20391
|
// src/commands/skills.ts
|
|
20174
20392
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
20393
|
+
|
|
20394
|
+
// src/lib/confirm.ts
|
|
20395
|
+
function needsConfirmationRefusal(yes) {
|
|
20396
|
+
return !yes && !process.stdin.isTTY && true;
|
|
20397
|
+
}
|
|
20398
|
+
|
|
20399
|
+
// src/commands/skills.ts
|
|
20175
20400
|
function resolveClient2() {
|
|
20176
20401
|
return new MediaStudioClient({
|
|
20177
20402
|
baseUrl: resolveApiBaseUrl(),
|
package/package.json
CHANGED