@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.
Files changed (3) hide show
  1. package/README.md +26 -2
  2. package/dist/index.js +307 -82
  3. 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.6",
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: "The message to send. Reference a skill by leading the prompt with /name or %%/name%% - see `msc skills`"
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: text,
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
- msc sessions rm <session-id> [-y]
18066
- Delete a session and its server-side history.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.0-beta.7",
4
4
  "description": "CLI for AI Media Studio — let external agents drive the core media-studio features",
5
5
  "author": "302.AI",
6
6
  "license": "Apache-2.0",