@302ai/media-studio-cli 0.1.1 → 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 +327 -270
  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.1",
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
@@ -16975,7 +17013,7 @@ function saveConfigFile(data) {
16975
17013
  const dir = getConfigDir();
16976
17014
  mkdirSync(dir, { recursive: true });
16977
17015
  const configPath = getConfigFilePath();
16978
- const tmpPath = `${configPath}.${nanoid3()}.tmp`;
17016
+ const tmpPath = `${configPath}.tmp.${process.pid}.${nanoid3()}`;
16979
17017
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8");
16980
17018
  chmodSync(tmpPath, 384);
16981
17019
  renameSync(tmpPath, configPath);
@@ -17541,6 +17579,12 @@ class MediaStudioClient {
17541
17579
  }, MeResponseSchema, (status) => `Authentication verification failed with status ${status}`);
17542
17580
  }
17543
17581
  }
17582
+ function createCliClient(options) {
17583
+ return new MediaStudioClient({
17584
+ baseUrl: options?.baseUrl ?? resolveApiBaseUrl(),
17585
+ apiKey: options?.apiKey ?? resolveApiKey()
17586
+ });
17587
+ }
17544
17588
  // src/client/tool-formatter.ts
17545
17589
  var ASPECT_RATIO_PATTERN = /aspect_ratio:\s*([^\s,;]+)/i;
17546
17590
  var MODEL_PATTERN = /(?:^|\n)\s*-\s*model:\s*([^\s,;]+)/i;
@@ -17903,6 +17947,26 @@ function reportCommandError(err, options) {
17903
17947
  process.exit(1);
17904
17948
  }
17905
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
+
17906
17970
  // src/lib/spinner.ts
17907
17971
  var import_picocolors = __toESM(require_picocolors(), 1);
17908
17972
  var BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -18065,7 +18129,7 @@ async function resolvePromptAttachments(options) {
18065
18129
  typed: typedByResolved[abs] ?? abs,
18066
18130
  file: await readAttachment(typedByResolved[abs] ?? abs, abs)
18067
18131
  })));
18068
- const client = new MediaStudioClient({
18132
+ const client = createCliClient({
18069
18133
  baseUrl: options.apiBaseUrl,
18070
18134
  apiKey: options.apiKey
18071
18135
  });
@@ -18129,7 +18193,7 @@ class SessionNotFoundError extends Error {
18129
18193
  }
18130
18194
  }
18131
18195
  async function ensureSessionExists(options) {
18132
- const client = new MediaStudioClient({
18196
+ const client = createCliClient({
18133
18197
  baseUrl: options.apiBaseUrl,
18134
18198
  apiKey: options.apiKey
18135
18199
  });
@@ -18149,7 +18213,7 @@ function finalizeProvision(result, spinner) {
18149
18213
  return result.sessionId;
18150
18214
  }
18151
18215
  async function provisionSession(options) {
18152
- const client = new MediaStudioClient({
18216
+ const client = createCliClient({
18153
18217
  baseUrl: options.apiBaseUrl,
18154
18218
  apiKey: options.apiKey
18155
18219
  });
@@ -18227,7 +18291,7 @@ async function resolvePromptSkills(options) {
18227
18291
  if (spans.length === 0) {
18228
18292
  return { text: options.prompt, skillUrls: [], autoEnabled: [] };
18229
18293
  }
18230
- const client = new MediaStudioClient({
18294
+ const client = createCliClient({
18231
18295
  baseUrl: options.apiBaseUrl,
18232
18296
  apiKey: options.apiKey
18233
18297
  });
@@ -18266,7 +18330,11 @@ var PROVISION_STAGE_LABELS = {
18266
18330
  skills: "Installing skills...",
18267
18331
  finishing: "Finishing session setup..."
18268
18332
  };
18269
- 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) {
18270
18338
  if (mediaAssets.length === 0) {
18271
18339
  if (fullText && !fullText.endsWith(`
18272
18340
  `)) {
@@ -18283,13 +18351,20 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
18283
18351
  process.stdout.write(`
18284
18352
  === MEDIA ASSETS ===
18285
18353
  `);
18354
+ let noted = false;
18286
18355
  for (const asset of mediaAssets) {
18356
+ const openUrl = deriveOpenUrl(asset.resultUrl, apiBaseUrl);
18357
+ noted ||= Boolean(openUrl);
18287
18358
  process.stdout.write(`- type: ${asset.domain ?? "media"}
18288
18359
  ` + (asset.resultUrl ? `- url: ${asset.resultUrl}
18360
+ ` : "") + (openUrl ? `- open_url: ${openUrl}
18289
18361
  ` : "") + (asset.modelUsed ? `- model: ${asset.modelUsed}
18290
18362
  ` : "") + (asset.taskId ? `- task_id: ${asset.taskId}
18291
18363
  ` : ""));
18292
18364
  }
18365
+ if (noted)
18366
+ process.stdout.write(`${OPEN_URL_NOTE}
18367
+ `);
18293
18368
  process.stdout.write(`====================
18294
18369
  `);
18295
18370
  }
@@ -18348,11 +18423,11 @@ function describeStreamFailure(err) {
18348
18423
  return String(err);
18349
18424
  }
18350
18425
  async function streamTurn(options) {
18351
- const { args, sessionId, spinner, autoEnabled, attachments } = options;
18426
+ const { args, sessionId, spinner, autoEnabled, attachments, apiBaseUrl } = options;
18352
18427
  if (!args.json) {
18353
18428
  spinner.start("Connecting to Media Studio...");
18354
18429
  }
18355
- const stream = await new MediaStudioClient({
18430
+ const stream = await createCliClient({
18356
18431
  baseUrl: options.apiBaseUrl,
18357
18432
  apiKey: options.apiKey
18358
18433
  }).chat.stream({
@@ -18435,7 +18510,7 @@ async function streamTurn(options) {
18435
18510
  sessionId,
18436
18511
  text: fullText,
18437
18512
  steps,
18438
- media: mediaAssets,
18513
+ media: mediaAssets.map((asset) => withMediaOpenUrl(asset, apiBaseUrl)),
18439
18514
  ...attachments.length > 0 ? { attachments } : {},
18440
18515
  ...autoEnabled.length > 0 ? { autoEnabled } : {},
18441
18516
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
@@ -18445,7 +18520,7 @@ async function streamTurn(options) {
18445
18520
  emitting = false;
18446
18521
  return;
18447
18522
  }
18448
- printTerminalMediaAssets(mediaAssets, fullText);
18523
+ printTerminalMediaAssets(mediaAssets, fullText, apiBaseUrl);
18449
18524
  process.stdout.write(`session: ${sessionId}
18450
18525
  `);
18451
18526
  emitting = false;
@@ -18469,7 +18544,7 @@ async function streamTurn(options) {
18469
18544
  sessionId,
18470
18545
  text: fullText,
18471
18546
  steps,
18472
- media: mediaAssets,
18547
+ media: mediaAssets.map((asset) => withMediaOpenUrl(asset, apiBaseUrl)),
18473
18548
  ...attachments.length > 0 ? { attachments } : {},
18474
18549
  ...autoEnabled.length > 0 ? { autoEnabled } : {},
18475
18550
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
@@ -18477,7 +18552,7 @@ async function streamTurn(options) {
18477
18552
  process.stdout.write(`${JSON.stringify(output, null, 2)}
18478
18553
  `);
18479
18554
  } else {
18480
- printTerminalMediaAssets(mediaAssets, fullText);
18555
+ printTerminalMediaAssets(mediaAssets, fullText, apiBaseUrl);
18481
18556
  process.stdout.write(`session: ${sessionId}
18482
18557
  `);
18483
18558
  }
@@ -18608,7 +18683,7 @@ var chatCommand = defineCommand({
18608
18683
  const shouldPatchNote = Boolean(explicitSessionDetail && !explicitSessionDetail.titleCustomized && explicitSessionDetail.isEmpty || !args.session && !args.new && readLastSessionPendingNote());
18609
18684
  if (shouldPatchNote) {
18610
18685
  const derivedNote = deriveFirstTurnNote(buildNotePrompt(promptWithAttachments, attachments), { fallbackTitle: CLI_DEFAULT_SESSION_NOTE });
18611
- new MediaStudioClient({
18686
+ createCliClient({
18612
18687
  baseUrl: apiBaseUrl,
18613
18688
  apiKey: resolveApiKey()
18614
18689
  }).sessions.updateNote(resolved.sessionId, derivedNote).then(() => clearLastSessionPendingNote()).catch(() => {});
@@ -18714,7 +18789,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18714
18789
 
18715
18790
  The CLI validates files locally (existence, supported format, under 50 MB),
18716
18791
  uploads them with progress indication, and inlines remote URLs into the prompt.
18717
- 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
18718
18793
  uploads it only once. Plain prose mentions (@team) and emails are ignored.
18719
18794
 
18720
18795
  @path is local-filesystem-only: @https://example.com/a.png fails with
@@ -18768,7 +18843,9 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18768
18843
 
18769
18844
  msc sessions artifacts <session-id>
18770
18845
  List all generated assets (images, videos, audio) produced in that session,
18771
- 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.
18772
18849
 
18773
18850
  msc sessions use <session-id>
18774
18851
  Switch your active session pointer to an existing session. The id is
@@ -18802,7 +18879,8 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18802
18879
  "status": "completed",
18803
18880
  "domain": "image", // "image" | "video" | etc.
18804
18881
  "operation": "t2i", // "t2i", "t2v", etc.
18805
- "resultUrl": "https://...",
18882
+ "resultUrl": "https://...", // stored address; submit this unchanged
18883
+ "openUrl": "https://...", // present only when rendering needs another origin
18806
18884
  "modelUsed": "string",
18807
18885
  "taskId": "string"
18808
18886
  }
@@ -18818,8 +18896,17 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18818
18896
  "reasoning": "string?" // present if --show-reasoning was passed
18819
18897
  }
18820
18898
 
18821
- Extracting the result URL (e.g. with jq):
18822
- 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')
18823
18910
 
18824
18911
  Exit codes: 0 = the turn ran to completion OR its stream ended early; 1 =
18825
18912
  \`status: "error"\`, a usage error, or any other failure. Only what the parser
@@ -18901,6 +18988,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18901
18988
  msc sessions --json # list: [{ sessionId, note, ... }]
18902
18989
  msc sessions use <id> --json # { "sessionId": "string" }
18903
18990
  msc sessions use --new --json # { "sessionId": "string" } (newly created)
18991
+ msc sessions artifacts <id> --json # [{..., link, openUrl?}]
18904
18992
  \`--session\`/\`use <id>\` verify the id upstream; an unknown id exits 1 with
18905
18993
  the \`--new\` hint and leaves the default session untouched.
18906
18994
 
@@ -20764,9 +20852,24 @@ var logoutCommand = defineCommand({
20764
20852
  }
20765
20853
  });
20766
20854
 
20855
+ // src/lib/run-command.ts
20856
+ async function runCliCommand(fn, options) {
20857
+ try {
20858
+ await fn();
20859
+ } catch (err) {
20860
+ if (isTransportFailure(err))
20861
+ throw err;
20862
+ reportCommandError(err, { json: options?.json });
20863
+ return;
20864
+ }
20865
+ }
20866
+
20767
20867
  // src/commands/sessions.ts
20768
20868
  function formatLastActive(iso) {
20769
- return iso.replace("T", " ").slice(0, 19);
20869
+ const date = new Date(iso);
20870
+ if (!iso || Number.isNaN(date.getTime()))
20871
+ return "";
20872
+ return date.toLocaleString("sv-SE", { hour12: false }).replace(", ", " ");
20770
20873
  }
20771
20874
  function renderSessionsTable(sessions, currentSessionId) {
20772
20875
  const rows = sessions.map((s) => ({
@@ -20792,27 +20895,31 @@ ${bodyLines.join(`
20792
20895
  `)}
20793
20896
  `;
20794
20897
  }
20795
- function resolveClient() {
20796
- const apiKey = resolveApiKey();
20797
- const apiBaseUrl = resolveApiBaseUrl();
20798
- return new MediaStudioClient({ baseUrl: apiBaseUrl, apiKey });
20799
- }
20800
- function renderArtifactsTable(artifacts) {
20801
- const rows = artifacts.map((a) => ({
20802
- type: a.type,
20803
- status: a.status,
20804
- title: a.title ? a.title.replace(/\r?\n/g, " ").trim() || "-" : "-",
20805
- link: a.link ? a.link.replace(/\r?\n/g, " ").trim() || "-" : "-"
20806
- }));
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);
20807
20913
  const headers = {
20808
20914
  type: "TYPE",
20809
20915
  status: "STATUS",
20810
20916
  title: "TITLE",
20811
- link: "LINK"
20917
+ link: "LINK",
20918
+ openUrl: "OPEN"
20812
20919
  };
20813
20920
  const typeWidth = Math.max(headers.type.length, ...rows.map((r) => r.type.length));
20814
20921
  const statusWidth = Math.max(headers.status.length, ...rows.map((r) => r.status.length));
20815
- 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}` : ""}`;
20816
20923
  return `${formatRow(headers)}
20817
20924
  ${rows.map(formatRow).join(`
20818
20925
  `)}
@@ -20830,27 +20937,21 @@ var listSessionsCommand = defineCommand({
20830
20937
  description: "Output machine-readable JSON array"
20831
20938
  }
20832
20939
  },
20833
- run: async ({ args }) => {
20834
- try {
20835
- const client = resolveClient();
20836
- const response = await client.sessions.list();
20837
- if (args.json) {
20838
- process.stdout.write(`${JSON.stringify(response.data, null, 2)}
20940
+ run: ({ args }) => runCliCommand(async () => {
20941
+ const client = createCliClient();
20942
+ const response = await client.sessions.list();
20943
+ if (args.json) {
20944
+ process.stdout.write(`${JSON.stringify(response.data, null, 2)}
20839
20945
  `);
20840
- return;
20841
- }
20842
- if (response.data.length === 0) {
20843
- consola.info("No sessions found.");
20844
- return;
20845
- }
20846
- const currentId = readLastSessionId();
20847
- consola.log(renderSessionsTable(response.data, currentId));
20848
- } catch (err) {
20849
- if (isTransportFailure(err))
20850
- throw err;
20851
- reportCommandError(err, { json: args.json });
20946
+ return;
20852
20947
  }
20853
- }
20948
+ if (response.data.length === 0) {
20949
+ consola.info("No sessions found.");
20950
+ return;
20951
+ }
20952
+ const currentId = readLastSessionId();
20953
+ consola.log(renderSessionsTable(response.data, currentId));
20954
+ }, { json: args.json })
20854
20955
  });
20855
20956
  var artifactsCommand = defineCommand({
20856
20957
  meta: {
@@ -20869,28 +20970,30 @@ var artifactsCommand = defineCommand({
20869
20970
  description: "Output machine-readable JSON array"
20870
20971
  }
20871
20972
  },
20872
- run: async ({ args }) => {
20873
- try {
20874
- const client = resolveClient();
20875
- const response = await client.sessions.artifacts({
20876
- sessionId: args.sessionId
20973
+ run: ({ args }) => runCliCommand(async () => {
20974
+ const client = createCliClient();
20975
+ const response = await client.sessions.artifacts({
20976
+ sessionId: args.sessionId
20977
+ });
20978
+ if (args.json) {
20979
+ const apiBaseUrl = resolveApiBaseUrl();
20980
+ const data = response.data.map((item) => {
20981
+ const openUrl = deriveOpenUrl(item.link, apiBaseUrl);
20982
+ return openUrl ? { ...item, openUrl } : item;
20877
20983
  });
20878
- if (args.json) {
20879
- process.stdout.write(`${JSON.stringify(response.data, null, 2)}
20984
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
20880
20985
  `);
20881
- return;
20882
- }
20883
- if (response.data.length === 0) {
20884
- consola.info("No artifacts found.");
20885
- return;
20886
- }
20887
- consola.log(renderArtifactsTable(response.data));
20888
- } catch (err) {
20889
- if (isTransportFailure(err))
20890
- throw err;
20891
- reportCommandError(err, { json: args.json });
20986
+ return;
20892
20987
  }
20893
- }
20988
+ if (response.data.length === 0) {
20989
+ consola.info("No artifacts found.");
20990
+ return;
20991
+ }
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);
20996
+ }, { json: args.json })
20894
20997
  });
20895
20998
  var useSessionCommand = defineCommand({
20896
20999
  meta: {
@@ -20913,50 +21016,44 @@ var useSessionCommand = defineCommand({
20913
21016
  description: "Output machine-readable JSON: { sessionId }"
20914
21017
  }
20915
21018
  },
20916
- run: async ({ args }) => {
20917
- try {
20918
- const sessionId = args.sessionId;
20919
- const apiBaseUrl = resolveApiBaseUrl();
20920
- if (args.new) {
20921
- if (sessionId) {
20922
- throw new Error("--new and <session-id> cannot be combined: pass one, not both.");
20923
- }
20924
- const result = await provisionSession({
20925
- apiBaseUrl,
20926
- apiKey: resolveApiKey(),
20927
- note: CLI_DEFAULT_SESSION_NOTE
20928
- });
20929
- const created = finalizeProvision(result);
20930
- saveLastSessionId(created, { pendingNote: true });
20931
- if (args.json) {
20932
- process.stdout.write(`${JSON.stringify({ sessionId: created })}
20933
- `);
20934
- return;
20935
- }
20936
- consola.success(`Created and switched to session ${created}`);
20937
- return;
21019
+ run: ({ args }) => runCliCommand(async () => {
21020
+ const sessionId = args.sessionId;
21021
+ const apiBaseUrl = resolveApiBaseUrl();
21022
+ if (args.new) {
21023
+ if (sessionId) {
21024
+ throw new Error("--new and <session-id> cannot be combined: pass one, not both.");
20938
21025
  }
20939
- if (!sessionId) {
20940
- throw new Error("Pass the session id to switch to, or --new to create a new session.");
20941
- }
20942
- await ensureSessionExists({
21026
+ const result = await provisionSession({
20943
21027
  apiBaseUrl,
20944
21028
  apiKey: resolveApiKey(),
20945
- sessionId
21029
+ note: CLI_DEFAULT_SESSION_NOTE
20946
21030
  });
20947
- saveLastSessionId(sessionId);
21031
+ const created = finalizeProvision(result);
21032
+ saveLastSessionId(created, { pendingNote: true });
20948
21033
  if (args.json) {
20949
- process.stdout.write(`${JSON.stringify({ sessionId })}
21034
+ process.stdout.write(`${JSON.stringify({ sessionId: created })}
20950
21035
  `);
20951
21036
  return;
20952
21037
  }
20953
- consola.success(`Switched default session to ${sessionId}`);
20954
- } catch (err) {
20955
- if (isTransportFailure(err))
20956
- throw err;
20957
- reportCommandError(err, { json: args.json });
21038
+ consola.success(`Created and switched to session ${created}`);
21039
+ return;
20958
21040
  }
20959
- }
21041
+ if (!sessionId) {
21042
+ throw new Error("Pass the session id to switch to, or --new to create a new session.");
21043
+ }
21044
+ await ensureSessionExists({
21045
+ apiBaseUrl,
21046
+ apiKey: resolveApiKey(),
21047
+ sessionId
21048
+ });
21049
+ saveLastSessionId(sessionId);
21050
+ if (args.json) {
21051
+ process.stdout.write(`${JSON.stringify({ sessionId })}
21052
+ `);
21053
+ return;
21054
+ }
21055
+ consola.success(`Switched default session to ${sessionId}`);
21056
+ }, { json: args.json })
20960
21057
  });
20961
21058
  var sessionsCommand = defineCommand({
20962
21059
  meta: {
@@ -20980,26 +21077,19 @@ function needsConfirmationRefusal(yes) {
20980
21077
  }
20981
21078
 
20982
21079
  // src/commands/skills.ts
20983
- function resolveClient2() {
20984
- return new MediaStudioClient({
20985
- baseUrl: resolveApiBaseUrl(),
20986
- apiKey: resolveApiKey()
20987
- });
20988
- }
20989
21080
  function stateMarker(state) {
20990
- if (state === "on")
20991
- return "[on]";
20992
- if (state === "off")
20993
- return "[off]";
20994
- return "[-] ";
21081
+ const marker = state === "on" ? "[on]" : state === "off" ? "[off]" : "[-]";
21082
+ return marker.padEnd(5);
20995
21083
  }
21084
+ var NAME_SEP = " ";
21085
+ var NAME_INDENT = stateMarker("off").length + NAME_SEP.length;
20996
21086
  function renderSkillRow(state, name, description) {
20997
21087
  const marker = stateMarker(state);
20998
21088
  const colorize = state === "on" ? import_picocolors2.green : state === "off" ? import_picocolors2.red : import_picocolors2.dim;
20999
- const head = `${colorize(marker)} ${stripControlChars(name)}`;
21089
+ const head = `${colorize(marker)}${NAME_SEP}${stripControlChars(name)}`;
21000
21090
  const clean = stripControlChars(description).trim();
21001
21091
  return clean ? `${head}
21002
- ${" ".repeat(6)}${clean}` : head;
21092
+ ${" ".repeat(NAME_INDENT)}${clean}` : head;
21003
21093
  }
21004
21094
  var addSkillCommand = defineCommand({
21005
21095
  meta: {
@@ -21018,23 +21108,17 @@ var addSkillCommand = defineCommand({
21018
21108
  description: "Output machine-readable JSON"
21019
21109
  }
21020
21110
  },
21021
- run: async ({ args }) => {
21022
- try {
21023
- const client = resolveClient2();
21024
- const entry = await findPlazaEntry(client, args["skill-slug"]);
21025
- const skill = await client.skills.install(entry);
21026
- if (args.json) {
21027
- process.stdout.write(`${JSON.stringify(skill, null, 2)}
21111
+ run: ({ args }) => runCliCommand(async () => {
21112
+ const client = createCliClient();
21113
+ const entry = await findPlazaEntry(client, args["skill-slug"]);
21114
+ const skill = await client.skills.install(entry);
21115
+ if (args.json) {
21116
+ process.stdout.write(`${JSON.stringify(skill, null, 2)}
21028
21117
  `);
21029
- return;
21030
- }
21031
- consola.success(`Installed ${stripControlChars(skill.command)} (${stripControlChars(skill.name)}).`);
21032
- } catch (err) {
21033
- if (isTransportFailure(err))
21034
- throw err;
21035
- reportCommandError(err, { json: args.json });
21118
+ return;
21036
21119
  }
21037
- }
21120
+ consola.success(`Installed ${stripControlChars(skill.command)} (${stripControlChars(skill.name)}).`);
21121
+ }, { json: args.json })
21038
21122
  });
21039
21123
  var rmSkillCommand = defineCommand({
21040
21124
  meta: {
@@ -21058,36 +21142,30 @@ var rmSkillCommand = defineCommand({
21058
21142
  description: "Output machine-readable JSON"
21059
21143
  }
21060
21144
  },
21061
- run: async ({ args }) => {
21062
- try {
21063
- if (needsConfirmationRefusal(args.yes === true)) {
21064
- throw new Error("Confirmation required. Pass --yes to confirm removal non-interactively.");
21065
- }
21066
- if (!args.yes) {
21067
- const confirmed = await confirm({
21068
- message: `Remove skill "${args["skill-command"]}" from your library? Re-installing needs a fresh plaza install.`,
21069
- initialValue: false
21070
- });
21071
- if (isCancel(confirmed) || !confirmed) {
21072
- consola.info("Skill removal cancelled.");
21073
- return;
21074
- }
21075
- }
21076
- const client = resolveClient2();
21077
- const skill = await findLibraryEntry(client, args["skill-command"]);
21078
- await client.skills.remove(skill.id);
21079
- if (args.json) {
21080
- process.stdout.write(`${JSON.stringify({ success: true, id: skill.id, deleted: true }, null, 2)}
21081
- `);
21145
+ run: ({ args }) => runCliCommand(async () => {
21146
+ if (needsConfirmationRefusal(args.yes === true)) {
21147
+ throw new Error("Confirmation required. Pass --yes to confirm removal non-interactively.");
21148
+ }
21149
+ if (!args.yes) {
21150
+ const confirmed = await confirm({
21151
+ message: `Remove skill "${args["skill-command"]}" from your library? Re-installing needs a fresh plaza install.`,
21152
+ initialValue: false
21153
+ });
21154
+ if (isCancel(confirmed) || !confirmed) {
21155
+ consola.info("Skill removal cancelled.");
21082
21156
  return;
21083
21157
  }
21084
- consola.success(`Skill "${args["skill-command"]}" removed from your library.`);
21085
- } catch (err) {
21086
- if (isTransportFailure(err))
21087
- throw err;
21088
- reportCommandError(err, { json: args.json });
21089
21158
  }
21090
- }
21159
+ const client = createCliClient();
21160
+ const skill = await findLibraryEntry(client, args["skill-command"]);
21161
+ await client.skills.remove(skill.id);
21162
+ if (args.json) {
21163
+ process.stdout.write(`${JSON.stringify({ success: true, id: skill.id, deleted: true }, null, 2)}
21164
+ `);
21165
+ return;
21166
+ }
21167
+ consola.success(`Skill "${args["skill-command"]}" removed from your library.`);
21168
+ }, { json: args.json })
21091
21169
  });
21092
21170
  function makeSetEnabledCommand(name, enabled, description) {
21093
21171
  return defineCommand({
@@ -21104,23 +21182,17 @@ function makeSetEnabledCommand(name, enabled, description) {
21104
21182
  description: "Output machine-readable JSON"
21105
21183
  }
21106
21184
  },
21107
- run: async ({ args }) => {
21108
- try {
21109
- const client = resolveClient2();
21110
- const found = await findLibraryEntry(client, args["skill-command"]);
21111
- const skill = await client.skills.setEnabled(found.id, enabled);
21112
- if (args.json) {
21113
- process.stdout.write(`${JSON.stringify(skill, null, 2)}
21185
+ run: ({ args }) => runCliCommand(async () => {
21186
+ const client = createCliClient();
21187
+ const found = await findLibraryEntry(client, args["skill-command"]);
21188
+ const skill = await client.skills.setEnabled(found.id, enabled);
21189
+ if (args.json) {
21190
+ process.stdout.write(`${JSON.stringify(skill, null, 2)}
21114
21191
  `);
21115
- return;
21116
- }
21117
- consola.success(`${enabled ? "Enabled" : "Disabled"} ${stripControlChars(skill.command)}.`);
21118
- } catch (err) {
21119
- if (isTransportFailure(err))
21120
- throw err;
21121
- reportCommandError(err, { json: args.json });
21192
+ return;
21122
21193
  }
21123
- }
21194
+ consola.success(`${enabled ? "Enabled" : "Disabled"} ${stripControlChars(skill.command)}.`);
21195
+ }, { json: args.json })
21124
21196
  });
21125
21197
  }
21126
21198
  var enableSkillCommand = makeSetEnabledCommand("enable", true, "Enable a skill in your library");
@@ -21162,59 +21234,53 @@ var skillsCommand = defineCommand({
21162
21234
  enable: enableSkillCommand,
21163
21235
  rm: rmSkillCommand
21164
21236
  },
21165
- run: async ({ args }) => {
21166
- try {
21167
- const client = resolveClient2();
21168
- if (args.plaza) {
21169
- const plaza = await client.skills.browsePlaza();
21170
- if (args.json) {
21171
- process.stdout.write(`${JSON.stringify(plaza, null, 2)}
21172
- `);
21173
- return;
21174
- }
21175
- if (plaza.length === 0) {
21176
- consola.info("The Skill Plaza has no entries yet.");
21177
- return;
21178
- }
21179
- const installed = plaza.filter((s) => s.added);
21180
- const available = plaza.filter((s) => !s.added);
21181
- const installedRows = installed.map((s) => renderSkillRow(s.enabled ? "on" : "off", s.name, s.descriptionEn));
21182
- const availableRows = available.map((s) => renderSkillRow("none", s.name, s.descriptionEn));
21183
- consola.log([
21184
- `Installed (${installed.length})`,
21185
- ...installedRows,
21186
- "",
21187
- `Available (${available.length})`,
21188
- ...availableRows,
21189
- "",
21190
- import_picocolors2.gray(`install with msc skills add <slug> · slug names are in --json`)
21191
- ].join(`
21192
- `));
21193
- return;
21194
- }
21195
- const skills = await client.skills.listAll();
21237
+ run: ({ args }) => runCliCommand(async () => {
21238
+ const client = createCliClient();
21239
+ if (args.plaza) {
21240
+ const plaza = await client.skills.browsePlaza();
21196
21241
  if (args.json) {
21197
- process.stdout.write(`${JSON.stringify(skills, null, 2)}
21242
+ process.stdout.write(`${JSON.stringify(plaza, null, 2)}
21198
21243
  `);
21199
21244
  return;
21200
21245
  }
21201
- if (skills.length === 0) {
21202
- consola.info("Your skill library is empty. Browse the plaza with --plaza, or add skills in the Media Studio web app.");
21246
+ if (plaza.length === 0) {
21247
+ consola.info("The Skill Plaza has no entries yet.");
21203
21248
  return;
21204
21249
  }
21205
- const rows = skills.map((s) => renderSkillRow(s.enabled ? "on" : "off", s.name, s.descriptionEn));
21250
+ const installed = plaza.filter((s) => s.added);
21251
+ const available = plaza.filter((s) => !s.added);
21252
+ const installedRows = installed.map((s) => renderSkillRow(s.enabled ? "on" : "off", s.name, s.descriptionEn));
21253
+ const availableRows = available.map((s) => renderSkillRow("none", s.name, s.descriptionEn));
21206
21254
  consola.log([
21207
- ...rows,
21255
+ `Installed (${installed.length})`,
21256
+ ...installedRows,
21257
+ "",
21258
+ `Available (${available.length})`,
21259
+ ...availableRows,
21208
21260
  "",
21209
- import_picocolors2.gray("skills in this list can be referenced as %%skill-name%% in a prompt")
21261
+ import_picocolors2.gray(`install with msc skills add <slug> · slug names are in --json`)
21210
21262
  ].join(`
21211
21263
  `));
21212
- } catch (err) {
21213
- if (isTransportFailure(err))
21214
- throw err;
21215
- reportCommandError(err, { json: args.json });
21264
+ return;
21216
21265
  }
21217
- }
21266
+ const skills = await client.skills.listAll();
21267
+ if (args.json) {
21268
+ process.stdout.write(`${JSON.stringify(skills, null, 2)}
21269
+ `);
21270
+ return;
21271
+ }
21272
+ if (skills.length === 0) {
21273
+ consola.info("Your skill library is empty. Browse the plaza with --plaza, or add skills in the Media Studio web app.");
21274
+ return;
21275
+ }
21276
+ const rows = skills.map((s) => renderSkillRow(s.enabled ? "on" : "off", s.name, s.descriptionEn));
21277
+ consola.log([
21278
+ ...rows,
21279
+ "",
21280
+ import_picocolors2.gray("skills in this list can be referenced as %%skill-name%% in a prompt")
21281
+ ].join(`
21282
+ `));
21283
+ }, { json: args.json })
21218
21284
  });
21219
21285
 
21220
21286
  // src/commands/whoami.ts
@@ -21226,7 +21292,7 @@ var whoamiCommand = defineCommand({
21226
21292
  name: "whoami",
21227
21293
  description: "Display current authentication status"
21228
21294
  },
21229
- run: async () => {
21295
+ run: () => runCliCommand(async () => {
21230
21296
  let auth;
21231
21297
  try {
21232
21298
  auth = readAuthFile();
@@ -21243,50 +21309,41 @@ var whoamiCommand = defineCommand({
21243
21309
  return;
21244
21310
  }
21245
21311
  const apiUrl = resolveApiBaseUrl();
21246
- const client = new MediaStudioClient({
21247
- baseUrl: apiUrl,
21248
- apiKey: auth.token
21249
- });
21250
- try {
21251
- const data = await client.getMe();
21252
- if (!data.valid) {
21253
- consola.error("Session expired or token invalid.\nPlease run `msc login` to re-authenticate.");
21254
- process.exit(1);
21255
- return;
21256
- }
21257
- const updatedName = data.name ?? auth.username;
21258
- const updatedUid = data.uid ?? auth.uid;
21259
- const updatedEmail = data.email ?? auth.email;
21260
- if (updatedName !== auth.username || updatedUid !== auth.uid || updatedEmail !== auth.email) {
21261
- try {
21262
- saveAuthFile({
21263
- ...auth,
21264
- token: client.apiKey,
21265
- username: updatedName,
21266
- uid: updatedUid,
21267
- email: updatedEmail
21268
- });
21269
- } catch (err) {
21270
- consola.warn(err instanceof Error ? err.message : String(err));
21271
- }
21272
- }
21273
- consola.success("Authenticated");
21274
- const lines = [
21275
- updatedName ? ` User: ${updatedName}` : null,
21276
- updatedUid ? ` UID: ${updatedUid}` : null,
21277
- updatedEmail ? ` Email: ${updatedEmail}` : null,
21278
- data.apiKeyMasked ? ` API Key: ${data.apiKeyMasked}` : null,
21279
- ` Token: ${maskToken(client.apiKey)}`
21280
- ].filter(Boolean);
21281
- consola.log(`${lines.join(`
21312
+ const client = createCliClient({ baseUrl: apiUrl, apiKey: auth.token });
21313
+ const data = await client.getMe();
21314
+ if (!data.valid) {
21315
+ consola.error("Session expired or token invalid.\nPlease run `msc login` to re-authenticate.");
21316
+ process.exit(1);
21317
+ return;
21318
+ }
21319
+ const updatedName = data.name ?? auth.username;
21320
+ const updatedUid = data.uid ?? auth.uid;
21321
+ const updatedEmail = data.email ?? auth.email;
21322
+ if (updatedName !== auth.username || updatedUid !== auth.uid || updatedEmail !== auth.email) {
21323
+ try {
21324
+ saveAuthFile({
21325
+ ...auth,
21326
+ token: client.apiKey,
21327
+ username: updatedName,
21328
+ uid: updatedUid,
21329
+ email: updatedEmail
21330
+ });
21331
+ } catch (err) {
21332
+ consola.warn(err instanceof Error ? err.message : String(err));
21333
+ }
21334
+ }
21335
+ consola.success("Authenticated");
21336
+ const lines = [
21337
+ updatedName ? ` User: ${updatedName}` : null,
21338
+ updatedUid ? ` UID: ${updatedUid}` : null,
21339
+ updatedEmail ? ` Email: ${updatedEmail}` : null,
21340
+ data.apiKeyMasked ? ` API Key: ${data.apiKeyMasked}` : null,
21341
+ ` Token: ${maskToken(client.apiKey)}`
21342
+ ].filter(Boolean);
21343
+ consola.log(`${lines.join(`
21282
21344
  `)}
21283
21345
  `);
21284
- } catch (err) {
21285
- if (isTransportFailure(err))
21286
- throw err;
21287
- reportCommandError(err);
21288
- }
21289
- }
21346
+ })
21290
21347
  });
21291
21348
 
21292
21349
  // src/lib/update-check.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.1",
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",