@302ai/media-studio-cli 0.1.1-beta.0 → 0.1.1-beta.2

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 +477 -192
  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.2",
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
  }
@@ -9783,6 +9784,7 @@ var ErrCode = {
9783
9784
  FORBIDDEN: 20301,
9784
9785
  ADMIN_REQUIRED: 20302,
9785
9786
  ACCOUNT_DEACTIVATED: 20303,
9787
+ CAPTCHA_VERIFY_FAILED: 20401,
9786
9788
  INSUFFICIENT_BALANCE: 30001,
9787
9789
  CREDIT_FREEZE_FAILED: 30002,
9788
9790
  FREEZE_RECORD_NOT_FOUND: 30003,
@@ -9928,6 +9930,7 @@ var ERR_NEXT_STEP = {
9928
9930
  20301: "none",
9929
9931
  20302: "none",
9930
9932
  20303: "relogin",
9933
+ 20401: "fix_input",
9931
9934
  30001: "top_up_balance",
9932
9935
  30002: "retry",
9933
9936
  30003: "none",
@@ -10073,11 +10076,14 @@ var APP_ENV_HOSTS = {
10073
10076
  // ../media-studio-contract/src/skills/activation.ts
10074
10077
  var SKILL_ACTIVATION_MARKER = "%%";
10075
10078
  var SKILL_ACTIVATION_RE = /%%(\/[^%\s]+)%%/g;
10079
+ function createSkillActivationRegex() {
10080
+ return new RegExp(SKILL_ACTIVATION_RE.source, SKILL_ACTIVATION_RE.flags);
10081
+ }
10076
10082
  function formatSkillActivation(command) {
10077
10083
  return `${SKILL_ACTIVATION_MARKER}${command}${SKILL_ACTIVATION_MARKER}`;
10078
10084
  }
10079
10085
  function findSkillActivationMatches(text) {
10080
- return Array.from(text.matchAll(SKILL_ACTIVATION_RE)).flatMap((match, index) => {
10086
+ return Array.from(text.matchAll(createSkillActivationRegex())).flatMap((match, index) => {
10081
10087
  const start = match.index;
10082
10088
  const command = match[1];
10083
10089
  if (start === undefined || !command)
@@ -10094,6 +10100,25 @@ function findSkillActivationMatches(text) {
10094
10100
  });
10095
10101
  }
10096
10102
 
10103
+ // ../media-studio-contract/src/sessions/derive-first-turn-note.ts
10104
+ var DEFAULT_FALLBACK_TITLE = "Agent Task";
10105
+ var MAX_NOTE_LENGTH = 64;
10106
+ function deriveFirstTurnNote(rawText, options) {
10107
+ const fallback = options?.fallbackTitle ?? DEFAULT_FALLBACK_TITLE;
10108
+ if (!rawText) {
10109
+ return fallback;
10110
+ }
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();
10112
+ if (!unwrapped) {
10113
+ return fallback;
10114
+ }
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;
10120
+ }
10121
+
10097
10122
  // ../media-studio-contract/src/skills/compose-turn.ts
10098
10123
  function applySkillActivations(text, spans) {
10099
10124
  const ordered = spans.toSorted((a, b) => b.start - a.start);
@@ -16875,9 +16900,7 @@ function validateApiBaseUrl(urlString) {
16875
16900
  }
16876
16901
  var AuthDataSchema = object2({
16877
16902
  token: string4().min(1),
16878
- apiUrl: string4().refine((val) => isAllowedCliApiOrigin(val), {
16879
- message: "Untrusted API URL in auth file"
16880
- }).optional(),
16903
+ apiUrl: string4().optional(),
16881
16904
  uid: string4().optional(),
16882
16905
  username: string4().optional(),
16883
16906
  email: string4().optional(),
@@ -16885,17 +16908,42 @@ var AuthDataSchema = object2({
16885
16908
  });
16886
16909
  var ConfigFileSchema = object2({
16887
16910
  lastSessionId: string4().optional(),
16911
+ lastSessionPendingNote: boolean4().optional(),
16888
16912
  updateCheck: object2({
16889
16913
  checkedAt: number4(),
16890
16914
  beta: string4().optional(),
16891
16915
  latest: string4().optional()
16892
16916
  }).optional()
16893
16917
  });
16918
+
16919
+ class UntrustedCredentialError extends Error {
16920
+ rejectedUrl;
16921
+ constructor(rejectedUrl) {
16922
+ 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.`);
16923
+ this.name = "UntrustedCredentialError";
16924
+ this.rejectedUrl = rejectedUrl;
16925
+ }
16926
+ }
16894
16927
  function readAuthFile() {
16928
+ let raw;
16895
16929
  try {
16896
- const parsed = JSON.parse(readFileSync(getAuthFilePath(), "utf8"));
16897
- const res = AuthDataSchema.safeParse(parsed);
16898
- return res.success ? res.data : null;
16930
+ raw = JSON.parse(readFileSync(getAuthFilePath(), "utf8"));
16931
+ } catch {
16932
+ return null;
16933
+ }
16934
+ const shape = AuthDataSchema.safeParse(raw);
16935
+ if (!shape.success || !shape.data.token.trim()) {
16936
+ return null;
16937
+ }
16938
+ const savedUrl = shape.data.apiUrl?.trim();
16939
+ if (savedUrl && !isAllowedCliApiOrigin(savedUrl)) {
16940
+ throw new UntrustedCredentialError(savedUrl);
16941
+ }
16942
+ return shape.data;
16943
+ }
16944
+ function readAuthFileIfUsable() {
16945
+ try {
16946
+ return readAuthFile();
16899
16947
  } catch {
16900
16948
  return null;
16901
16949
  }
@@ -16939,8 +16987,17 @@ function writeConfigFile(patch) {
16939
16987
  function readLastSessionId() {
16940
16988
  return readConfigFile()?.lastSessionId ?? null;
16941
16989
  }
16942
- function saveLastSessionId(sessionId) {
16943
- writeConfigFile({ lastSessionId: sessionId });
16990
+ function saveLastSessionId(sessionId, options) {
16991
+ writeConfigFile({
16992
+ lastSessionId: sessionId,
16993
+ lastSessionPendingNote: options?.pendingNote ?? false
16994
+ });
16995
+ }
16996
+ function readLastSessionPendingNote() {
16997
+ return readConfigFile()?.lastSessionPendingNote ?? false;
16998
+ }
16999
+ function clearLastSessionPendingNote() {
17000
+ writeConfigFile({ lastSessionPendingNote: false });
16944
17001
  }
16945
17002
  function readUpdateCheck() {
16946
17003
  return readConfigFile()?.updateCheck;
@@ -16949,7 +17006,8 @@ function saveUpdateCheck(check) {
16949
17006
  writeConfigFile({ updateCheck: check });
16950
17007
  }
16951
17008
  function resolveApiKey() {
16952
- const token = readAuthFile()?.token;
17009
+ const auth = readAuthFile();
17010
+ const token = auth?.token;
16953
17011
  if (!token?.trim()) {
16954
17012
  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
17013
  }
@@ -16958,7 +17016,7 @@ function resolveApiKey() {
16958
17016
  function resolveApiBaseUrl(flag) {
16959
17017
  if (flag)
16960
17018
  return validateApiBaseUrl(flag);
16961
- const fromAuth = readAuthFile()?.apiUrl;
17019
+ const fromAuth = readAuthFileIfUsable()?.apiUrl;
16962
17020
  if (typeof fromAuth === "string" && fromAuth.trim()) {
16963
17021
  if (isAllowedCliApiOrigin(fromAuth)) {
16964
17022
  return fromAuth;
@@ -16969,6 +17027,7 @@ function resolveApiBaseUrl(flag) {
16969
17027
 
16970
17028
  // src/client/session-types.ts
16971
17029
  var SessionSourceSchema = _enum2(["web", "cli"]);
17030
+ var CLI_DEFAULT_SESSION_NOTE = "Agent Task";
16972
17031
  var ApiResponseBaseSchema = object2({
16973
17032
  success: boolean4(),
16974
17033
  error: object2({
@@ -17055,6 +17114,14 @@ var ListArtifactsQuerySchema = object2({
17055
17114
  var ListArtifactsResponseSchema = ApiResponseBaseSchema.extend({
17056
17115
  data: ArtifactItemsSchema
17057
17116
  });
17117
+ var SessionDetailSummarySchema = ApiResponseBaseSchema.extend({
17118
+ titleCustomized: boolean4().optional(),
17119
+ title_customized: boolean4().optional(),
17120
+ snapshots: array2(unknown2()).optional()
17121
+ }).transform((data) => ({
17122
+ titleCustomized: data.titleCustomized ?? data.title_customized ?? false,
17123
+ isEmpty: (data.snapshots?.length ?? 0) === 0
17124
+ }));
17058
17125
  var PrewarmSessionReadySchema = object2({
17059
17126
  session_id: string4(),
17060
17127
  note: string4().nullable(),
@@ -17123,15 +17190,22 @@ class ClientSessions {
17123
17190
  const url = `${this.baseUrl}/api/sessions${search ? `?${search}` : ""}`;
17124
17191
  return requestJson(this.customFetch, url, { method: "GET", headers: this.authHeaders() }, ListSessionsResponseSchema, (status) => `Failed to fetch sessions with status ${status}`);
17125
17192
  }
17126
- async exists(sessionId) {
17193
+ async getDetailSummary(sessionId) {
17127
17194
  const params = new URLSearchParams({ sessionId, limit: "1" });
17128
- const [err] = await attemptAsync(() => requestJson(this.customFetch, `${this.baseUrl}/api/sessions/detail?${params.toString()}`, { method: "GET", headers: this.authHeaders() }, unknown2(), (status) => `Failed to verify session ${sessionId} with status ${status}`));
17129
- if (!err)
17130
- return true;
17195
+ const [err, res] = await attemptAsync(() => requestJson(this.customFetch, `${this.baseUrl}/api/sessions/detail?${params.toString()}`, { method: "GET", headers: this.authHeaders() }, SessionDetailSummarySchema, (status) => `Failed to fetch session detail for ${sessionId} with status ${status}`));
17196
+ if (!err && res)
17197
+ return res;
17131
17198
  if (err instanceof ChatUpstreamError && err.status === 404)
17132
- return false;
17199
+ return null;
17133
17200
  throw err;
17134
17201
  }
17202
+ async updateNote(sessionId, note) {
17203
+ await requestJson(this.customFetch, `${this.baseUrl}/api/sessions/note`, {
17204
+ method: "POST",
17205
+ headers: { ...this.authHeaders(), "Content-Type": "application/json" },
17206
+ body: JSON.stringify({ sessionId, note })
17207
+ }, unknown2(), (status) => `Failed to update session note for ${sessionId} with status ${status}`);
17208
+ }
17135
17209
  async artifacts(query) {
17136
17210
  const params = new URLSearchParams({ sessionId: query.sessionId });
17137
17211
  if (query.taskId !== undefined)
@@ -17438,7 +17512,7 @@ class MediaStudioClient {
17438
17512
  const nextKey = rotatedToken.trim();
17439
17513
  this._apiKey = nextKey;
17440
17514
  try {
17441
- const authData = readAuthFile();
17515
+ const authData = readAuthFileIfUsable();
17442
17516
  if (authData) {
17443
17517
  saveAuthFile({
17444
17518
  ...authData,
@@ -17761,10 +17835,50 @@ var NEXT_STEP_GUIDANCE = {
17761
17835
  retry: "The failure looks transient. Retrying the same request may succeed.",
17762
17836
  none: ""
17763
17837
  };
17838
+ var TRANSPORT_CAUSE_DEPTH = 5;
17839
+ var TRANSPORT_MESSAGE = /fetch failed|Unable to connect/i;
17840
+ var ERRNO_SHAPE = /^[A-Z][A-Z0-9_]*$/;
17841
+ function isTerminalControl(codePoint) {
17842
+ 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;
17843
+ }
17844
+ function stripTerminalControls(text) {
17845
+ let out = "";
17846
+ for (const char of text) {
17847
+ if (!isTerminalControl(char.codePointAt(0) ?? -1))
17848
+ out += char;
17849
+ }
17850
+ return out;
17851
+ }
17852
+ function detailOf(node, code) {
17853
+ if (!("message" in node))
17854
+ return "";
17855
+ const { message } = node;
17856
+ if (typeof message !== "string")
17857
+ return "";
17858
+ return stripTerminalControls(message.split(code).join(" ")).replace(/\s+/g, " ").trim().replace(/^[:-]\s*/, "");
17859
+ }
17860
+ function findTransportErrno(err) {
17861
+ let current = err;
17862
+ for (let depth = 0;depth < TRANSPORT_CAUSE_DEPTH; depth++) {
17863
+ if (!(current instanceof Error))
17864
+ return null;
17865
+ const code = "code" in current ? current.code : undefined;
17866
+ const errno = typeof code === "string" && ERRNO_SHAPE.test(code) ? code : null;
17867
+ if (errno)
17868
+ return { code: errno, detail: detailOf(current, errno) };
17869
+ current = current.cause;
17870
+ }
17871
+ return null;
17872
+ }
17873
+ function isTransportFailure(err) {
17874
+ return findTransportErrno(err) !== null || err instanceof Error && TRANSPORT_MESSAGE.test(err.message);
17875
+ }
17764
17876
  function reportCommandError(err, options) {
17765
- const message = err instanceof Error ? err.message : String(err);
17877
+ const plain = err instanceof Error ? err.message : String(err);
17878
+ const transport = findTransportErrno(err);
17879
+ const message = transport && !plain.includes(transport.code) ? `${plain} (${transport.code}${transport.detail ? `: ${transport.detail}` : ""})` : plain;
17766
17880
  const upstream = err instanceof ChatUpstreamError ? err : null;
17767
- const nextStep = upstream?.nextStep ?? (upstream?.status === 401 ? "relogin" : null);
17881
+ const nextStep = upstream?.nextStep ?? (upstream?.status === 401 || err instanceof UntrustedCredentialError ? "relogin" : null);
17768
17882
  const status = upstream?.status !== undefined && upstream.status !== 0 ? upstream.status : null;
17769
17883
  const guidance = nextStep ? NEXT_STEP_GUIDANCE[nextStep] : "";
17770
17884
  if (options?.json) {
@@ -17774,7 +17888,8 @@ function reportCommandError(err, options) {
17774
17888
  ...upstream?.errCode != null ? { errCode: upstream.errCode } : {},
17775
17889
  ...status != null ? { httpStatus: status } : {},
17776
17890
  ...nextStep != null ? { nextStep } : {},
17777
- ...guidance ? { guidance } : {}
17891
+ ...guidance ? { guidance } : {},
17892
+ ...transport ? { transportCode: transport.code } : {}
17778
17893
  };
17779
17894
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
17780
17895
  `);
@@ -18003,15 +18118,25 @@ function isSessionGoneError(err) {
18003
18118
  return true;
18004
18119
  return err.body.includes(ERR_SESSION_EXPIRED);
18005
18120
  }
18121
+
18122
+ class SessionNotFoundError extends Error {
18123
+ sessionId;
18124
+ constructor(sessionId) {
18125
+ super(`Session ${sessionId} does not exist.
18126
+ ` + ' → Create one: `msc sessions use --new`, or `msc chat --new "<prompt>"` to also send a prompt.');
18127
+ this.name = "SessionNotFoundError";
18128
+ this.sessionId = sessionId;
18129
+ }
18130
+ }
18006
18131
  async function ensureSessionExists(options) {
18007
18132
  const client = new MediaStudioClient({
18008
18133
  baseUrl: options.apiBaseUrl,
18009
18134
  apiKey: options.apiKey
18010
18135
  });
18011
- if (await client.sessions.exists(options.sessionId))
18012
- 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.');
18136
+ const detail = await client.sessions.getDetailSummary(options.sessionId);
18137
+ if (detail)
18138
+ return detail;
18139
+ throw new SessionNotFoundError(options.sessionId);
18015
18140
  }
18016
18141
  function finalizeProvision(result, spinner) {
18017
18142
  spinner?.stop();
@@ -18032,6 +18157,7 @@ async function provisionSession(options) {
18032
18157
  const outcome = await client.sessions.prewarm({
18033
18158
  sessionId,
18034
18159
  source: "cli",
18160
+ note: options.note ?? CLI_DEFAULT_SESSION_NOTE,
18035
18161
  onSessionReady: () => saveLastSessionId(sessionId),
18036
18162
  onStageChange: options.onStageChange
18037
18163
  });
@@ -18094,7 +18220,7 @@ async function ensureUsable(client, skill) {
18094
18220
  });
18095
18221
  if (ensured)
18096
18222
  return;
18097
- throw new SkillResolutionError(describeEnableFailure(skill, cause));
18223
+ throw new SkillResolutionError(describeEnableFailure(skill, cause), cause ? { cause } : undefined);
18098
18224
  }
18099
18225
  async function resolvePromptSkills(options) {
18100
18226
  const spans = findSkillRefSpans(options.prompt);
@@ -18167,11 +18293,21 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
18167
18293
  process.stdout.write(`====================
18168
18294
  `);
18169
18295
  }
18296
+ function buildNotePrompt(prompt, attachments) {
18297
+ return prompt.replace(ATTACHMENT_MARKER_REGEX, (marker, url) => {
18298
+ const attachment = attachments.find((a) => a.remoteUrl === url);
18299
+ return attachment ? attachment.name || attachment.remoteUrl : marker;
18300
+ });
18301
+ }
18170
18302
  async function provisionNewSession(options) {
18171
18303
  const onStageChange = options.quiet ? undefined : (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage]);
18304
+ const note = deriveFirstTurnNote(options.prompt, {
18305
+ fallbackTitle: CLI_DEFAULT_SESSION_NOTE
18306
+ });
18172
18307
  const result = await provisionSession({
18173
18308
  apiBaseUrl: options.apiBaseUrl,
18174
18309
  apiKey: options.apiKey,
18310
+ note,
18175
18311
  onStageChange
18176
18312
  });
18177
18313
  return finalizeProvision(result, options.spinner);
@@ -18179,7 +18315,10 @@ async function provisionNewSession(options) {
18179
18315
  async function resolveChatSession(options) {
18180
18316
  if (options.newSession) {
18181
18317
  return {
18182
- sessionId: await provisionNewSession(options),
18318
+ sessionId: await provisionNewSession({
18319
+ ...options,
18320
+ prompt: options.prompt
18321
+ }),
18183
18322
  recreateOnGone: false
18184
18323
  };
18185
18324
  }
@@ -18192,10 +18331,22 @@ async function resolveChatSession(options) {
18192
18331
  return { sessionId: pointer, recreateOnGone: true };
18193
18332
  }
18194
18333
  return {
18195
- sessionId: await provisionNewSession(options),
18334
+ sessionId: await provisionNewSession({
18335
+ ...options,
18336
+ prompt: options.prompt
18337
+ }),
18196
18338
  recreateOnGone: false
18197
18339
  };
18198
18340
  }
18341
+ function describeStreamFailure(err) {
18342
+ if (err instanceof Error && err.name === "AbortError") {
18343
+ return "aborted";
18344
+ }
18345
+ if (err instanceof Error && err.message) {
18346
+ return err.message;
18347
+ }
18348
+ return String(err);
18349
+ }
18199
18350
  async function streamTurn(options) {
18200
18351
  const { args, sessionId, spinner, autoEnabled, attachments } = options;
18201
18352
  if (!args.json) {
@@ -18215,87 +18366,103 @@ async function streamTurn(options) {
18215
18366
  const steps = [];
18216
18367
  const mediaAssets = [];
18217
18368
  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);
18369
+ let emitting = false;
18370
+ let streamFailure;
18371
+ try {
18372
+ for await (const ev of stream) {
18373
+ if (ev.type !== "finish" && ev.type !== "error") {
18374
+ options.progress.emitted = true;
18228
18375
  }
18229
- }).with({ type: "reasoning-delta", delta: z2.string }, ({ delta }) => {
18230
- reasoning += delta;
18231
- if (args["show-reasoning"] && !args.json) {
18376
+ M(ev).with({ type: "text-delta", delta: z2.string }, ({ delta }) => {
18377
+ isThinking = false;
18232
18378
  spinner.stop();
18233
- process.stdout.write(delta);
18234
- } else if (!args.json) {
18235
- if (!isThinking) {
18236
- isThinking = true;
18379
+ fullText += delta;
18380
+ if (!args.json) {
18381
+ process.stdout.write(delta);
18382
+ }
18383
+ }).with({ type: "reasoning-delta", delta: z2.string }, ({ delta }) => {
18384
+ reasoning += delta;
18385
+ if (args["show-reasoning"] && !args.json) {
18237
18386
  spinner.stop();
18238
- if (fullText && !fullText.endsWith(`
18387
+ process.stdout.write(delta);
18388
+ } else if (!args.json) {
18389
+ if (!isThinking) {
18390
+ isThinking = true;
18391
+ spinner.stop();
18392
+ if (fullText && !fullText.endsWith(`
18239
18393
  `)) {
18240
- process.stdout.write(`
18394
+ process.stdout.write(`
18241
18395
  `);
18242
- fullText += `
18396
+ fullText += `
18243
18397
  `;
18244
- }
18245
- process.stdout.write(`[reasoning] Thinking...
18398
+ }
18399
+ process.stdout.write(`[reasoning] Thinking...
18246
18400
  `);
18247
- spinner.start("Thinking...");
18401
+ spinner.start("Thinking...");
18402
+ }
18248
18403
  }
18249
- }
18250
- }).with({ type: "tool-call", toolName: z2.string }, ({ toolName, args: toolArgs }) => {
18251
- isThinking = false;
18252
- spinner.stop();
18253
- if (fullText && !fullText.endsWith(`
18404
+ }).with({ type: "tool-call", toolName: z2.string }, ({ toolName, args: toolArgs }) => {
18405
+ isThinking = false;
18406
+ spinner.stop();
18407
+ if (fullText && !fullText.endsWith(`
18254
18408
  `) && !args.json) {
18255
- process.stdout.write(`
18409
+ process.stdout.write(`
18256
18410
  `);
18257
- fullText += `
18411
+ fullText += `
18258
18412
  `;
18259
- }
18260
- const formatted = formatToolMilestone(toolName, toolArgs);
18261
- steps.push(formatted.milestoneLine);
18262
- if (!args.json) {
18263
- process.stdout.write(`${formatted.milestoneLine}
18413
+ }
18414
+ const formatted = formatToolMilestone(toolName, toolArgs);
18415
+ steps.push(formatted.milestoneLine);
18416
+ if (!args.json) {
18417
+ process.stdout.write(`${formatted.milestoneLine}
18264
18418
  `);
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)}
18419
+ spinner.start(formatted.actionLabel);
18420
+ }
18421
+ }).with({ type: "media-result" }, ({ result }) => {
18422
+ spinner.stop();
18423
+ addOrMergeMediaResult(mediaAssets, result);
18424
+ }).with({ type: "error" }, (ev) => {
18425
+ spinner.stop();
18426
+ 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";
18427
+ throw new ChatUpstreamError(extractUpstreamErrorMessage(raw) || raw, 0, raw);
18428
+ }).with({ type: "finish" }, () => {
18429
+ emitting = true;
18430
+ hasFinished = true;
18431
+ spinner.stop();
18432
+ if (args.json) {
18433
+ const output = {
18434
+ status: "success",
18435
+ sessionId,
18436
+ text: fullText,
18437
+ steps,
18438
+ media: mediaAssets,
18439
+ ...attachments.length > 0 ? { attachments } : {},
18440
+ ...autoEnabled.length > 0 ? { autoEnabled } : {},
18441
+ ...args["show-reasoning"] && reasoning ? { reasoning } : {}
18442
+ };
18443
+ process.stdout.write(`${JSON.stringify(output, null, 2)}
18289
18444
  `);
18290
- return;
18291
- }
18292
- printTerminalMediaAssets(mediaAssets, fullText);
18293
- process.stdout.write(`session: ${sessionId}
18445
+ emitting = false;
18446
+ return;
18447
+ }
18448
+ printTerminalMediaAssets(mediaAssets, fullText);
18449
+ process.stdout.write(`session: ${sessionId}
18294
18450
  `);
18295
- }).with(z2._, () => {}).exhaustive();
18451
+ emitting = false;
18452
+ }).with(z2._, () => {}).exhaustive();
18453
+ }
18454
+ } catch (err) {
18455
+ if (err instanceof ChatUpstreamError)
18456
+ throw err;
18457
+ if (emitting)
18458
+ throw err;
18459
+ if (!hasFinished && !options.progress.emitted)
18460
+ throw err;
18461
+ streamFailure = err;
18296
18462
  }
18297
18463
  if (!hasFinished) {
18298
18464
  spinner.stop();
18465
+ consola.warn(streamFailure === undefined ? "Stream ended early: no finish event" : `Stream ended early: ${describeStreamFailure(streamFailure)}`);
18299
18466
  if (args.json) {
18300
18467
  const output = {
18301
18468
  status: "incomplete",
@@ -18321,30 +18488,48 @@ async function runTurn(options) {
18321
18488
  await streamTurn(options);
18322
18489
  } catch (err) {
18323
18490
  const sessionGone = isSessionGoneError(err);
18324
- if (sessionGone && options.progress.emitted) {
18491
+ const ambiguousGone = err instanceof ChatUpstreamError && err.errCode === ErrCode.INVALID_PARAM_FORMAT;
18492
+ if (!sessionGone && !ambiguousGone) {
18493
+ throw err;
18494
+ }
18495
+ if (options.progress.emitted) {
18325
18496
  throw new Error(`Session ${options.sessionId} failed after this turn had already started.
18326
18497
  ` + " → The partial result above stands; re-run the same prompt to continue in a new session.");
18327
18498
  }
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.');
18499
+ if (!options.recreateOnGone) {
18500
+ if (sessionGone || options.args.session) {
18501
+ throw new SessionNotFoundError(options.sessionId);
18502
+ }
18503
+ throw err;
18331
18504
  }
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;
18505
+ if (ambiguousGone) {
18506
+ let confirmedGone = false;
18507
+ try {
18508
+ await ensureSessionExists({
18509
+ apiBaseUrl: options.apiBaseUrl,
18510
+ apiKey: resolveApiKey(),
18511
+ sessionId: options.sessionId
18512
+ });
18513
+ } catch (checkErr) {
18514
+ confirmedGone = checkErr instanceof SessionNotFoundError;
18515
+ }
18516
+ if (!confirmedGone) {
18517
+ throw err;
18518
+ }
18346
18519
  }
18347
- throw err;
18520
+ consola.warn(`Session ${options.sessionId} is no longer available. Provisioning a new one...`);
18521
+ const sessionId = await provisionNewSession({
18522
+ apiBaseUrl: options.apiBaseUrl,
18523
+ apiKey: resolveApiKey(),
18524
+ spinner: options.spinner,
18525
+ quiet: options.args.json === true,
18526
+ prompt: buildNotePrompt(options.prompt, options.attachments)
18527
+ });
18528
+ await streamTurn({
18529
+ ...options,
18530
+ sessionId,
18531
+ apiKey: resolveApiKey()
18532
+ });
18348
18533
  }
18349
18534
  }
18350
18535
  var chatCommand = defineCommand({
@@ -18384,8 +18569,9 @@ var chatCommand = defineCommand({
18384
18569
  if (args.new && args.session) {
18385
18570
  throw new Error("--new and --session cannot be combined: --session selects an existing session, --new creates one.");
18386
18571
  }
18572
+ let explicitSessionDetail;
18387
18573
  if (args.session) {
18388
- await ensureSessionExists({
18574
+ explicitSessionDetail = await ensureSessionExists({
18389
18575
  apiBaseUrl,
18390
18576
  apiKey: resolveApiKey(),
18391
18577
  sessionId: args.session
@@ -18410,11 +18596,23 @@ var chatCommand = defineCommand({
18410
18596
  const resolved = await resolveChatSession({
18411
18597
  sessionFlag: args.session,
18412
18598
  newSession: args.new,
18599
+ prompt: buildNotePrompt(promptWithAttachments, attachments),
18413
18600
  apiBaseUrl,
18414
18601
  apiKey: resolveApiKey(),
18415
18602
  spinner,
18416
18603
  quiet: args.json === true
18417
18604
  });
18605
+ if (args.session || args.new) {
18606
+ clearLastSessionPendingNote();
18607
+ }
18608
+ const shouldPatchNote = Boolean(explicitSessionDetail && !explicitSessionDetail.titleCustomized && explicitSessionDetail.isEmpty || !args.session && !args.new && readLastSessionPendingNote());
18609
+ if (shouldPatchNote) {
18610
+ const derivedNote = deriveFirstTurnNote(buildNotePrompt(promptWithAttachments, attachments), { fallbackTitle: CLI_DEFAULT_SESSION_NOTE });
18611
+ new MediaStudioClient({
18612
+ baseUrl: apiBaseUrl,
18613
+ apiKey: resolveApiKey()
18614
+ }).sessions.updateNote(resolved.sessionId, derivedNote).then(() => clearLastSessionPendingNote()).catch(() => {});
18615
+ }
18418
18616
  await runTurn({
18419
18617
  args,
18420
18618
  prompt: promptWithAttachments,
@@ -18477,7 +18675,11 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18477
18675
 
18478
18676
  msc whoami
18479
18677
  Displays your current account information (user, email, UID, server URL).
18480
- Exits 0 if authenticated, 1 if unauthenticated or session expired.
18678
+ Exits 0 only when the platform confirmed the session, and the fields it
18679
+ prints are the platform's answer. Exits 1 otherwise - unauthenticated,
18680
+ expired, or the request never reached the platform (DNS, refused, TLS):
18681
+ a saved credential describes a session that was never checked, so it is
18682
+ never printed in place of an answer.
18481
18683
 
18482
18684
  msc logout
18483
18685
  Removes saved credentials on this machine.
@@ -18543,7 +18745,7 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18543
18745
  msc skills add <slug> # add a skill from the plaza to your library
18544
18746
 
18545
18747
  Referencing Skills in Prompts:
18546
- Lead the prompt with your skill reference — the message must start with it
18748
+ Lead the prompt with your skill reference - the message must start with it
18547
18749
  for the skill to activate:
18548
18750
  Slash form: msc chat "/<name> your prompt here"
18549
18751
  Marker form: msc chat "%%/<name>%% your prompt here"
@@ -18619,14 +18821,81 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18619
18821
  Extracting the result URL (e.g. with jq):
18620
18822
  url=$(msc chat "a red balloon" --json | jq -r '.media[0].resultUrl')
18621
18823
 
18824
+ Exit codes: 0 = the turn ran to completion OR its stream ended early; 1 =
18825
+ \`status: "error"\`, a usage error, or any other failure. Only what the parser
18826
+ itself rejects (unknown flag, missing \`<prompt>\`) exits 1 before the command
18827
+ runs with an empty stdout; argument conflicts checked inside the command --
18828
+ \`--new\` with \`--session\`, \`sessions use --new <id>\` -- do print the error
18829
+ payload below. Either way, check the exit code first, then branch on
18830
+ \`.status\`.
18831
+ Exit code 1 does not imply a payload. A bare transport failure -- the host
18832
+ does not resolve, the port is refused, TLS fails -- is printed by the runtime
18833
+ with its \`cause\` chain instead, so \`--json\` stdout stays empty there: to
18834
+ tell "could not reach the platform" from "the platform answered with a
18835
+ failure", read the exit code, then check whether stdout has a payload at all.
18836
+ \`"success" | "incomplete" | "error"\` are \`chat\`-envelope values: \`chat\` is
18837
+ the only command whose *success* payload carries a \`status\`, and every
18838
+ command that supports \`--json\` puts \`"status": "error"\` on its failure
18839
+ payload. \`login\`, \`logout\` and \`whoami\` define no \`--json\` and emit no
18840
+ payload at all -- their failures are prose on stderr with exit 1. Do not
18841
+ reuse that vocabulary elsewhere -- \`sessions --json\` items carry
18842
+ \`status: { chat, task }\` (a boolean pair) and \`sessions artifacts <id> --json\`
18843
+ items carry the task lifecycle
18844
+ (\`pending | running | processing | completed | failed | cancelled\`).
18845
+ Human stdout carries no status marker -- an early end only warns on
18846
+ stderr -- so never parse it in scripts.
18847
+
18622
18848
  Error schema on failure (exit code 1):
18623
18849
  {
18624
18850
  "status": "error",
18625
18851
  "message": "string", // human-readable error description
18626
18852
  "errCode": 12345, // optional platform error code
18627
18853
  "httpStatus": 401, // optional HTTP status code
18628
- "nextStep": "relogin" // recommended action keyword
18854
+ "nextStep": "relogin", // recommended action keyword
18855
+ "guidance": "string?", // present when nextStep names an action;
18856
+ // absent for nextStep "none"
18857
+ "transportCode": "ECONNREFUSED" // optional: errno of a transport failure
18858
+ // the error wraps, when it had one
18859
+ }
18860
+
18861
+ Classifying a failure (branch on these fields, not on message prose):
18862
+ A consumer reads \`errCode\`, \`httpStatus\`, \`nextStep\` and
18863
+ \`transportCode\` - never the prose in \`message\`, which is written for a
18864
+ human and may change between versions.
18865
+ \`errCode\`, \`httpStatus\`, \`nextStep\` and \`transportCode\` are optional
18866
+ on *every* failure. All four absent means unclassified: nothing structured
18867
+ to act on, so report the payload as-is.
18868
+ \`transportCode\` is diagnostic, not a branch: it names the errno a
18869
+ transport failure left on the error's cause chain when a wrapper copied
18870
+ only the constant message. A bare transport failure does not reach this
18871
+ reporter at all - the runtime prints it with its cause chain and the
18872
+ command exits 1.
18873
+
18874
+ A rejected saved credential (auth.json's apiUrl is off the CLI allowlist)
18875
+ reports the same error shape with no errCode/httpStatus - nothing came from
18876
+ the platform - and nextStep "relogin", because \`msc login\` rewrites the file:
18877
+ {
18878
+ "status": "error",
18879
+ "message": "Untrusted apiUrl in the saved credential: ...",
18880
+ "nextStep": "relogin",
18881
+ "guidance": "Run \`msc login\` to re-authenticate; ..."
18629
18882
  }
18883
+ With no credential at all the payload stays minimal (status + message only),
18884
+ so an agent can tell "credential file rejected" from "not logged in" without
18885
+ reading the message.
18886
+
18887
+ A chat turn whose stream is open and then ends before the finish event --
18888
+ the connection dropped, or the response was truncated -- is not a failure: it
18889
+ exits 0 with \`status: "incomplete"\` and whatever
18890
+ \`text\`/\`steps\`/\`media\` had arrived. An early end may therefore carry an
18891
+ empty \`text\` and an empty \`media\` array, so do not treat \`incomplete\` as
18892
+ proof that a partial result exists. This applies once the turn has observed
18893
+ any frame at all; a stream request that fails before that -- the host does not
18894
+ resolve, the port is refused, TLS fails, and no event ever arrived -- never
18895
+ reaches \`incomplete\`: it is \`status: "error"\` with exit 1, carrying the
18896
+ errno in \`transportCode\` (and naming it in \`message\`). A
18897
+ platform error (an upstream \`error\` frame, or a non-2xx response) is likewise
18898
+ \`status: "error"\` with exit 1.
18630
18899
 
18631
18900
  Session commands:
18632
18901
  msc sessions --json # list: [{ sessionId, note, ... }]
@@ -18636,7 +18905,8 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18636
18905
  the \`--new\` hint and leaves the default session untouched.
18637
18906
 
18638
18907
  Recommended next steps:
18639
- relogin Session expired or missing; run \`msc login\`
18908
+ relogin Session expired or missing, or a saved credential was
18909
+ rejected (its apiUrl is off the allowlist); run \`msc login\`
18640
18910
  retry_later Rate limit reached; wait before re-sending
18641
18911
  top_up_balance Insufficient balance; top up your account
18642
18912
  fix_input Invalid parameters or prompt; adjust input
@@ -19549,7 +19819,7 @@ function applyForHost(urlString) {
19549
19819
  return true;
19550
19820
  }
19551
19821
  function applyInsecureTlsIfRequested() {
19552
- const target = readAuthFile()?.apiUrl || getDefaultApiBaseUrl();
19822
+ const target = readAuthFileIfUsable()?.apiUrl || getDefaultApiBaseUrl();
19553
19823
  return applyForHost(target);
19554
19824
  }
19555
19825
  function applyInsecureTlsForUrl(urlString) {
@@ -20422,37 +20692,36 @@ var loginCommand = defineCommand({
20422
20692
  }
20423
20693
  },
20424
20694
  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.
20695
+ if (process.env.CI || process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
20696
+ throw new Error(`Browser sign-in needs a graphical session on this machine, so it cannot complete here.
20428
20697
  ` + "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.");
20698
+ }
20699
+ let targetApiUrl;
20700
+ if (args.region) {
20701
+ targetApiUrl = matchRegionUrl(args.region);
20702
+ } else if (process.env.CI || false || !process.stdin.isTTY) {
20703
+ targetApiUrl = SERVERS.default.url;
20704
+ } else {
20705
+ const answer = await select({
20706
+ message: "Select server:",
20707
+ options: getAvailableRegions().map((opt) => ({
20708
+ label: opt.label,
20709
+ value: opt.url
20710
+ })),
20711
+ initialValue: SERVERS.default.url
20712
+ });
20713
+ if (isCancel(answer)) {
20714
+ process.exit(130);
20429
20715
  }
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(`
20716
+ targetApiUrl = String(answer);
20717
+ }
20718
+ applyInsecureTlsForUrl(targetApiUrl);
20719
+ const apiUrl = resolveApiBaseUrl(targetApiUrl);
20720
+ consola.info("Opening browser for sign-in...");
20721
+ if (!process.stdin.isTTY) {
20722
+ const regionList = getAvailableRegions().map((r) => ` - ${r.key.padEnd(8)}: ${r.url} (${r.description})`).join(`
20454
20723
  `);
20455
- process.stdout.write(`Sign-in needs a human at a browser on this machine:
20724
+ process.stdout.write(`Sign-in needs a human at a browser on this machine:
20456
20725
  ` + ` 1. already signed in to the Media Studio web app there -> completes at once
20457
20726
  ` + ` 2. not signed in -> a browser opens; a human finishes it within 120s
20458
20727
  ` + `
@@ -20462,29 +20731,24 @@ ${regionList}
20462
20731
  ` + `
20463
20732
  ` + `If you are an agent, ask the user to sign in to the web app in their browser first
20464
20733
  ` + "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:
20734
+ }
20735
+ const result = await runLoopbackLogin({
20736
+ apiUrl,
20737
+ onUrl: (url) => {
20738
+ consola.info(`If your browser does not open automatically, visit:
20470
20739
  ${url}
20471
20740
  ` + "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
- }
20741
+ }
20742
+ });
20743
+ saveAuthFile({
20744
+ token: result.token,
20745
+ apiUrl,
20746
+ uid: result.uid,
20747
+ username: result.username,
20748
+ email: result.email,
20749
+ updated_at: new Date().toISOString()
20750
+ });
20751
+ consola.success("Successfully logged in!");
20488
20752
  }
20489
20753
  });
20490
20754
 
@@ -20509,7 +20773,7 @@ function renderSessionsTable(sessions, currentSessionId) {
20509
20773
  id: s.id === currentSessionId ? `${s.id} (current)` : s.id,
20510
20774
  source: s.source,
20511
20775
  lastActive: formatLastActive(s.lastActiveAt),
20512
- note: s.note ? s.note.replace(/\r?\n/g, " ").trim() : "-"
20776
+ note: s.note ? s.note.replace(/\r?\n/g, " ").trim() || CLI_DEFAULT_SESSION_NOTE : CLI_DEFAULT_SESSION_NOTE
20513
20777
  }));
20514
20778
  const headers = {
20515
20779
  id: "ID",
@@ -20582,6 +20846,8 @@ var listSessionsCommand = defineCommand({
20582
20846
  const currentId = readLastSessionId();
20583
20847
  consola.log(renderSessionsTable(response.data, currentId));
20584
20848
  } catch (err) {
20849
+ if (isTransportFailure(err))
20850
+ throw err;
20585
20851
  reportCommandError(err, { json: args.json });
20586
20852
  }
20587
20853
  }
@@ -20620,6 +20886,8 @@ var artifactsCommand = defineCommand({
20620
20886
  }
20621
20887
  consola.log(renderArtifactsTable(response.data));
20622
20888
  } catch (err) {
20889
+ if (isTransportFailure(err))
20890
+ throw err;
20623
20891
  reportCommandError(err, { json: args.json });
20624
20892
  }
20625
20893
  }
@@ -20655,9 +20923,11 @@ var useSessionCommand = defineCommand({
20655
20923
  }
20656
20924
  const result = await provisionSession({
20657
20925
  apiBaseUrl,
20658
- apiKey: resolveApiKey()
20926
+ apiKey: resolveApiKey(),
20927
+ note: CLI_DEFAULT_SESSION_NOTE
20659
20928
  });
20660
20929
  const created = finalizeProvision(result);
20930
+ saveLastSessionId(created, { pendingNote: true });
20661
20931
  if (args.json) {
20662
20932
  process.stdout.write(`${JSON.stringify({ sessionId: created })}
20663
20933
  `);
@@ -20682,6 +20952,8 @@ var useSessionCommand = defineCommand({
20682
20952
  }
20683
20953
  consola.success(`Switched default session to ${sessionId}`);
20684
20954
  } catch (err) {
20955
+ if (isTransportFailure(err))
20956
+ throw err;
20685
20957
  reportCommandError(err, { json: args.json });
20686
20958
  }
20687
20959
  }
@@ -20758,6 +21030,8 @@ var addSkillCommand = defineCommand({
20758
21030
  }
20759
21031
  consola.success(`Installed ${stripControlChars(skill.command)} (${stripControlChars(skill.name)}).`);
20760
21032
  } catch (err) {
21033
+ if (isTransportFailure(err))
21034
+ throw err;
20761
21035
  reportCommandError(err, { json: args.json });
20762
21036
  }
20763
21037
  }
@@ -20809,6 +21083,8 @@ var rmSkillCommand = defineCommand({
20809
21083
  }
20810
21084
  consola.success(`Skill "${args["skill-command"]}" removed from your library.`);
20811
21085
  } catch (err) {
21086
+ if (isTransportFailure(err))
21087
+ throw err;
20812
21088
  reportCommandError(err, { json: args.json });
20813
21089
  }
20814
21090
  }
@@ -20840,6 +21116,8 @@ function makeSetEnabledCommand(name, enabled, description) {
20840
21116
  }
20841
21117
  consola.success(`${enabled ? "Enabled" : "Disabled"} ${stripControlChars(skill.command)}.`);
20842
21118
  } catch (err) {
21119
+ if (isTransportFailure(err))
21120
+ throw err;
20843
21121
  reportCommandError(err, { json: args.json });
20844
21122
  }
20845
21123
  }
@@ -20932,13 +21210,14 @@ var skillsCommand = defineCommand({
20932
21210
  ].join(`
20933
21211
  `));
20934
21212
  } catch (err) {
21213
+ if (isTransportFailure(err))
21214
+ throw err;
20935
21215
  reportCommandError(err, { json: args.json });
20936
21216
  }
20937
21217
  }
20938
21218
  });
20939
21219
 
20940
21220
  // src/commands/whoami.ts
20941
- var import_picocolors3 = __toESM(require_picocolors(), 1);
20942
21221
  function maskToken(token) {
20943
21222
  return `${token.slice(0, 10)}...${token.slice(-6)}`;
20944
21223
  }
@@ -20948,7 +21227,16 @@ var whoamiCommand = defineCommand({
20948
21227
  description: "Display current authentication status"
20949
21228
  },
20950
21229
  run: async () => {
20951
- const auth = readAuthFile();
21230
+ let auth;
21231
+ try {
21232
+ auth = readAuthFile();
21233
+ } catch (err) {
21234
+ consola.error(err instanceof Error ? err.message : String(err));
21235
+ consola.error(`
21236
+ → ${NEXT_STEP_GUIDANCE.relogin}`);
21237
+ process.exit(1);
21238
+ return;
21239
+ }
20952
21240
  if (!auth) {
20953
21241
  consola.error("Not logged in. Run `msc login` to authenticate - it opens a browser, so a human must complete it.");
20954
21242
  process.exit(1);
@@ -20970,13 +21258,17 @@ var whoamiCommand = defineCommand({
20970
21258
  const updatedUid = data.uid ?? auth.uid;
20971
21259
  const updatedEmail = data.email ?? auth.email;
20972
21260
  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
- });
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
+ }
20980
21272
  }
20981
21273
  consola.success("Authenticated");
20982
21274
  const lines = [
@@ -20984,27 +21276,16 @@ var whoamiCommand = defineCommand({
20984
21276
  updatedUid ? ` UID: ${updatedUid}` : null,
20985
21277
  updatedEmail ? ` Email: ${updatedEmail}` : null,
20986
21278
  data.apiKeyMasked ? ` API Key: ${data.apiKeyMasked}` : null,
20987
- ` Token: ${maskToken(auth.token)}`,
20988
- auth.updated_at ? import_picocolors3.gray(` Saved: ${auth.updated_at}`) : null
21279
+ ` Token: ${maskToken(client.apiKey)}`
20989
21280
  ].filter(Boolean);
20990
21281
  consola.log(`${lines.join(`
20991
21282
  `)}
20992
21283
  `);
20993
- return;
20994
21284
  } 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
- }
21285
+ if (isTransportFailure(err))
21286
+ throw err;
21287
+ reportCommandError(err);
21000
21288
  }
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
21289
  }
21009
21290
  });
21010
21291
 
@@ -21076,6 +21357,10 @@ AGENT QUICKSTART
21076
21357
  Failure exits 1 with a JSON error carrying message, errCode, httpStatus and
21077
21358
  nextStep, which is the action to take:
21078
21359
  relogin | retry_later | top_up_balance | fix_input | retry | none.
21360
+ A rejected saved credential (auth.json's apiUrl is off the allowlist) reports
21361
+ no errCode/httpStatus - nothing came from the platform - and nextStep
21362
+ "relogin", since \`msc login\` rewrites the file. No credential at all reports
21363
+ no nextStep, so the two states differ without parsing the message.
21079
21364
  A turn reports status "success", or "incomplete" if the stream ended early.
21080
21365
  `;
21081
21366
  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.2",
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",