@302ai/media-studio-cli 0.1.2-beta.0 → 0.1.2-beta.1

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 +1 -1
  2. package/dist/index.js +130 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -215,7 +215,7 @@ The CLI mirrors the web composer's upload flow:
215
215
  1. Validates each referenced file locally (exists, readable, supported extension, under 50 MB) before any network call.
216
216
  2. Uploads each file via the web app's `POST /api/chat/attachments` endpoint with a terminal spinner (`Uploading attachment 1/2: cover.png`).
217
217
  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.
218
- 4. Up to 5 unique attachments per turn (same as the web composer); referencing the same file twice uploads it once.
218
+ 4. Up to 10 unique attachments per turn (same as the web composer); referencing the same file twice uploads it once.
219
219
  5. An unattachable reference (missing file, unsupported format) fails the turn before any session is provisioned.
220
220
  6. Prose mentions (`@team 辛苦了`, email addresses) stay literal text — only tokens with a file extension or path slash are treated as attachments.
221
221
  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.
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.2-beta.0",
4287
+ version: "0.1.2-beta.1",
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",
@@ -9648,7 +9648,7 @@ var ArtifactItemSchema = object({
9648
9648
  var ArtifactItemsSchema = array(ArtifactItemSchema);
9649
9649
 
9650
9650
  // ../media-studio-contract/src/attachments/constants.ts
9651
- var MAX_ATTACHMENT_COUNT = 5;
9651
+ var MAX_ATTACHMENT_COUNT = 10;
9652
9652
  var MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
9653
9653
  var ATTACHMENT_MIME_BY_EXTENSION = {
9654
9654
  png: "image/png",
@@ -10058,6 +10058,17 @@ var MEDIA_STUDIO_URLS = {
10058
10058
  cn: "https://media.302ai.cn",
10059
10059
  staging: "https://media.tool302.com"
10060
10060
  };
10061
+ var OSS_GLOBAL_ORIGIN = "https://sonier-file-oss.small-base-aaeb.workers.dev";
10062
+ var OSS_CN_ORIGIN = "https://sonier.oss-cn-shenzhen.aliyuncs.com";
10063
+ function resolveOssUrl(url, useCnDomain) {
10064
+ if (!useCnDomain || !url.startsWith(OSS_GLOBAL_ORIGIN))
10065
+ return url;
10066
+ const suffix = url.slice(OSS_GLOBAL_ORIGIN.length);
10067
+ const boundary = suffix[0];
10068
+ if (boundary && !"/?#".includes(boundary))
10069
+ return url;
10070
+ return OSS_CN_ORIGIN + suffix;
10071
+ }
10061
10072
  var MEDIA_STUDIO_HOSTS = [
10062
10073
  new URL(MEDIA_STUDIO_URLS.default).hostname,
10063
10074
  new URL(MEDIA_STUDIO_URLS.cn).hostname,
@@ -10073,6 +10084,34 @@ var APP_ENV_HOSTS = {
10073
10084
  ]
10074
10085
  };
10075
10086
 
10087
+ // ../media-studio-contract/src/model-flag.ts
10088
+ var MODEL_FLAG_BLOCK_RE = /\$(?=\s*--model\b)([^$]+)\$/gi;
10089
+ var MODEL_FLAG_HEAD_RE = /^--model\s+("(?:[^"\\]|\\.)+"|[^\s]+)/i;
10090
+ function findModelFlagMatches(text) {
10091
+ const pattern = new RegExp(MODEL_FLAG_BLOCK_RE.source, "gi");
10092
+ return Array.from(text.matchAll(pattern)).flatMap((match) => {
10093
+ const start = match.index;
10094
+ const body = match[1]?.trim();
10095
+ if (start === undefined || !body)
10096
+ return [];
10097
+ const token = body.match(MODEL_FLAG_HEAD_RE)?.[1];
10098
+ if (!token)
10099
+ return [];
10100
+ let modelId = token;
10101
+ if (token.startsWith('"')) {
10102
+ try {
10103
+ const decoded = JSON.parse(token);
10104
+ if (typeof decoded !== "string")
10105
+ return [];
10106
+ modelId = decoded;
10107
+ } catch {
10108
+ return [];
10109
+ }
10110
+ }
10111
+ return [{ start, end: start + match[0].length, body, modelId }];
10112
+ });
10113
+ }
10114
+
10076
10115
  // ../media-studio-contract/src/skills/activation.ts
10077
10116
  var SKILL_ACTIVATION_MARKER = "%%";
10078
10117
  var SKILL_ACTIVATION_RE = /%%(\/[^%\s]+)%%/g;
@@ -10102,21 +10141,20 @@ function findSkillActivationMatches(text) {
10102
10141
 
10103
10142
  // ../media-studio-contract/src/sessions/derive-first-turn-note.ts
10104
10143
  var DEFAULT_FALLBACK_TITLE = "Agent Task";
10105
- var MAX_NOTE_LENGTH = 64;
10144
+ var MAX_PROJECT_TITLE_LENGTH = 64;
10145
+ function clampSessionTitle(title) {
10146
+ return Array.from(title).slice(0, MAX_PROJECT_TITLE_LENGTH).join("");
10147
+ }
10106
10148
  function deriveFirstTurnNote(rawText, options) {
10107
10149
  const fallback = options?.fallbackTitle ?? DEFAULT_FALLBACK_TITLE;
10108
10150
  if (!rawText) {
10109
10151
  return fallback;
10110
10152
  }
10111
- const unwrapped = rawText.replace(/\$--model\s+"([^"$]+)"[^$]*\$/gi, "$1 ").replace(/\$--model\s+([^$\s]+)[^$]*\$/gi, "$1 ").replace(SKILL_ACTIVATION_RE, "$1").replace(ATTACHMENT_MARKER_REGEX, "$1").replace(/\s+/g, " ").trim();
10153
+ const unwrapped = findModelFlagMatches(rawText).toReversed().reduce((text, match) => `${text.slice(0, match.start)}${match.modelId} ${text.slice(match.end)}`, rawText).replace(SKILL_ACTIVATION_RE, "$1").replace(ATTACHMENT_MARKER_REGEX, "$1").replace(/\s+/g, " ").trim();
10112
10154
  if (!unwrapped) {
10113
10155
  return fallback;
10114
10156
  }
10115
- const codePoints = Array.from(unwrapped);
10116
- if (codePoints.length > MAX_NOTE_LENGTH) {
10117
- return codePoints.slice(0, MAX_NOTE_LENGTH).join("");
10118
- }
10119
- return unwrapped;
10157
+ return clampSessionTitle(unwrapped);
10120
10158
  }
10121
10159
 
10122
10160
  // ../media-studio-contract/src/skills/compose-turn.ts
@@ -17909,6 +17947,26 @@ function reportCommandError(err, options) {
17909
17947
  process.exit(1);
17910
17948
  }
17911
17949
 
17950
+ // src/lib/open-url.ts
17951
+ var OPEN_URL_NOTE = "Open or render open_url. Submit or forward url unchanged.";
17952
+ function loginUsesCnOss(apiBaseUrl) {
17953
+ try {
17954
+ return new URL(apiBaseUrl).origin === MEDIA_STUDIO_URLS.cn;
17955
+ } catch {
17956
+ return false;
17957
+ }
17958
+ }
17959
+ function resolveOpenUrl(url, apiBaseUrl) {
17960
+ return resolveOssUrl(url, loginUsesCnOss(apiBaseUrl));
17961
+ }
17962
+ function deriveOpenUrl(rawUrl, apiBaseUrl) {
17963
+ const sanitized = rawUrl?.replace(/\r?\n/g, " ").trim();
17964
+ if (!sanitized)
17965
+ return;
17966
+ const openUrl = resolveOpenUrl(sanitized, apiBaseUrl);
17967
+ return openUrl === sanitized ? undefined : openUrl;
17968
+ }
17969
+
17912
17970
  // src/lib/spinner.ts
17913
17971
  var import_picocolors = __toESM(require_picocolors(), 1);
17914
17972
  var BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -18272,7 +18330,11 @@ var PROVISION_STAGE_LABELS = {
18272
18330
  skills: "Installing skills...",
18273
18331
  finishing: "Finishing session setup..."
18274
18332
  };
18275
- function printTerminalMediaAssets(mediaAssets, fullText) {
18333
+ function withMediaOpenUrl(asset, apiBaseUrl) {
18334
+ const openUrl = deriveOpenUrl(asset.resultUrl, apiBaseUrl);
18335
+ return openUrl ? { ...asset, openUrl } : asset;
18336
+ }
18337
+ function printTerminalMediaAssets(mediaAssets, fullText, apiBaseUrl) {
18276
18338
  if (mediaAssets.length === 0) {
18277
18339
  if (fullText && !fullText.endsWith(`
18278
18340
  `)) {
@@ -18289,13 +18351,20 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
18289
18351
  process.stdout.write(`
18290
18352
  === MEDIA ASSETS ===
18291
18353
  `);
18354
+ let noted = false;
18292
18355
  for (const asset of mediaAssets) {
18356
+ const openUrl = deriveOpenUrl(asset.resultUrl, apiBaseUrl);
18357
+ noted ||= Boolean(openUrl);
18293
18358
  process.stdout.write(`- type: ${asset.domain ?? "media"}
18294
18359
  ` + (asset.resultUrl ? `- url: ${asset.resultUrl}
18360
+ ` : "") + (openUrl ? `- open_url: ${openUrl}
18295
18361
  ` : "") + (asset.modelUsed ? `- model: ${asset.modelUsed}
18296
18362
  ` : "") + (asset.taskId ? `- task_id: ${asset.taskId}
18297
18363
  ` : ""));
18298
18364
  }
18365
+ if (noted)
18366
+ process.stdout.write(`${OPEN_URL_NOTE}
18367
+ `);
18299
18368
  process.stdout.write(`====================
18300
18369
  `);
18301
18370
  }
@@ -18354,7 +18423,7 @@ function describeStreamFailure(err) {
18354
18423
  return String(err);
18355
18424
  }
18356
18425
  async function streamTurn(options) {
18357
- const { args, sessionId, spinner, autoEnabled, attachments } = options;
18426
+ const { args, sessionId, spinner, autoEnabled, attachments, apiBaseUrl } = options;
18358
18427
  if (!args.json) {
18359
18428
  spinner.start("Connecting to Media Studio...");
18360
18429
  }
@@ -18441,7 +18510,7 @@ async function streamTurn(options) {
18441
18510
  sessionId,
18442
18511
  text: fullText,
18443
18512
  steps,
18444
- media: mediaAssets,
18513
+ media: mediaAssets.map((asset) => withMediaOpenUrl(asset, apiBaseUrl)),
18445
18514
  ...attachments.length > 0 ? { attachments } : {},
18446
18515
  ...autoEnabled.length > 0 ? { autoEnabled } : {},
18447
18516
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
@@ -18451,7 +18520,7 @@ async function streamTurn(options) {
18451
18520
  emitting = false;
18452
18521
  return;
18453
18522
  }
18454
- printTerminalMediaAssets(mediaAssets, fullText);
18523
+ printTerminalMediaAssets(mediaAssets, fullText, apiBaseUrl);
18455
18524
  process.stdout.write(`session: ${sessionId}
18456
18525
  `);
18457
18526
  emitting = false;
@@ -18475,7 +18544,7 @@ async function streamTurn(options) {
18475
18544
  sessionId,
18476
18545
  text: fullText,
18477
18546
  steps,
18478
- media: mediaAssets,
18547
+ media: mediaAssets.map((asset) => withMediaOpenUrl(asset, apiBaseUrl)),
18479
18548
  ...attachments.length > 0 ? { attachments } : {},
18480
18549
  ...autoEnabled.length > 0 ? { autoEnabled } : {},
18481
18550
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
@@ -18483,7 +18552,7 @@ async function streamTurn(options) {
18483
18552
  process.stdout.write(`${JSON.stringify(output, null, 2)}
18484
18553
  `);
18485
18554
  } else {
18486
- printTerminalMediaAssets(mediaAssets, fullText);
18555
+ printTerminalMediaAssets(mediaAssets, fullText, apiBaseUrl);
18487
18556
  process.stdout.write(`session: ${sessionId}
18488
18557
  `);
18489
18558
  }
@@ -18720,7 +18789,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18720
18789
 
18721
18790
  The CLI validates files locally (existence, supported format, under 50 MB),
18722
18791
  uploads them with progress indication, and inlines remote URLs into the prompt.
18723
- Up to 5 attachments per turn. Referencing the same file multiple times
18792
+ Up to 10 attachments per turn. Referencing the same file multiple times
18724
18793
  uploads it only once. Plain prose mentions (@team) and emails are ignored.
18725
18794
 
18726
18795
  @path is local-filesystem-only: @https://example.com/a.png fails with
@@ -18774,7 +18843,9 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18774
18843
 
18775
18844
  msc sessions artifacts <session-id>
18776
18845
  List all generated assets (images, videos, audio) produced in that session,
18777
- including download URLs and model metadata.
18846
+ including download URLs and model metadata. Text output adds an OPEN column
18847
+ only when the signed-in site needs a different origin; JSON adds the openUrl
18848
+ field in the same case. Submit or forward the link unchanged.
18778
18849
 
18779
18850
  msc sessions use <session-id>
18780
18851
  Switch your active session pointer to an existing session. The id is
@@ -18808,7 +18879,8 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18808
18879
  "status": "completed",
18809
18880
  "domain": "image", // "image" | "video" | etc.
18810
18881
  "operation": "t2i", // "t2i", "t2v", etc.
18811
- "resultUrl": "https://...",
18882
+ "resultUrl": "https://...", // stored address; submit this unchanged
18883
+ "openUrl": "https://...", // present only when rendering needs another origin
18812
18884
  "modelUsed": "string",
18813
18885
  "taskId": "string"
18814
18886
  }
@@ -18824,8 +18896,17 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18824
18896
  "reasoning": "string?" // present if --show-reasoning was passed
18825
18897
  }
18826
18898
 
18827
- Extracting the result URL (e.g. with jq):
18828
- url=$(msc chat "a red balloon" --json | jq -r '.media[0].resultUrl')
18899
+ resultUrl is the stored address. Submit or forward it unchanged. openUrl is
18900
+ present only when the signed-in site needs a different origin to render the
18901
+ object. Text output names that address open_url and adds one fixed note;
18902
+ JSON does not contain the note.
18903
+
18904
+ Extracting each role (e.g. with jq):
18905
+ result=$(msc chat "a red balloon" --json)
18906
+ url=$(printf '%s
18907
+ ' "$result" | jq -r '.media[0].resultUrl')
18908
+ open=$(printf '%s
18909
+ ' "$result" | jq -r '.media[0].openUrl // .media[0].resultUrl')
18829
18910
 
18830
18911
  Exit codes: 0 = the turn ran to completion OR its stream ended early; 1 =
18831
18912
  \`status: "error"\`, a usage error, or any other failure. Only what the parser
@@ -18907,6 +18988,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18907
18988
  msc sessions --json # list: [{ sessionId, note, ... }]
18908
18989
  msc sessions use <id> --json # { "sessionId": "string" }
18909
18990
  msc sessions use --new --json # { "sessionId": "string" } (newly created)
18991
+ msc sessions artifacts <id> --json # [{..., link, openUrl?}]
18910
18992
  \`--session\`/\`use <id>\` verify the id upstream; an unknown id exits 1 with
18911
18993
  the \`--new\` hint and leaves the default session untouched.
18912
18994
 
@@ -20813,22 +20895,31 @@ ${bodyLines.join(`
20813
20895
  `)}
20814
20896
  `;
20815
20897
  }
20816
- function renderArtifactsTable(artifacts) {
20817
- const rows = artifacts.map((a) => ({
20818
- type: a.type,
20819
- status: a.status,
20820
- title: a.title ? a.title.replace(/\r?\n/g, " ").trim() || "-" : "-",
20821
- link: a.link ? a.link.replace(/\r?\n/g, " ").trim() || "-" : "-"
20822
- }));
20898
+ function artifactRows(artifacts, apiBaseUrl) {
20899
+ return artifacts.map((a) => {
20900
+ const link = a.link ? a.link.replace(/\r?\n/g, " ").trim() || "-" : "-";
20901
+ const openUrl = link === "-" ? undefined : deriveOpenUrl(link, apiBaseUrl);
20902
+ return {
20903
+ type: a.type,
20904
+ status: a.status,
20905
+ title: a.title ? a.title.replace(/\r?\n/g, " ").trim() || "-" : "-",
20906
+ link,
20907
+ openUrl: openUrl ?? ""
20908
+ };
20909
+ });
20910
+ }
20911
+ function renderArtifactsTable(rows) {
20912
+ const showOpen = rows.some((row) => row.openUrl);
20823
20913
  const headers = {
20824
20914
  type: "TYPE",
20825
20915
  status: "STATUS",
20826
20916
  title: "TITLE",
20827
- link: "LINK"
20917
+ link: "LINK",
20918
+ openUrl: "OPEN"
20828
20919
  };
20829
20920
  const typeWidth = Math.max(headers.type.length, ...rows.map((r) => r.type.length));
20830
20921
  const statusWidth = Math.max(headers.status.length, ...rows.map((r) => r.status.length));
20831
- const formatRow = (r) => `${r.type.padEnd(typeWidth)} ${r.status.padEnd(statusWidth)} ${r.title} ${r.link}`;
20922
+ const formatRow = (r) => `${r.type.padEnd(typeWidth)} ${r.status.padEnd(statusWidth)} ${r.title} ${r.link}${showOpen ? ` ${r.openUrl}` : ""}`;
20832
20923
  return `${formatRow(headers)}
20833
20924
  ${rows.map(formatRow).join(`
20834
20925
  `)}
@@ -20885,7 +20976,12 @@ var artifactsCommand = defineCommand({
20885
20976
  sessionId: args.sessionId
20886
20977
  });
20887
20978
  if (args.json) {
20888
- process.stdout.write(`${JSON.stringify(response.data, null, 2)}
20979
+ const apiBaseUrl = resolveApiBaseUrl();
20980
+ const data = response.data.map((item) => {
20981
+ const openUrl = deriveOpenUrl(item.link, apiBaseUrl);
20982
+ return openUrl ? { ...item, openUrl } : item;
20983
+ });
20984
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
20889
20985
  `);
20890
20986
  return;
20891
20987
  }
@@ -20893,7 +20989,10 @@ var artifactsCommand = defineCommand({
20893
20989
  consola.info("No artifacts found.");
20894
20990
  return;
20895
20991
  }
20896
- consola.log(renderArtifactsTable(response.data));
20992
+ const rows = artifactRows(response.data, resolveApiBaseUrl());
20993
+ consola.log(renderArtifactsTable(rows));
20994
+ if (rows.some((row) => row.openUrl))
20995
+ consola.log(OPEN_URL_NOTE);
20897
20996
  }, { json: args.json })
20898
20997
  });
20899
20998
  var useSessionCommand = defineCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.2-beta.0",
3
+ "version": "0.1.2-beta.1",
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",