@302ai/media-studio-cli 0.1.1-beta.0 → 0.1.1-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 +14 -1
  2. package/dist/index.js +426 -182
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -115,9 +115,22 @@ The CLI stores credentials and local settings in:
115
115
  - **Override**: `MEDIA_STUDIO_CONFIG_DIR=/path/to/dir`
116
116
 
117
117
  Key files:
118
- - `auth.json`: stores active platform credentials and user session metadata (permissions `0600`)
118
+ - `auth.json`: stores active platform credentials and user session metadata (permissions `0600` on POSIX)
119
119
  - `config.json`: stores CLI client settings, including `lastSessionId` pointer
120
120
 
121
+ `auth.json`'s `apiUrl` is only accepted when its host is on the CLI allowlist:
122
+ the official hosts (which the rejection message lists) plus the local-dev
123
+ hosts `localhost` / `127.0.0.1` / `::1`. A hand-edited URL outside that list is
124
+ refused before any request is sent, and the CLI reports it as a rejected
125
+ credential naming the URL — not as "not logged in", since the token itself may
126
+ be fine. `msc login` rewrites the file with a server-supplied URL and is the
127
+ fix; the refused URL is never used, and base-URL resolution falls back to the
128
+ official default host.
129
+
130
+ In `--json` mode the two auth states are distinguishable without reading the
131
+ message: a rejected credential carries `"nextStep": "relogin"` (and its
132
+ `guidance`), while no credential at all reports `status` and `message` only.
133
+
121
134
  ### Skills
122
135
 
123
136
  Discover what your account can reference, then reference it — the reference
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-beta.0",
4287
+ version: "0.1.1-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",
@@ -9701,6 +9701,7 @@ var ATTACHMENT_MIME_BY_EXTENSION = {
9701
9701
 
9702
9702
  // ../media-studio-contract/src/attachments/markers.ts
9703
9703
  var ATTACHMENT_MARKER = "&&";
9704
+ var ATTACHMENT_MARKER_REGEX = /&&([^&\s]+)&&/g;
9704
9705
  function formatAttachmentMarker(remoteUrl) {
9705
9706
  return `${ATTACHMENT_MARKER}${remoteUrl}${ATTACHMENT_MARKER}`;
9706
9707
  }
@@ -10073,11 +10074,14 @@ var APP_ENV_HOSTS = {
10073
10074
  // ../media-studio-contract/src/skills/activation.ts
10074
10075
  var SKILL_ACTIVATION_MARKER = "%%";
10075
10076
  var SKILL_ACTIVATION_RE = /%%(\/[^%\s]+)%%/g;
10077
+ function createSkillActivationRegex() {
10078
+ return new RegExp(SKILL_ACTIVATION_RE.source, SKILL_ACTIVATION_RE.flags);
10079
+ }
10076
10080
  function formatSkillActivation(command) {
10077
10081
  return `${SKILL_ACTIVATION_MARKER}${command}${SKILL_ACTIVATION_MARKER}`;
10078
10082
  }
10079
10083
  function findSkillActivationMatches(text) {
10080
- return Array.from(text.matchAll(SKILL_ACTIVATION_RE)).flatMap((match, index) => {
10084
+ return Array.from(text.matchAll(createSkillActivationRegex())).flatMap((match, index) => {
10081
10085
  const start = match.index;
10082
10086
  const command = match[1];
10083
10087
  if (start === undefined || !command)
@@ -10094,6 +10098,25 @@ function findSkillActivationMatches(text) {
10094
10098
  });
10095
10099
  }
10096
10100
 
10101
+ // ../media-studio-contract/src/sessions/derive-first-turn-note.ts
10102
+ var DEFAULT_FALLBACK_TITLE = "Agent Task";
10103
+ var MAX_NOTE_LENGTH = 64;
10104
+ function deriveFirstTurnNote(rawText, options) {
10105
+ const fallback = options?.fallbackTitle ?? DEFAULT_FALLBACK_TITLE;
10106
+ if (!rawText) {
10107
+ return fallback;
10108
+ }
10109
+ 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();
10110
+ if (!unwrapped) {
10111
+ return fallback;
10112
+ }
10113
+ const codePoints = Array.from(unwrapped);
10114
+ if (codePoints.length > MAX_NOTE_LENGTH) {
10115
+ return codePoints.slice(0, MAX_NOTE_LENGTH).join("");
10116
+ }
10117
+ return unwrapped;
10118
+ }
10119
+
10097
10120
  // ../media-studio-contract/src/skills/compose-turn.ts
10098
10121
  function applySkillActivations(text, spans) {
10099
10122
  const ordered = spans.toSorted((a, b) => b.start - a.start);
@@ -16875,9 +16898,7 @@ function validateApiBaseUrl(urlString) {
16875
16898
  }
16876
16899
  var AuthDataSchema = object2({
16877
16900
  token: string4().min(1),
16878
- apiUrl: string4().refine((val) => isAllowedCliApiOrigin(val), {
16879
- message: "Untrusted API URL in auth file"
16880
- }).optional(),
16901
+ apiUrl: string4().optional(),
16881
16902
  uid: string4().optional(),
16882
16903
  username: string4().optional(),
16883
16904
  email: string4().optional(),
@@ -16891,11 +16912,35 @@ var ConfigFileSchema = object2({
16891
16912
  latest: string4().optional()
16892
16913
  }).optional()
16893
16914
  });
16915
+
16916
+ class UntrustedCredentialError extends Error {
16917
+ rejectedUrl;
16918
+ constructor(rejectedUrl) {
16919
+ super(`Untrusted apiUrl in the saved credential: "${rejectedUrl}". ` + `Allowed hosts: ${MEDIA_STUDIO_HOSTS.join(", ")}. ` + `Run \`msc login\` to sign in - it opens a browser, so a human must complete it.`);
16920
+ this.name = "UntrustedCredentialError";
16921
+ this.rejectedUrl = rejectedUrl;
16922
+ }
16923
+ }
16894
16924
  function readAuthFile() {
16925
+ let raw;
16895
16926
  try {
16896
- const parsed = JSON.parse(readFileSync(getAuthFilePath(), "utf8"));
16897
- const res = AuthDataSchema.safeParse(parsed);
16898
- return res.success ? res.data : null;
16927
+ raw = JSON.parse(readFileSync(getAuthFilePath(), "utf8"));
16928
+ } catch {
16929
+ return null;
16930
+ }
16931
+ const shape = AuthDataSchema.safeParse(raw);
16932
+ if (!shape.success || !shape.data.token.trim()) {
16933
+ return null;
16934
+ }
16935
+ const savedUrl = shape.data.apiUrl?.trim();
16936
+ if (savedUrl && !isAllowedCliApiOrigin(savedUrl)) {
16937
+ throw new UntrustedCredentialError(savedUrl);
16938
+ }
16939
+ return shape.data;
16940
+ }
16941
+ function readAuthFileIfUsable() {
16942
+ try {
16943
+ return readAuthFile();
16899
16944
  } catch {
16900
16945
  return null;
16901
16946
  }
@@ -16949,7 +16994,8 @@ function saveUpdateCheck(check) {
16949
16994
  writeConfigFile({ updateCheck: check });
16950
16995
  }
16951
16996
  function resolveApiKey() {
16952
- const token = readAuthFile()?.token;
16997
+ const auth = readAuthFile();
16998
+ const token = auth?.token;
16953
16999
  if (!token?.trim()) {
16954
17000
  throw new Error("Missing authentication. Run `msc login` to sign in - it opens a browser, so a human " + "must complete it. Instant if that browser is already signed in to the web app.");
16955
17001
  }
@@ -16958,7 +17004,7 @@ function resolveApiKey() {
16958
17004
  function resolveApiBaseUrl(flag) {
16959
17005
  if (flag)
16960
17006
  return validateApiBaseUrl(flag);
16961
- const fromAuth = readAuthFile()?.apiUrl;
17007
+ const fromAuth = readAuthFileIfUsable()?.apiUrl;
16962
17008
  if (typeof fromAuth === "string" && fromAuth.trim()) {
16963
17009
  if (isAllowedCliApiOrigin(fromAuth)) {
16964
17010
  return fromAuth;
@@ -16969,6 +17015,7 @@ function resolveApiBaseUrl(flag) {
16969
17015
 
16970
17016
  // src/client/session-types.ts
16971
17017
  var SessionSourceSchema = _enum2(["web", "cli"]);
17018
+ var CLI_DEFAULT_SESSION_NOTE = "Agent Task";
16972
17019
  var ApiResponseBaseSchema = object2({
16973
17020
  success: boolean4(),
16974
17021
  error: object2({
@@ -17438,7 +17485,7 @@ class MediaStudioClient {
17438
17485
  const nextKey = rotatedToken.trim();
17439
17486
  this._apiKey = nextKey;
17440
17487
  try {
17441
- const authData = readAuthFile();
17488
+ const authData = readAuthFileIfUsable();
17442
17489
  if (authData) {
17443
17490
  saveAuthFile({
17444
17491
  ...authData,
@@ -17761,10 +17808,50 @@ var NEXT_STEP_GUIDANCE = {
17761
17808
  retry: "The failure looks transient. Retrying the same request may succeed.",
17762
17809
  none: ""
17763
17810
  };
17811
+ var TRANSPORT_CAUSE_DEPTH = 5;
17812
+ var TRANSPORT_MESSAGE = /fetch failed|Unable to connect/i;
17813
+ var ERRNO_SHAPE = /^[A-Z][A-Z0-9_]*$/;
17814
+ function isTerminalControl(codePoint) {
17815
+ return codePoint <= 8 && codePoint !== 9 || codePoint >= 14 && codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint === 8206 || codePoint === 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
17816
+ }
17817
+ function stripTerminalControls(text) {
17818
+ let out = "";
17819
+ for (const char of text) {
17820
+ if (!isTerminalControl(char.codePointAt(0) ?? -1))
17821
+ out += char;
17822
+ }
17823
+ return out;
17824
+ }
17825
+ function detailOf(node, code) {
17826
+ if (!("message" in node))
17827
+ return "";
17828
+ const { message } = node;
17829
+ if (typeof message !== "string")
17830
+ return "";
17831
+ return stripTerminalControls(message.split(code).join(" ")).replace(/\s+/g, " ").trim().replace(/^[:-]\s*/, "");
17832
+ }
17833
+ function findTransportErrno(err) {
17834
+ let current = err;
17835
+ for (let depth = 0;depth < TRANSPORT_CAUSE_DEPTH; depth++) {
17836
+ if (!(current instanceof Error))
17837
+ return null;
17838
+ const code = "code" in current ? current.code : undefined;
17839
+ const errno = typeof code === "string" && ERRNO_SHAPE.test(code) ? code : null;
17840
+ if (errno)
17841
+ return { code: errno, detail: detailOf(current, errno) };
17842
+ current = current.cause;
17843
+ }
17844
+ return null;
17845
+ }
17846
+ function isTransportFailure(err) {
17847
+ return findTransportErrno(err) !== null || err instanceof Error && TRANSPORT_MESSAGE.test(err.message);
17848
+ }
17764
17849
  function reportCommandError(err, options) {
17765
- const message = err instanceof Error ? err.message : String(err);
17850
+ const plain = err instanceof Error ? err.message : String(err);
17851
+ const transport = findTransportErrno(err);
17852
+ const message = transport && !plain.includes(transport.code) ? `${plain} (${transport.code}${transport.detail ? `: ${transport.detail}` : ""})` : plain;
17766
17853
  const upstream = err instanceof ChatUpstreamError ? err : null;
17767
- const nextStep = upstream?.nextStep ?? (upstream?.status === 401 ? "relogin" : null);
17854
+ const nextStep = upstream?.nextStep ?? (upstream?.status === 401 || err instanceof UntrustedCredentialError ? "relogin" : null);
17768
17855
  const status = upstream?.status !== undefined && upstream.status !== 0 ? upstream.status : null;
17769
17856
  const guidance = nextStep ? NEXT_STEP_GUIDANCE[nextStep] : "";
17770
17857
  if (options?.json) {
@@ -17774,7 +17861,8 @@ function reportCommandError(err, options) {
17774
17861
  ...upstream?.errCode != null ? { errCode: upstream.errCode } : {},
17775
17862
  ...status != null ? { httpStatus: status } : {},
17776
17863
  ...nextStep != null ? { nextStep } : {},
17777
- ...guidance ? { guidance } : {}
17864
+ ...guidance ? { guidance } : {},
17865
+ ...transport ? { transportCode: transport.code } : {}
17778
17866
  };
17779
17867
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
17780
17868
  `);
@@ -18003,6 +18091,16 @@ function isSessionGoneError(err) {
18003
18091
  return true;
18004
18092
  return err.body.includes(ERR_SESSION_EXPIRED);
18005
18093
  }
18094
+
18095
+ class SessionNotFoundError extends Error {
18096
+ sessionId;
18097
+ constructor(sessionId) {
18098
+ super(`Session ${sessionId} does not exist.
18099
+ ` + ' → Create one: `msc sessions use --new`, or `msc chat --new "<prompt>"` to also send a prompt.');
18100
+ this.name = "SessionNotFoundError";
18101
+ this.sessionId = sessionId;
18102
+ }
18103
+ }
18006
18104
  async function ensureSessionExists(options) {
18007
18105
  const client = new MediaStudioClient({
18008
18106
  baseUrl: options.apiBaseUrl,
@@ -18010,8 +18108,7 @@ async function ensureSessionExists(options) {
18010
18108
  });
18011
18109
  if (await client.sessions.exists(options.sessionId))
18012
18110
  return;
18013
- throw new Error(`Session ${options.sessionId} does not exist.
18014
- ` + ' → Create one: `msc sessions use --new`, or `msc chat --new "<prompt>"` to also send a prompt.');
18111
+ throw new SessionNotFoundError(options.sessionId);
18015
18112
  }
18016
18113
  function finalizeProvision(result, spinner) {
18017
18114
  spinner?.stop();
@@ -18032,6 +18129,7 @@ async function provisionSession(options) {
18032
18129
  const outcome = await client.sessions.prewarm({
18033
18130
  sessionId,
18034
18131
  source: "cli",
18132
+ note: options.note ?? CLI_DEFAULT_SESSION_NOTE,
18035
18133
  onSessionReady: () => saveLastSessionId(sessionId),
18036
18134
  onStageChange: options.onStageChange
18037
18135
  });
@@ -18094,7 +18192,7 @@ async function ensureUsable(client, skill) {
18094
18192
  });
18095
18193
  if (ensured)
18096
18194
  return;
18097
- throw new SkillResolutionError(describeEnableFailure(skill, cause));
18195
+ throw new SkillResolutionError(describeEnableFailure(skill, cause), cause ? { cause } : undefined);
18098
18196
  }
18099
18197
  async function resolvePromptSkills(options) {
18100
18198
  const spans = findSkillRefSpans(options.prompt);
@@ -18167,11 +18265,21 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
18167
18265
  process.stdout.write(`====================
18168
18266
  `);
18169
18267
  }
18268
+ function buildNotePrompt(prompt, attachments) {
18269
+ return prompt.replace(ATTACHMENT_MARKER_REGEX, (marker, url) => {
18270
+ const attachment = attachments.find((a) => a.remoteUrl === url);
18271
+ return attachment ? attachment.name || attachment.remoteUrl : marker;
18272
+ });
18273
+ }
18170
18274
  async function provisionNewSession(options) {
18171
18275
  const onStageChange = options.quiet ? undefined : (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage]);
18276
+ const note = deriveFirstTurnNote(options.prompt, {
18277
+ fallbackTitle: CLI_DEFAULT_SESSION_NOTE
18278
+ });
18172
18279
  const result = await provisionSession({
18173
18280
  apiBaseUrl: options.apiBaseUrl,
18174
18281
  apiKey: options.apiKey,
18282
+ note,
18175
18283
  onStageChange
18176
18284
  });
18177
18285
  return finalizeProvision(result, options.spinner);
@@ -18179,7 +18287,10 @@ async function provisionNewSession(options) {
18179
18287
  async function resolveChatSession(options) {
18180
18288
  if (options.newSession) {
18181
18289
  return {
18182
- sessionId: await provisionNewSession(options),
18290
+ sessionId: await provisionNewSession({
18291
+ ...options,
18292
+ prompt: options.prompt
18293
+ }),
18183
18294
  recreateOnGone: false
18184
18295
  };
18185
18296
  }
@@ -18192,10 +18303,22 @@ async function resolveChatSession(options) {
18192
18303
  return { sessionId: pointer, recreateOnGone: true };
18193
18304
  }
18194
18305
  return {
18195
- sessionId: await provisionNewSession(options),
18306
+ sessionId: await provisionNewSession({
18307
+ ...options,
18308
+ prompt: options.prompt
18309
+ }),
18196
18310
  recreateOnGone: false
18197
18311
  };
18198
18312
  }
18313
+ function describeStreamFailure(err) {
18314
+ if (err instanceof Error && err.name === "AbortError") {
18315
+ return "aborted";
18316
+ }
18317
+ if (err instanceof Error && err.message) {
18318
+ return err.message;
18319
+ }
18320
+ return String(err);
18321
+ }
18199
18322
  async function streamTurn(options) {
18200
18323
  const { args, sessionId, spinner, autoEnabled, attachments } = options;
18201
18324
  if (!args.json) {
@@ -18215,87 +18338,103 @@ async function streamTurn(options) {
18215
18338
  const steps = [];
18216
18339
  const mediaAssets = [];
18217
18340
  let hasFinished = false;
18218
- for await (const ev of stream) {
18219
- if (ev.type !== "finish" && ev.type !== "error") {
18220
- options.progress.emitted = true;
18221
- }
18222
- M(ev).with({ type: "text-delta", delta: z2.string }, ({ delta }) => {
18223
- isThinking = false;
18224
- spinner.stop();
18225
- fullText += delta;
18226
- if (!args.json) {
18227
- process.stdout.write(delta);
18341
+ let emitting = false;
18342
+ let streamFailure;
18343
+ try {
18344
+ for await (const ev of stream) {
18345
+ if (ev.type !== "finish" && ev.type !== "error") {
18346
+ options.progress.emitted = true;
18228
18347
  }
18229
- }).with({ type: "reasoning-delta", delta: z2.string }, ({ delta }) => {
18230
- reasoning += delta;
18231
- if (args["show-reasoning"] && !args.json) {
18348
+ M(ev).with({ type: "text-delta", delta: z2.string }, ({ delta }) => {
18349
+ isThinking = false;
18232
18350
  spinner.stop();
18233
- process.stdout.write(delta);
18234
- } else if (!args.json) {
18235
- if (!isThinking) {
18236
- isThinking = true;
18351
+ fullText += delta;
18352
+ if (!args.json) {
18353
+ process.stdout.write(delta);
18354
+ }
18355
+ }).with({ type: "reasoning-delta", delta: z2.string }, ({ delta }) => {
18356
+ reasoning += delta;
18357
+ if (args["show-reasoning"] && !args.json) {
18237
18358
  spinner.stop();
18238
- if (fullText && !fullText.endsWith(`
18359
+ process.stdout.write(delta);
18360
+ } else if (!args.json) {
18361
+ if (!isThinking) {
18362
+ isThinking = true;
18363
+ spinner.stop();
18364
+ if (fullText && !fullText.endsWith(`
18239
18365
  `)) {
18240
- process.stdout.write(`
18366
+ process.stdout.write(`
18241
18367
  `);
18242
- fullText += `
18368
+ fullText += `
18243
18369
  `;
18244
- }
18245
- process.stdout.write(`[reasoning] Thinking...
18370
+ }
18371
+ process.stdout.write(`[reasoning] Thinking...
18246
18372
  `);
18247
- spinner.start("Thinking...");
18373
+ spinner.start("Thinking...");
18374
+ }
18248
18375
  }
18249
- }
18250
- }).with({ type: "tool-call", toolName: z2.string }, ({ toolName, args: toolArgs }) => {
18251
- isThinking = false;
18252
- spinner.stop();
18253
- if (fullText && !fullText.endsWith(`
18376
+ }).with({ type: "tool-call", toolName: z2.string }, ({ toolName, args: toolArgs }) => {
18377
+ isThinking = false;
18378
+ spinner.stop();
18379
+ if (fullText && !fullText.endsWith(`
18254
18380
  `) && !args.json) {
18255
- process.stdout.write(`
18381
+ process.stdout.write(`
18256
18382
  `);
18257
- fullText += `
18383
+ fullText += `
18258
18384
  `;
18259
- }
18260
- const formatted = formatToolMilestone(toolName, toolArgs);
18261
- steps.push(formatted.milestoneLine);
18262
- if (!args.json) {
18263
- process.stdout.write(`${formatted.milestoneLine}
18385
+ }
18386
+ const formatted = formatToolMilestone(toolName, toolArgs);
18387
+ steps.push(formatted.milestoneLine);
18388
+ if (!args.json) {
18389
+ process.stdout.write(`${formatted.milestoneLine}
18264
18390
  `);
18265
- spinner.start(formatted.actionLabel);
18266
- }
18267
- }).with({ type: "media-result" }, ({ result }) => {
18268
- spinner.stop();
18269
- addOrMergeMediaResult(mediaAssets, result);
18270
- }).with({ type: "error" }, (ev) => {
18271
- spinner.stop();
18272
- const raw = ("error" in ev && typeof ev.error === "string" ? ev.error : null) ?? ("message" in ev && typeof ev.message === "string" ? ev.message : null) ?? "Upstream stream error";
18273
- throw new ChatUpstreamError(extractUpstreamErrorMessage(raw) || raw, 0, raw);
18274
- }).with({ type: "finish" }, () => {
18275
- hasFinished = true;
18276
- spinner.stop();
18277
- if (args.json) {
18278
- const output = {
18279
- status: "success",
18280
- sessionId,
18281
- text: fullText,
18282
- steps,
18283
- media: mediaAssets,
18284
- ...attachments.length > 0 ? { attachments } : {},
18285
- ...autoEnabled.length > 0 ? { autoEnabled } : {},
18286
- ...args["show-reasoning"] && reasoning ? { reasoning } : {}
18287
- };
18288
- process.stdout.write(`${JSON.stringify(output, null, 2)}
18391
+ spinner.start(formatted.actionLabel);
18392
+ }
18393
+ }).with({ type: "media-result" }, ({ result }) => {
18394
+ spinner.stop();
18395
+ addOrMergeMediaResult(mediaAssets, result);
18396
+ }).with({ type: "error" }, (ev) => {
18397
+ spinner.stop();
18398
+ const raw = ("error" in ev && typeof ev.error === "string" ? ev.error : null) ?? ("message" in ev && typeof ev.message === "string" ? ev.message : null) ?? "Upstream stream error";
18399
+ throw new ChatUpstreamError(extractUpstreamErrorMessage(raw) || raw, 0, raw);
18400
+ }).with({ type: "finish" }, () => {
18401
+ emitting = true;
18402
+ hasFinished = true;
18403
+ spinner.stop();
18404
+ if (args.json) {
18405
+ const output = {
18406
+ status: "success",
18407
+ sessionId,
18408
+ text: fullText,
18409
+ steps,
18410
+ media: mediaAssets,
18411
+ ...attachments.length > 0 ? { attachments } : {},
18412
+ ...autoEnabled.length > 0 ? { autoEnabled } : {},
18413
+ ...args["show-reasoning"] && reasoning ? { reasoning } : {}
18414
+ };
18415
+ process.stdout.write(`${JSON.stringify(output, null, 2)}
18289
18416
  `);
18290
- return;
18291
- }
18292
- printTerminalMediaAssets(mediaAssets, fullText);
18293
- process.stdout.write(`session: ${sessionId}
18417
+ emitting = false;
18418
+ return;
18419
+ }
18420
+ printTerminalMediaAssets(mediaAssets, fullText);
18421
+ process.stdout.write(`session: ${sessionId}
18294
18422
  `);
18295
- }).with(z2._, () => {}).exhaustive();
18423
+ emitting = false;
18424
+ }).with(z2._, () => {}).exhaustive();
18425
+ }
18426
+ } catch (err) {
18427
+ if (err instanceof ChatUpstreamError)
18428
+ throw err;
18429
+ if (emitting)
18430
+ throw err;
18431
+ if (!hasFinished && !options.progress.emitted)
18432
+ throw err;
18433
+ streamFailure = err;
18296
18434
  }
18297
18435
  if (!hasFinished) {
18298
18436
  spinner.stop();
18437
+ consola.warn(streamFailure === undefined ? "Stream ended early: no finish event" : `Stream ended early: ${describeStreamFailure(streamFailure)}`);
18299
18438
  if (args.json) {
18300
18439
  const output = {
18301
18440
  status: "incomplete",
@@ -18321,30 +18460,48 @@ async function runTurn(options) {
18321
18460
  await streamTurn(options);
18322
18461
  } catch (err) {
18323
18462
  const sessionGone = isSessionGoneError(err);
18324
- if (sessionGone && options.progress.emitted) {
18463
+ const ambiguousGone = err instanceof ChatUpstreamError && err.errCode === ErrCode.INVALID_PARAM_FORMAT;
18464
+ if (!sessionGone && !ambiguousGone) {
18465
+ throw err;
18466
+ }
18467
+ if (options.progress.emitted) {
18325
18468
  throw new Error(`Session ${options.sessionId} failed after this turn had already started.
18326
18469
  ` + " → The partial result above stands; re-run the same prompt to continue in a new session.");
18327
18470
  }
18328
- if (sessionGone && !options.recreateOnGone) {
18329
- throw new Error(`Session ${options.sessionId} does not exist.
18330
- ` + ' → Create one: `msc sessions use --new`, or `msc chat --new "<prompt>"` to also send a prompt.');
18471
+ if (!options.recreateOnGone) {
18472
+ if (sessionGone || options.args.session) {
18473
+ throw new SessionNotFoundError(options.sessionId);
18474
+ }
18475
+ throw err;
18331
18476
  }
18332
- if (sessionGone) {
18333
- consola.warn(`Session ${options.sessionId} is no longer available. Provisioning a new one...`);
18334
- const sessionId = await provisionNewSession({
18335
- apiBaseUrl: options.apiBaseUrl,
18336
- apiKey: resolveApiKey(),
18337
- spinner: options.spinner,
18338
- quiet: options.args.json === true
18339
- });
18340
- await streamTurn({
18341
- ...options,
18342
- sessionId,
18343
- apiKey: resolveApiKey()
18344
- });
18345
- return;
18477
+ if (ambiguousGone) {
18478
+ let confirmedGone = false;
18479
+ try {
18480
+ await ensureSessionExists({
18481
+ apiBaseUrl: options.apiBaseUrl,
18482
+ apiKey: resolveApiKey(),
18483
+ sessionId: options.sessionId
18484
+ });
18485
+ } catch (checkErr) {
18486
+ confirmedGone = checkErr instanceof SessionNotFoundError;
18487
+ }
18488
+ if (!confirmedGone) {
18489
+ throw err;
18490
+ }
18346
18491
  }
18347
- throw err;
18492
+ consola.warn(`Session ${options.sessionId} is no longer available. Provisioning a new one...`);
18493
+ const sessionId = await provisionNewSession({
18494
+ apiBaseUrl: options.apiBaseUrl,
18495
+ apiKey: resolveApiKey(),
18496
+ spinner: options.spinner,
18497
+ quiet: options.args.json === true,
18498
+ prompt: buildNotePrompt(options.prompt, options.attachments)
18499
+ });
18500
+ await streamTurn({
18501
+ ...options,
18502
+ sessionId,
18503
+ apiKey: resolveApiKey()
18504
+ });
18348
18505
  }
18349
18506
  }
18350
18507
  var chatCommand = defineCommand({
@@ -18410,6 +18567,7 @@ var chatCommand = defineCommand({
18410
18567
  const resolved = await resolveChatSession({
18411
18568
  sessionFlag: args.session,
18412
18569
  newSession: args.new,
18570
+ prompt: buildNotePrompt(promptWithAttachments, attachments),
18413
18571
  apiBaseUrl,
18414
18572
  apiKey: resolveApiKey(),
18415
18573
  spinner,
@@ -18477,7 +18635,11 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18477
18635
 
18478
18636
  msc whoami
18479
18637
  Displays your current account information (user, email, UID, server URL).
18480
- Exits 0 if authenticated, 1 if unauthenticated or session expired.
18638
+ Exits 0 only when the platform confirmed the session, and the fields it
18639
+ prints are the platform's answer. Exits 1 otherwise - unauthenticated,
18640
+ expired, or the request never reached the platform (DNS, refused, TLS):
18641
+ a saved credential describes a session that was never checked, so it is
18642
+ never printed in place of an answer.
18481
18643
 
18482
18644
  msc logout
18483
18645
  Removes saved credentials on this machine.
@@ -18543,7 +18705,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18543
18705
  msc skills add <slug> # add a skill from the plaza to your library
18544
18706
 
18545
18707
  Referencing Skills in Prompts:
18546
- Lead the prompt with your skill reference — the message must start with it
18708
+ Lead the prompt with your skill reference - the message must start with it
18547
18709
  for the skill to activate:
18548
18710
  Slash form: msc chat "/<name> your prompt here"
18549
18711
  Marker form: msc chat "%%/<name>%% your prompt here"
@@ -18619,15 +18781,82 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18619
18781
  Extracting the result URL (e.g. with jq):
18620
18782
  url=$(msc chat "a red balloon" --json | jq -r '.media[0].resultUrl')
18621
18783
 
18784
+ Exit codes: 0 = the turn ran to completion OR its stream ended early; 1 =
18785
+ \`status: "error"\`, a usage error, or any other failure. Only what the parser
18786
+ itself rejects (unknown flag, missing \`<prompt>\`) exits 1 before the command
18787
+ runs with an empty stdout; argument conflicts checked inside the command --
18788
+ \`--new\` with \`--session\`, \`sessions use --new <id>\` -- do print the error
18789
+ payload below. Either way, check the exit code first, then branch on
18790
+ \`.status\`.
18791
+ Exit code 1 does not imply a payload. A bare transport failure -- the host
18792
+ does not resolve, the port is refused, TLS fails -- is printed by the runtime
18793
+ with its \`cause\` chain instead, so \`--json\` stdout stays empty there: to
18794
+ tell "could not reach the platform" from "the platform answered with a
18795
+ failure", read the exit code, then check whether stdout has a payload at all.
18796
+ \`"success" | "incomplete" | "error"\` are \`chat\`-envelope values: \`chat\` is
18797
+ the only command whose *success* payload carries a \`status\`, and every
18798
+ command that supports \`--json\` puts \`"status": "error"\` on its failure
18799
+ payload. \`login\`, \`logout\` and \`whoami\` define no \`--json\` and emit no
18800
+ payload at all -- their failures are prose on stderr with exit 1. Do not
18801
+ reuse that vocabulary elsewhere -- \`sessions --json\` items carry
18802
+ \`status: { chat, task }\` (a boolean pair) and \`sessions artifacts <id> --json\`
18803
+ items carry the task lifecycle
18804
+ (\`pending | running | processing | completed | failed | cancelled\`).
18805
+ Human stdout carries no status marker -- an early end only warns on
18806
+ stderr -- so never parse it in scripts.
18807
+
18622
18808
  Error schema on failure (exit code 1):
18623
18809
  {
18624
18810
  "status": "error",
18625
18811
  "message": "string", // human-readable error description
18626
18812
  "errCode": 12345, // optional platform error code
18627
18813
  "httpStatus": 401, // optional HTTP status code
18628
- "nextStep": "relogin" // recommended action keyword
18814
+ "nextStep": "relogin", // recommended action keyword
18815
+ "guidance": "string?", // present when nextStep names an action;
18816
+ // absent for nextStep "none"
18817
+ "transportCode": "ECONNREFUSED" // optional: errno of a transport failure
18818
+ // the error wraps, when it had one
18629
18819
  }
18630
18820
 
18821
+ Classifying a failure (branch on these fields, not on message prose):
18822
+ A consumer reads \`errCode\`, \`httpStatus\`, \`nextStep\` and
18823
+ \`transportCode\` - never the prose in \`message\`, which is written for a
18824
+ human and may change between versions.
18825
+ \`errCode\`, \`httpStatus\`, \`nextStep\` and \`transportCode\` are optional
18826
+ on *every* failure. All four absent means unclassified: nothing structured
18827
+ to act on, so report the payload as-is.
18828
+ \`transportCode\` is diagnostic, not a branch: it names the errno a
18829
+ transport failure left on the error's cause chain when a wrapper copied
18830
+ only the constant message. A bare transport failure does not reach this
18831
+ reporter at all - the runtime prints it with its cause chain and the
18832
+ command exits 1.
18833
+
18834
+ A rejected saved credential (auth.json's apiUrl is off the CLI allowlist)
18835
+ reports the same error shape with no errCode/httpStatus - nothing came from
18836
+ the platform - and nextStep "relogin", because \`msc login\` rewrites the file:
18837
+ {
18838
+ "status": "error",
18839
+ "message": "Untrusted apiUrl in the saved credential: ...",
18840
+ "nextStep": "relogin",
18841
+ "guidance": "Run \`msc login\` to re-authenticate; ..."
18842
+ }
18843
+ With no credential at all the payload stays minimal (status + message only),
18844
+ so an agent can tell "credential file rejected" from "not logged in" without
18845
+ reading the message.
18846
+
18847
+ A chat turn whose stream is open and then ends before the finish event --
18848
+ the connection dropped, or the response was truncated -- is not a failure: it
18849
+ exits 0 with \`status: "incomplete"\` and whatever
18850
+ \`text\`/\`steps\`/\`media\` had arrived. An early end may therefore carry an
18851
+ empty \`text\` and an empty \`media\` array, so do not treat \`incomplete\` as
18852
+ proof that a partial result exists. This applies once the turn has observed
18853
+ any frame at all; a stream request that fails before that -- the host does not
18854
+ resolve, the port is refused, TLS fails, and no event ever arrived -- never
18855
+ reaches \`incomplete\`: it is \`status: "error"\` with exit 1, carrying the
18856
+ errno in \`transportCode\` (and naming it in \`message\`). A
18857
+ platform error (an upstream \`error\` frame, or a non-2xx response) is likewise
18858
+ \`status: "error"\` with exit 1.
18859
+
18631
18860
  Session commands:
18632
18861
  msc sessions --json # list: [{ sessionId, note, ... }]
18633
18862
  msc sessions use <id> --json # { "sessionId": "string" }
@@ -18636,7 +18865,8 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18636
18865
  the \`--new\` hint and leaves the default session untouched.
18637
18866
 
18638
18867
  Recommended next steps:
18639
- relogin Session expired or missing; run \`msc login\`
18868
+ relogin Session expired or missing, or a saved credential was
18869
+ rejected (its apiUrl is off the allowlist); run \`msc login\`
18640
18870
  retry_later Rate limit reached; wait before re-sending
18641
18871
  top_up_balance Insufficient balance; top up your account
18642
18872
  fix_input Invalid parameters or prompt; adjust input
@@ -19549,7 +19779,7 @@ function applyForHost(urlString) {
19549
19779
  return true;
19550
19780
  }
19551
19781
  function applyInsecureTlsIfRequested() {
19552
- const target = readAuthFile()?.apiUrl || getDefaultApiBaseUrl();
19782
+ const target = readAuthFileIfUsable()?.apiUrl || getDefaultApiBaseUrl();
19553
19783
  return applyForHost(target);
19554
19784
  }
19555
19785
  function applyInsecureTlsForUrl(urlString) {
@@ -20422,37 +20652,36 @@ var loginCommand = defineCommand({
20422
20652
  }
20423
20653
  },
20424
20654
  run: async ({ args }) => {
20425
- try {
20426
- if (process.env.CI || process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
20427
- throw new Error(`Browser sign-in needs a graphical session on this machine, so it cannot complete here.
20655
+ if (process.env.CI || process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
20656
+ throw new Error(`Browser sign-in needs a graphical session on this machine, so it cannot complete here.
20428
20657
  ` + "Run `msc login` from a terminal on a machine whose browser is signed in to Media Studio.\n" + "See `msc guide` for the full authentication flow.");
20658
+ }
20659
+ let targetApiUrl;
20660
+ if (args.region) {
20661
+ targetApiUrl = matchRegionUrl(args.region);
20662
+ } else if (process.env.CI || false || !process.stdin.isTTY) {
20663
+ targetApiUrl = SERVERS.default.url;
20664
+ } else {
20665
+ const answer = await select({
20666
+ message: "Select server:",
20667
+ options: getAvailableRegions().map((opt) => ({
20668
+ label: opt.label,
20669
+ value: opt.url
20670
+ })),
20671
+ initialValue: SERVERS.default.url
20672
+ });
20673
+ if (isCancel(answer)) {
20674
+ process.exit(130);
20429
20675
  }
20430
- let targetApiUrl;
20431
- if (args.region) {
20432
- targetApiUrl = matchRegionUrl(args.region);
20433
- } else if (process.env.CI || false || !process.stdin.isTTY) {
20434
- targetApiUrl = SERVERS.default.url;
20435
- } else {
20436
- const answer = await select({
20437
- message: "Select server:",
20438
- options: getAvailableRegions().map((opt) => ({
20439
- label: opt.label,
20440
- value: opt.url
20441
- })),
20442
- initialValue: SERVERS.default.url
20443
- });
20444
- if (isCancel(answer)) {
20445
- process.exit(130);
20446
- }
20447
- targetApiUrl = String(answer);
20448
- }
20449
- applyInsecureTlsForUrl(targetApiUrl);
20450
- const apiUrl = resolveApiBaseUrl(targetApiUrl);
20451
- consola.info("Opening browser for sign-in...");
20452
- if (!process.stdin.isTTY) {
20453
- const regionList = getAvailableRegions().map((r) => ` - ${r.key.padEnd(8)}: ${r.url} (${r.description})`).join(`
20676
+ targetApiUrl = String(answer);
20677
+ }
20678
+ applyInsecureTlsForUrl(targetApiUrl);
20679
+ const apiUrl = resolveApiBaseUrl(targetApiUrl);
20680
+ consola.info("Opening browser for sign-in...");
20681
+ if (!process.stdin.isTTY) {
20682
+ const regionList = getAvailableRegions().map((r) => ` - ${r.key.padEnd(8)}: ${r.url} (${r.description})`).join(`
20454
20683
  `);
20455
- process.stdout.write(`Sign-in needs a human at a browser on this machine:
20684
+ process.stdout.write(`Sign-in needs a human at a browser on this machine:
20456
20685
  ` + ` 1. already signed in to the Media Studio web app there -> completes at once
20457
20686
  ` + ` 2. not signed in -> a browser opens; a human finishes it within 120s
20458
20687
  ` + `
@@ -20462,29 +20691,24 @@ ${regionList}
20462
20691
  ` + `
20463
20692
  ` + `If you are an agent, ask the user to sign in to the web app in their browser first
20464
20693
  ` + "or ask the user to run `msc login` in their own terminal,\n" + "then poll `msc whoami` until it succeeds.\n");
20465
- }
20466
- const result = await runLoopbackLogin({
20467
- apiUrl,
20468
- onUrl: (url) => {
20469
- consola.info(`If your browser does not open automatically, visit:
20694
+ }
20695
+ const result = await runLoopbackLogin({
20696
+ apiUrl,
20697
+ onUrl: (url) => {
20698
+ consola.info(`If your browser does not open automatically, visit:
20470
20699
  ${url}
20471
20700
  ` + "Signed in to the Media Studio web app in that browser already? This completes without a prompt.");
20472
- }
20473
- });
20474
- saveAuthFile({
20475
- token: result.token,
20476
- apiUrl,
20477
- uid: result.uid,
20478
- username: result.username,
20479
- email: result.email,
20480
- updated_at: new Date().toISOString()
20481
- });
20482
- consola.success("Successfully logged in!");
20483
- } catch (err) {
20484
- const message = err instanceof Error ? err.message : String(err);
20485
- consola.error(message);
20486
- process.exit(1);
20487
- }
20701
+ }
20702
+ });
20703
+ saveAuthFile({
20704
+ token: result.token,
20705
+ apiUrl,
20706
+ uid: result.uid,
20707
+ username: result.username,
20708
+ email: result.email,
20709
+ updated_at: new Date().toISOString()
20710
+ });
20711
+ consola.success("Successfully logged in!");
20488
20712
  }
20489
20713
  });
20490
20714
 
@@ -20509,7 +20733,7 @@ function renderSessionsTable(sessions, currentSessionId) {
20509
20733
  id: s.id === currentSessionId ? `${s.id} (current)` : s.id,
20510
20734
  source: s.source,
20511
20735
  lastActive: formatLastActive(s.lastActiveAt),
20512
- note: s.note ? s.note.replace(/\r?\n/g, " ").trim() : "-"
20736
+ note: s.note ? s.note.replace(/\r?\n/g, " ").trim() || CLI_DEFAULT_SESSION_NOTE : CLI_DEFAULT_SESSION_NOTE
20513
20737
  }));
20514
20738
  const headers = {
20515
20739
  id: "ID",
@@ -20582,6 +20806,8 @@ var listSessionsCommand = defineCommand({
20582
20806
  const currentId = readLastSessionId();
20583
20807
  consola.log(renderSessionsTable(response.data, currentId));
20584
20808
  } catch (err) {
20809
+ if (isTransportFailure(err))
20810
+ throw err;
20585
20811
  reportCommandError(err, { json: args.json });
20586
20812
  }
20587
20813
  }
@@ -20620,6 +20846,8 @@ var artifactsCommand = defineCommand({
20620
20846
  }
20621
20847
  consola.log(renderArtifactsTable(response.data));
20622
20848
  } catch (err) {
20849
+ if (isTransportFailure(err))
20850
+ throw err;
20623
20851
  reportCommandError(err, { json: args.json });
20624
20852
  }
20625
20853
  }
@@ -20655,7 +20883,8 @@ var useSessionCommand = defineCommand({
20655
20883
  }
20656
20884
  const result = await provisionSession({
20657
20885
  apiBaseUrl,
20658
- apiKey: resolveApiKey()
20886
+ apiKey: resolveApiKey(),
20887
+ note: CLI_DEFAULT_SESSION_NOTE
20659
20888
  });
20660
20889
  const created = finalizeProvision(result);
20661
20890
  if (args.json) {
@@ -20682,6 +20911,8 @@ var useSessionCommand = defineCommand({
20682
20911
  }
20683
20912
  consola.success(`Switched default session to ${sessionId}`);
20684
20913
  } catch (err) {
20914
+ if (isTransportFailure(err))
20915
+ throw err;
20685
20916
  reportCommandError(err, { json: args.json });
20686
20917
  }
20687
20918
  }
@@ -20758,6 +20989,8 @@ var addSkillCommand = defineCommand({
20758
20989
  }
20759
20990
  consola.success(`Installed ${stripControlChars(skill.command)} (${stripControlChars(skill.name)}).`);
20760
20991
  } catch (err) {
20992
+ if (isTransportFailure(err))
20993
+ throw err;
20761
20994
  reportCommandError(err, { json: args.json });
20762
20995
  }
20763
20996
  }
@@ -20809,6 +21042,8 @@ var rmSkillCommand = defineCommand({
20809
21042
  }
20810
21043
  consola.success(`Skill "${args["skill-command"]}" removed from your library.`);
20811
21044
  } catch (err) {
21045
+ if (isTransportFailure(err))
21046
+ throw err;
20812
21047
  reportCommandError(err, { json: args.json });
20813
21048
  }
20814
21049
  }
@@ -20840,6 +21075,8 @@ function makeSetEnabledCommand(name, enabled, description) {
20840
21075
  }
20841
21076
  consola.success(`${enabled ? "Enabled" : "Disabled"} ${stripControlChars(skill.command)}.`);
20842
21077
  } catch (err) {
21078
+ if (isTransportFailure(err))
21079
+ throw err;
20843
21080
  reportCommandError(err, { json: args.json });
20844
21081
  }
20845
21082
  }
@@ -20932,13 +21169,14 @@ var skillsCommand = defineCommand({
20932
21169
  ].join(`
20933
21170
  `));
20934
21171
  } catch (err) {
21172
+ if (isTransportFailure(err))
21173
+ throw err;
20935
21174
  reportCommandError(err, { json: args.json });
20936
21175
  }
20937
21176
  }
20938
21177
  });
20939
21178
 
20940
21179
  // src/commands/whoami.ts
20941
- var import_picocolors3 = __toESM(require_picocolors(), 1);
20942
21180
  function maskToken(token) {
20943
21181
  return `${token.slice(0, 10)}...${token.slice(-6)}`;
20944
21182
  }
@@ -20948,7 +21186,16 @@ var whoamiCommand = defineCommand({
20948
21186
  description: "Display current authentication status"
20949
21187
  },
20950
21188
  run: async () => {
20951
- const auth = readAuthFile();
21189
+ let auth;
21190
+ try {
21191
+ auth = readAuthFile();
21192
+ } catch (err) {
21193
+ consola.error(err instanceof Error ? err.message : String(err));
21194
+ consola.error(`
21195
+ → ${NEXT_STEP_GUIDANCE.relogin}`);
21196
+ process.exit(1);
21197
+ return;
21198
+ }
20952
21199
  if (!auth) {
20953
21200
  consola.error("Not logged in. Run `msc login` to authenticate - it opens a browser, so a human must complete it.");
20954
21201
  process.exit(1);
@@ -20970,13 +21217,17 @@ var whoamiCommand = defineCommand({
20970
21217
  const updatedUid = data.uid ?? auth.uid;
20971
21218
  const updatedEmail = data.email ?? auth.email;
20972
21219
  if (updatedName !== auth.username || updatedUid !== auth.uid || updatedEmail !== auth.email) {
20973
- saveAuthFile({
20974
- ...auth,
20975
- token: client.apiKey,
20976
- username: updatedName,
20977
- uid: updatedUid,
20978
- email: updatedEmail
20979
- });
21220
+ try {
21221
+ saveAuthFile({
21222
+ ...auth,
21223
+ token: client.apiKey,
21224
+ username: updatedName,
21225
+ uid: updatedUid,
21226
+ email: updatedEmail
21227
+ });
21228
+ } catch (err) {
21229
+ consola.warn(err instanceof Error ? err.message : String(err));
21230
+ }
20980
21231
  }
20981
21232
  consola.success("Authenticated");
20982
21233
  const lines = [
@@ -20984,27 +21235,16 @@ var whoamiCommand = defineCommand({
20984
21235
  updatedUid ? ` UID: ${updatedUid}` : null,
20985
21236
  updatedEmail ? ` Email: ${updatedEmail}` : null,
20986
21237
  data.apiKeyMasked ? ` API Key: ${data.apiKeyMasked}` : null,
20987
- ` Token: ${maskToken(auth.token)}`,
20988
- auth.updated_at ? import_picocolors3.gray(` Saved: ${auth.updated_at}`) : null
21238
+ ` Token: ${maskToken(client.apiKey)}`
20989
21239
  ].filter(Boolean);
20990
21240
  consola.log(`${lines.join(`
20991
21241
  `)}
20992
21242
  `);
20993
- return;
20994
21243
  } catch (err) {
20995
- const isNetworkError = err instanceof TypeError && /fetch failed|failed to fetch/i.test(err.message) || isObject2(err) && "code" in err && isString(err.code) && (err.code === "ECONNREFUSED" || err.code === "ENOTFOUND" || err.code === "ETIMEDOUT" || err.code === "EHOSTUNREACH");
20996
- if (!isNetworkError) {
20997
- reportCommandError(err);
20998
- return;
20999
- }
21244
+ if (isTransportFailure(err))
21245
+ throw err;
21246
+ reportCommandError(err);
21000
21247
  }
21001
- consola.info("Authentication Status (Cached)");
21002
- consola.log(` Token: ${maskToken(auth.token)}
21003
- ` + (auth.username ? ` User: ${auth.username}
21004
- ` : "") + (auth.uid ? ` UID: ${auth.uid}
21005
- ` : "") + (auth.email ? ` Email: ${auth.email}
21006
- ` : "") + (auth.updated_at ? import_picocolors3.gray(` Saved: ${auth.updated_at}
21007
- `) : ""));
21008
21248
  }
21009
21249
  });
21010
21250
 
@@ -21076,6 +21316,10 @@ AGENT QUICKSTART
21076
21316
  Failure exits 1 with a JSON error carrying message, errCode, httpStatus and
21077
21317
  nextStep, which is the action to take:
21078
21318
  relogin | retry_later | top_up_balance | fix_input | retry | none.
21319
+ A rejected saved credential (auth.json's apiUrl is off the allowlist) reports
21320
+ no errCode/httpStatus - nothing came from the platform - and nextStep
21321
+ "relogin", since \`msc login\` rewrites the file. No credential at all reports
21322
+ no nextStep, so the two states differ without parsing the message.
21079
21323
  A turn reports status "success", or "incomplete" if the stream ended early.
21080
21324
  `;
21081
21325
  await runMain({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.1-beta.0",
3
+ "version": "0.1.1-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",