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

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 +25 -18
  2. package/dist/index.js +368 -55
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,16 +10,9 @@ npm install -g @302ai/media-studio-cli
10
10
  # requires Node.js >= 22
11
11
  ```
12
12
 
13
- Newer, less tested prerelease builds:
14
-
15
- ```bash
16
- npm install -g @302ai/media-studio-cli@beta
17
- ```
18
-
19
- > **No stable release exists yet.** `latest` points at a prerelease, so both
20
- > commands above currently install the same build. Staging- and Local-server
21
- > visibility is gated on the package *version* carrying a prerelease tag — not
22
- > on the dist-tag — so both are on for either install: **Staging** and
13
+ > **No stable release exists yet.** `latest` currently points at a prerelease.
14
+ > Staging- and Local-server visibility is gated on the package *version*
15
+ > carrying a prerelease tag — not on the dist-tag — so **Staging** and
23
16
  > **Local** appear in `msc login` and in `--region` whichever way you install.
24
17
  > A future stable release will hide them from both channels.
25
18
 
@@ -49,10 +42,12 @@ name:
49
42
  ```bash
50
43
  msc chat "画一只猫" -r # -r/--show-reasoning = 展示模型推理过程
51
44
  msc chat "hi" --json # machine-readable output
52
- msc chat "hi" --session <id> # 切换到指定会话并设为默认
45
+ msc chat "hi" --session <id> # 切换到已有会话(先校验存在)并设为默认
46
+ msc chat "hi" --new # 新建会话并设为默认(id 不复用)
53
47
  msc sessions # 列出所有会话(标记当前默认 `(current)`)
54
48
  msc sessions --json # 机器可读的原始 JSON 数组
55
- msc sessions use <id> # 纯本地切换默认会话(零请求、不校验)
49
+ msc sessions use <id> # 校验存在后切换默认会话
50
+ msc sessions use --new # 新建会话并设为默认
56
51
  msc skills # 列出技能库(--plaza 浏览技能广场)
57
52
  msc skills add <slug> # 从广场安装技能(如 brainstorming,见 --plaza --json 的 slug)
58
53
  msc skills rm <command> # 从技能库移除(-y/--yes 跳过确认)
@@ -86,10 +81,20 @@ whose browser can reach the loopback callback.
86
81
  loses a live session.
87
82
  - **Later chats** continue that session — upstream remembers the
88
83
  conversation by session id, so no history is resent.
89
- - If the session was deleted elsewhere, the CLI transparently provisions a
90
- fresh one and continues — you are never stranded.
91
- - `--session <id>` switches to a specific session (including web-created
92
- ones) and makes it the default, without provisioning.
84
+ - If the current session is gone **before a turn produces any output**, the CLI
85
+ transparently provisions a fresh one and re-runs the turn — a first-turn
86
+ failure never strands you.
87
+ - Once a turn has started producing output, a session failure is reported
88
+ instead: the partial result stands, nothing is re-run, and no replacement
89
+ session is created (a retry would replay output and re-dispatch tool calls or
90
+ generation tasks the first attempt may already have started).
91
+ - `--session <id>` switches to an existing session (including web-created
92
+ ones) and makes it the default. The id is verified upstream first: a missing
93
+ id fails with a hint, it never provisions a different session behind your
94
+ back.
95
+ - `--new` creates a fresh session with a newly generated id and makes it the
96
+ default for later runs. Ids are never reused or adopted: the CLI either uses
97
+ the id it generated or reports the refusal.
93
98
 
94
99
  Manual proof of continuity:
95
100
 
@@ -324,8 +329,10 @@ msc chat "刚才的数字是几?" # answers 42
324
329
 
325
330
  # Session management verification:
326
331
  msc sessions # shows session list with (current) marker
327
- msc chat "hi" --session <id> # switch to specific session
328
- msc sessions use <id> # switch default pointer locally
332
+ msc chat "hi" --session <id> # switch to an existing session
333
+ msc chat "hi" --new # start a new session
334
+ msc sessions use <id> # switch default pointer to an existing session
335
+ msc sessions use --new # create a new session and switch to it
329
336
  ```
330
337
 
331
338
  Without the local server, point at production with
package/dist/index.js CHANGED
@@ -4284,7 +4284,7 @@ var consola = createConsola2();
4284
4284
  // package.json
4285
4285
  var package_default = {
4286
4286
  name: "@302ai/media-studio-cli",
4287
- version: "0.1.0-beta.7",
4287
+ version: "0.1.1-beta.0",
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",
@@ -9749,6 +9749,151 @@ function isString(value) {
9749
9749
  return typeof value === "string";
9750
9750
  }
9751
9751
  // ../media-studio-contract/src/err-codes.ts
9752
+ var ErrCode = {
9753
+ MISSING_REQUIRED_PARAM: 10001,
9754
+ INVALID_PARAM_FORMAT: 10002,
9755
+ RESOURCE_NOT_FOUND: 10003,
9756
+ METHOD_NOT_ALLOWED: 10004,
9757
+ RATE_LIMITED: 10005,
9758
+ PAYLOAD_TOO_LARGE: 10006,
9759
+ UNSUPPORTED_MEDIA_TYPE: 10007,
9760
+ INTERNAL_ERROR: 10500,
9761
+ DEPENDENCY_UNAVAILABLE: 10501,
9762
+ DATABASE_ERROR: 10502,
9763
+ SERVICE_UNAVAILABLE: 10503,
9764
+ CACHE_UNAVAILABLE: 10504,
9765
+ UNAUTHORIZED: 20001,
9766
+ TOKEN_MISSING: 20002,
9767
+ TOKEN_INVALID: 20003,
9768
+ TOKEN_EXPIRED: 20004,
9769
+ TOKEN_REVOKED: 20005,
9770
+ REFRESH_TOKEN_INVALID: 20006,
9771
+ REFRESH_TOKEN_ROTATED: 20007,
9772
+ VERIFICATION_CODE_INVALID: 20101,
9773
+ VERIFICATION_CODE_EXPIRED: 20102,
9774
+ CODE_SEND_RATE_LIMITED: 20103,
9775
+ CODE_SEND_DAILY_LIMIT: 20104,
9776
+ EMAIL_SEND_FAILED: 20105,
9777
+ EMAIL_ALREADY_REGISTERED: 20106,
9778
+ EMAIL_NOT_REGISTERED: 20107,
9779
+ OAUTH_STATE_INVALID: 20201,
9780
+ OAUTH_CALLBACK_FAILED: 20202,
9781
+ OAUTH_ACCOUNT_ALREADY_BOUND: 20203,
9782
+ SSO_INFO_CONFLICT: 20204,
9783
+ FORBIDDEN: 20301,
9784
+ ADMIN_REQUIRED: 20302,
9785
+ ACCOUNT_DEACTIVATED: 20303,
9786
+ INSUFFICIENT_BALANCE: 30001,
9787
+ CREDIT_FREEZE_FAILED: 30002,
9788
+ FREEZE_RECORD_NOT_FOUND: 30003,
9789
+ FREEZE_ALREADY_SETTLED: 30004,
9790
+ CREDIT_SETTLE_FAILED: 30005,
9791
+ SETTLE_EXCEEDS_FREEZE: 30006,
9792
+ UNFREEZE_FAILED: 30007,
9793
+ CREDIT_BATCH_EXPIRED: 30008,
9794
+ CREDIT_BATCH_NOT_FOUND: 30009,
9795
+ CONCURRENT_MODIFICATION: 30010,
9796
+ PRICING_RULE_NOT_FOUND: 30101,
9797
+ MODEL_PRICE_MISSING: 30102,
9798
+ USAGE_RECORD_DUPLICATED: 30103,
9799
+ BALANCE_CACHE_REBUILD_FAILED: 30104,
9800
+ LEDGER_WRITE_FAILED: 30105,
9801
+ UPSTREAM_ERROR: 40001,
9802
+ UPSTREAM_TIMEOUT: 40002,
9803
+ UPSTREAM_RATE_LIMITED: 40003,
9804
+ UPSTREAM_UNAVAILABLE: 40004,
9805
+ UPSTREAM_AUTH_FAILED: 40005,
9806
+ UPSTREAM_DATA_INVALID: 40006,
9807
+ MODEL_UNSUPPORTED: 40101,
9808
+ MODEL_LABEL_INVALID: 40102,
9809
+ MODEL_PRICE_NOT_CONFIGURED: 40103,
9810
+ MODEL_LABEL_DEPRECATED: 40104,
9811
+ SANDBOX_TOKEN_INVALID: 40201,
9812
+ SANDBOX_TOKEN_EXPIRED: 40202,
9813
+ OSS_TRANSFER_FAILED: 40301,
9814
+ OSS_URL_PRESIGN_FAILED: 40302,
9815
+ PLAN_NOT_FOUND: 50001,
9816
+ PLAN_UNAVAILABLE: 50002,
9817
+ PLAN_PRICE_MISSING: 50003,
9818
+ ORDER_NOT_FOUND: 50101,
9819
+ ORDER_ALREADY_PAID: 50102,
9820
+ ORDER_EXPIRED: 50103,
9821
+ ORDER_AMOUNT_MISMATCH: 50104,
9822
+ ORDER_STATUS_NOT_ALLOWED: 50105,
9823
+ ORDER_CANCEL_FAILED: 50106,
9824
+ ORDER_PENDING_EXISTS: 50107,
9825
+ PAYMENT_FAILED: 50201,
9826
+ PAYMENT_REJECTED: 50202,
9827
+ PAYMENT_METHOD_NOT_FOUND: 50203,
9828
+ WEBHOOK_SIGNATURE_INVALID: 50204,
9829
+ WEBHOOK_EVENT_UNKNOWN: 50205,
9830
+ STRIPE_API_ERROR: 50206,
9831
+ SUBSCRIPTION_NOT_FOUND: 50301,
9832
+ SUBSCRIPTION_STATUS_NOT_ALLOWED: 50302,
9833
+ SUBSCRIPTION_CANCELED: 50303,
9834
+ UPGRADE_PRORATION_FAILED: 50304,
9835
+ DOWNGRADE_NOT_ALLOWED: 50305,
9836
+ AUTO_RENEW_FAILED: 50306,
9837
+ SAVED_CARD_INVALID: 50307,
9838
+ REFUND_NOT_ELIGIBLE: 50401,
9839
+ REFUND_WINDOW_EXPIRED: 50402,
9840
+ REFUND_THRESHOLD_EXCEEDED: 50403,
9841
+ REFUND_FAILED: 50404,
9842
+ TOPUP_SUBSCRIPTION_ONLY: 50501,
9843
+ TOPUP_FROZEN: 50502,
9844
+ TOPUP_NOT_FOUND: 50503,
9845
+ SESSION_NOT_FOUND: 60001,
9846
+ SESSION_ALREADY_EXISTS: 60002,
9847
+ SESSION_STATUS_NOT_ALLOWED: 60003,
9848
+ CHAT_SESSION_EXPIRED: 60004,
9849
+ CHAT_CONTEXT_MISSING: 60005,
9850
+ PROMPT_SUGGESTION_FAILED: 60006,
9851
+ TITLE_GENERATION_FAILED: 60007,
9852
+ CHAT_TASK_NOT_FOUND: 60101,
9853
+ CHAT_TASK_IN_PROGRESS: 60102,
9854
+ CHAT_TASK_CANCELED: 60103,
9855
+ CHAT_TASK_STATUS_NOT_ALLOWED: 60104,
9856
+ SANDBOX_CREATE_FAILED: 60201,
9857
+ SANDBOX_WARMUP_FAILED: 60202,
9858
+ SANDBOX_TIMEOUT: 60203,
9859
+ SANDBOX_UNAVAILABLE: 60204,
9860
+ SANDBOX_COMMAND_FAILED: 60205,
9861
+ SKILL_NOT_FOUND: 60301,
9862
+ SKILL_FILE_INVALID: 60302,
9863
+ SKILL_BUILTIN_IMMUTABLE: 60303,
9864
+ SKILL_UPLOAD_INVALID: 60304,
9865
+ ASSET_NOT_FOUND: 70001,
9866
+ ASSET_GROUP_NOT_FOUND: 70002,
9867
+ ASSET_GROUP_NAME_EXISTS: 70003,
9868
+ ASSET_UPLOAD_FAILED: 70004,
9869
+ ASSET_TOO_LARGE: 70005,
9870
+ ASSET_TYPE_NOT_ALLOWED: 70006,
9871
+ ASSET_GROUP_INVALID: 70007,
9872
+ OSS_UPLOAD_FAILED: 70101,
9873
+ OSS_DOWNLOAD_FAILED: 70102,
9874
+ COVER_UPLOAD_FAILED: 70103,
9875
+ OSS_DELETE_FAILED: 70104,
9876
+ FILE_NOT_FOUND: 70201,
9877
+ FILE_READ_FAILED: 70202,
9878
+ FILE_WRITE_FAILED: 70203,
9879
+ FILE_FORMAT_INVALID: 70204,
9880
+ FILE_FORMAT_TEXT_FILE_ONLY: 70205,
9881
+ FILE_OPERATION_INVALID: 70206,
9882
+ FILE_TARGET_PATH_REQUIRED: 70207,
9883
+ FILE_PATH_COUNT_MISMATCH: 70208,
9884
+ FILE_RENAME_BATCH_UNSUPPORTED: 70209,
9885
+ ADMIN_NOT_LOGGED_IN: 90001,
9886
+ ADMIN_2FA_REQUIRED: 90002,
9887
+ ADMIN_2FA_CODE_INVALID: 90003,
9888
+ ADMIN_2FA_RATE_LIMITED: 90004,
9889
+ ADMIN_FORBIDDEN: 90005,
9890
+ ADMIN_NOT_FOUND: 90006,
9891
+ RISK_CONTROL_TRIGGERED: 90101,
9892
+ RISK_CONTROL_RATE_LIMITED: 90102,
9893
+ RECONCILIATION_MISMATCH: 90103,
9894
+ RECONCILIATION_TASK_FAILED: 90104,
9895
+ AUDIT_LOG_WRITE_FAILED: 90105
9896
+ };
9752
9897
  var ERR_NEXT_STEP = {
9753
9898
  10001: "fix_input",
9754
9899
  10002: "fix_input",
@@ -9915,6 +10060,15 @@ var MEDIA_STUDIO_HOSTS = [
9915
10060
  new URL(MEDIA_STUDIO_URLS.cn).hostname,
9916
10061
  new URL(MEDIA_STUDIO_URLS.staging).hostname
9917
10062
  ];
10063
+ var APP_ENVS = ["development", "staging", "production"];
10064
+ var APP_ENV_SCHEMA = _enum(APP_ENVS);
10065
+ var APP_ENV_HOSTS = {
10066
+ staging: [new URL(MEDIA_STUDIO_URLS.staging).hostname],
10067
+ production: [
10068
+ new URL(MEDIA_STUDIO_URLS.default).hostname,
10069
+ new URL(MEDIA_STUDIO_URLS.cn).hostname
10070
+ ]
10071
+ };
9918
10072
 
9919
10073
  // ../media-studio-contract/src/skills/activation.ts
9920
10074
  var SKILL_ACTIVATION_MARKER = "%%";
@@ -10018,6 +10172,15 @@ function attempt2(func) {
10018
10172
  return [error, null];
10019
10173
  }
10020
10174
  }
10175
+
10176
+ // ../../node_modules/es-toolkit/dist/util/attemptAsync.mjs
10177
+ async function attemptAsync(func) {
10178
+ try {
10179
+ return [null, await func()];
10180
+ } catch (error) {
10181
+ return [error, null];
10182
+ }
10183
+ }
10021
10184
  // ../../node_modules/ts-pattern/dist/index.js
10022
10185
  var t2 = Symbol.for("@ts-pattern/matcher");
10023
10186
  var e2 = Symbol.for("@ts-pattern/isVariadic");
@@ -16960,6 +17123,15 @@ class ClientSessions {
16960
17123
  const url = `${this.baseUrl}/api/sessions${search ? `?${search}` : ""}`;
16961
17124
  return requestJson(this.customFetch, url, { method: "GET", headers: this.authHeaders() }, ListSessionsResponseSchema, (status) => `Failed to fetch sessions with status ${status}`);
16962
17125
  }
17126
+ async exists(sessionId) {
17127
+ 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;
17131
+ if (err instanceof ChatUpstreamError && err.status === 404)
17132
+ return false;
17133
+ throw err;
17134
+ }
16963
17135
  async artifacts(query) {
16964
17136
  const params = new URLSearchParams({ sessionId: query.sessionId });
16965
17137
  if (query.taskId !== undefined)
@@ -16997,12 +17169,17 @@ class ClientSessions {
16997
17169
  signal
16998
17170
  });
16999
17171
  if (!response.ok) {
17000
- await response.body?.cancel().catch(() => {
17001
- return;
17002
- });
17172
+ const body = await response.text().catch(() => "");
17003
17173
  const retryable = response.status >= 500 || response.status === 429;
17004
- if (!retryable)
17174
+ if (!retryable) {
17175
+ if (!state.sessionReady) {
17176
+ state.outcome = {
17177
+ status: "failed",
17178
+ error: new ChatUpstreamError(`Session prewarm was rejected with status ${response.status}`, response.status, body)
17179
+ };
17180
+ }
17005
17181
  break;
17182
+ }
17006
17183
  continue;
17007
17184
  }
17008
17185
  await this.consumePrewarmStream(response, state, {
@@ -17021,7 +17198,10 @@ class ClientSessions {
17021
17198
  }
17022
17199
  if (state.outcome)
17023
17200
  return state.outcome;
17024
- return state.sessionReady ? { status: "fallback", session: state.sessionReady } : { status: "failed" };
17201
+ return state.sessionReady ? { status: "fallback", session: state.sessionReady } : {
17202
+ status: "failed",
17203
+ error: new ChatUpstreamError("Session prewarm never completed: no session_ready event (request retries exhausted or timed out).", 0, "")
17204
+ };
17025
17205
  }
17026
17206
  async consumePrewarmStream(response, state, callbacks) {
17027
17207
  if (!response.body)
@@ -17052,12 +17232,18 @@ class ClientSessions {
17052
17232
  }
17053
17233
  case "done": {
17054
17234
  PrewarmDoneSchema.parse(data);
17055
- state.outcome = state.sessionReady ? { status: "ready", session: state.sessionReady } : { status: "failed" };
17235
+ state.outcome = state.sessionReady ? { status: "ready", session: state.sessionReady } : {
17236
+ status: "failed",
17237
+ error: new ChatUpstreamError("Session prewarm ended before the session was created upstream.", 0, "")
17238
+ };
17056
17239
  return;
17057
17240
  }
17058
17241
  case "error": {
17059
- PrewarmErrorSchema.parse(data);
17060
- state.outcome = state.sessionReady ? { status: "fallback", session: state.sessionReady } : { status: "failed" };
17242
+ const payload = PrewarmErrorSchema.parse(data);
17243
+ state.outcome = state.sessionReady ? { status: "fallback", session: state.sessionReady } : {
17244
+ status: "failed",
17245
+ error: new ChatUpstreamError(payload.message, 0, JSON.stringify({ error: payload }))
17246
+ };
17061
17247
  return;
17062
17248
  }
17063
17249
  default:
@@ -17101,8 +17287,6 @@ var SkillListItemSchema = object2({
17101
17287
  link: string4(),
17102
17288
  descriptionEn: string4(),
17103
17289
  descriptionZh: string4(),
17104
- tagsEn: array2(string4()),
17105
- tagsZh: array2(string4()),
17106
17290
  enabled: boolean4(),
17107
17291
  added: boolean4(),
17108
17292
  builtIn: boolean4(),
@@ -17132,8 +17316,6 @@ var PlazaSkillItemSchema = object2({
17132
17316
  command: string4(),
17133
17317
  descriptionEn: string4(),
17134
17318
  descriptionZh: string4(),
17135
- tagsEn: array2(string4()),
17136
- tagsZh: array2(string4()),
17137
17319
  link: string4(),
17138
17320
  cover: string4().nullable(),
17139
17321
  version: string4(),
@@ -17807,29 +17989,56 @@ async function resolvePromptAttachments(options) {
17807
17989
  }
17808
17990
 
17809
17991
  // src/services/session.ts
17992
+ var SESSION_GONE_ERR_CODES = [
17993
+ ErrCode.SESSION_NOT_FOUND,
17994
+ ErrCode.CHAT_SESSION_EXPIRED
17995
+ ];
17810
17996
  function isSessionGoneError(err) {
17811
17997
  if (!(err instanceof ChatUpstreamError))
17812
17998
  return false;
17999
+ if (err.errCode !== null) {
18000
+ return SESSION_GONE_ERR_CODES.includes(err.errCode);
18001
+ }
17813
18002
  if (err.status === 404)
17814
18003
  return true;
17815
18004
  return err.body.includes(ERR_SESSION_EXPIRED);
17816
18005
  }
18006
+ async function ensureSessionExists(options) {
18007
+ const client = new MediaStudioClient({
18008
+ baseUrl: options.apiBaseUrl,
18009
+ apiKey: options.apiKey
18010
+ });
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.');
18015
+ }
18016
+ function finalizeProvision(result, spinner) {
18017
+ spinner?.stop();
18018
+ if (result.status === "failed") {
18019
+ throw result.error;
18020
+ }
18021
+ if (result.status === "fallback") {
18022
+ consola.warn("Session created, but sandbox/skill setup did not fully complete. Plain-text chat still works.");
18023
+ }
18024
+ return result.sessionId;
18025
+ }
17817
18026
  async function provisionSession(options) {
17818
- const sessionId = nanoid3();
17819
18027
  const client = new MediaStudioClient({
17820
18028
  baseUrl: options.apiBaseUrl,
17821
18029
  apiKey: options.apiKey
17822
18030
  });
18031
+ const sessionId = nanoid3();
17823
18032
  const outcome = await client.sessions.prewarm({
17824
18033
  sessionId,
17825
18034
  source: "cli",
17826
18035
  onSessionReady: () => saveLastSessionId(sessionId),
17827
- ...options.onStageChange ? { onStageChange: options.onStageChange } : {}
18036
+ onStageChange: options.onStageChange
17828
18037
  });
17829
- return M(outcome).with({ status: "ready" }, { status: "fallback" }, ({ status }) => ({
17830
- status,
17831
- sessionId
17832
- })).with({ status: "failed" }, () => ({ status: "failed" })).exhaustive();
18038
+ if (outcome.status === "failed") {
18039
+ return { status: "failed", error: outcome.error };
18040
+ }
18041
+ return { status: outcome.status, sessionId };
17833
18042
  }
17834
18043
 
17835
18044
  // src/lib/skill-refs.ts
@@ -17958,34 +18167,34 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
17958
18167
  process.stdout.write(`====================
17959
18168
  `);
17960
18169
  }
17961
- function finalizeProvision(result, spinner) {
17962
- spinner.stop();
17963
- if (result.status === "failed") {
17964
- throw new Error("Session provisioning failed before any session was created upstream. Try again.");
17965
- }
17966
- if (result.status === "fallback") {
17967
- consola.warn("Session created, but sandbox/skill setup did not fully complete. Continuing with chat.");
17968
- }
17969
- return result.sessionId;
18170
+ async function provisionNewSession(options) {
18171
+ const onStageChange = options.quiet ? undefined : (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage]);
18172
+ const result = await provisionSession({
18173
+ apiBaseUrl: options.apiBaseUrl,
18174
+ apiKey: options.apiKey,
18175
+ onStageChange
18176
+ });
18177
+ return finalizeProvision(result, options.spinner);
17970
18178
  }
17971
18179
  async function resolveChatSession(options) {
18180
+ if (options.newSession) {
18181
+ return {
18182
+ sessionId: await provisionNewSession(options),
18183
+ recreateOnGone: false
18184
+ };
18185
+ }
17972
18186
  if (options.sessionFlag) {
17973
18187
  saveLastSessionId(options.sessionFlag);
17974
- return { sessionId: options.sessionFlag, recreateOnGone: true };
18188
+ return { sessionId: options.sessionFlag, recreateOnGone: false };
17975
18189
  }
17976
18190
  const pointer = readLastSessionId();
17977
18191
  if (pointer) {
17978
18192
  return { sessionId: pointer, recreateOnGone: true };
17979
18193
  }
17980
- const result = await provisionSession({
17981
- apiBaseUrl: options.apiBaseUrl,
17982
- apiKey: options.apiKey,
17983
- ...options.quiet ? {} : {
17984
- onStageChange: (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage])
17985
- }
17986
- });
17987
- const sessionId = finalizeProvision(result, options.spinner);
17988
- return { sessionId, recreateOnGone: false };
18194
+ return {
18195
+ sessionId: await provisionNewSession(options),
18196
+ recreateOnGone: false
18197
+ };
17989
18198
  }
17990
18199
  async function streamTurn(options) {
17991
18200
  const { args, sessionId, spinner, autoEnabled, attachments } = options;
@@ -18007,6 +18216,9 @@ async function streamTurn(options) {
18007
18216
  const mediaAssets = [];
18008
18217
  let hasFinished = false;
18009
18218
  for await (const ev of stream) {
18219
+ if (ev.type !== "finish" && ev.type !== "error") {
18220
+ options.progress.emitted = true;
18221
+ }
18010
18222
  M(ev).with({ type: "text-delta", delta: z2.string }, ({ delta }) => {
18011
18223
  isThinking = false;
18012
18224
  spinner.stop();
@@ -18108,17 +18320,28 @@ async function runTurn(options) {
18108
18320
  try {
18109
18321
  await streamTurn(options);
18110
18322
  } catch (err) {
18111
- if (options.recreateOnGone && isSessionGoneError(err)) {
18323
+ const sessionGone = isSessionGoneError(err);
18324
+ if (sessionGone && options.progress.emitted) {
18325
+ throw new Error(`Session ${options.sessionId} failed after this turn had already started.
18326
+ ` + " → The partial result above stands; re-run the same prompt to continue in a new session.");
18327
+ }
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.');
18331
+ }
18332
+ if (sessionGone) {
18112
18333
  consola.warn(`Session ${options.sessionId} is no longer available. Provisioning a new one...`);
18113
- const result = await provisionSession({
18334
+ const sessionId = await provisionNewSession({
18114
18335
  apiBaseUrl: options.apiBaseUrl,
18115
18336
  apiKey: resolveApiKey(),
18116
- ...options.args.json ? {} : {
18117
- onStageChange: (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage])
18118
- }
18337
+ spinner: options.spinner,
18338
+ quiet: options.args.json === true
18339
+ });
18340
+ await streamTurn({
18341
+ ...options,
18342
+ sessionId,
18343
+ apiKey: resolveApiKey()
18119
18344
  });
18120
- const sessionId = finalizeProvision(result, options.spinner);
18121
- await streamTurn({ ...options, sessionId, apiKey: resolveApiKey() });
18122
18345
  return;
18123
18346
  }
18124
18347
  throw err;
@@ -18142,7 +18365,11 @@ var chatCommand = defineCommand({
18142
18365
  },
18143
18366
  session: {
18144
18367
  type: "string",
18145
- description: "Session id to chat with; becomes the default session for future runs"
18368
+ description: "Chat in an existing session id (verified upstream); becomes the default session. A missing id fails — create one with --new"
18369
+ },
18370
+ new: {
18371
+ type: "boolean",
18372
+ description: "Create a new session and chat in it; becomes the default session"
18146
18373
  },
18147
18374
  "show-reasoning": {
18148
18375
  type: "boolean",
@@ -18154,6 +18381,16 @@ var chatCommand = defineCommand({
18154
18381
  const spinner = new AgentSpinner;
18155
18382
  try {
18156
18383
  const apiBaseUrl = resolveApiBaseUrl();
18384
+ if (args.new && args.session) {
18385
+ throw new Error("--new and --session cannot be combined: --session selects an existing session, --new creates one.");
18386
+ }
18387
+ if (args.session) {
18388
+ await ensureSessionExists({
18389
+ apiBaseUrl,
18390
+ apiKey: resolveApiKey(),
18391
+ sessionId: args.session
18392
+ });
18393
+ }
18157
18394
  const { text, skillUrls, autoEnabled } = await resolvePromptSkills({
18158
18395
  prompt: args.prompt,
18159
18396
  apiBaseUrl,
@@ -18172,6 +18409,7 @@ var chatCommand = defineCommand({
18172
18409
  });
18173
18410
  const resolved = await resolveChatSession({
18174
18411
  sessionFlag: args.session,
18412
+ newSession: args.new,
18175
18413
  apiBaseUrl,
18176
18414
  apiKey: resolveApiKey(),
18177
18415
  spinner,
@@ -18182,6 +18420,7 @@ var chatCommand = defineCommand({
18182
18420
  prompt: promptWithAttachments,
18183
18421
  sessionId: resolved.sessionId,
18184
18422
  recreateOnGone: resolved.recreateOnGone,
18423
+ progress: { emitted: false },
18185
18424
  apiBaseUrl,
18186
18425
  apiKey: resolveApiKey(),
18187
18426
  spinner,
@@ -18216,6 +18455,12 @@ images, videos, and multimedia content through AI Media Studio.
18216
18455
  3. Refine in the next turn (multi-turn is automatic):
18217
18456
  msc chat "change the car color to emerald green and add rain"
18218
18457
 
18458
+ Agent Skill (recommended for AI agents):
18459
+ If the agent does not already know how to use msc, install the public
18460
+ Agent Skill before starting a new session:
18461
+ npx -y skills add 302ai/msc-skill --skill msc-skill -g -y
18462
+ This is optional for direct terminal use.
18463
+
18219
18464
 
18220
18465
  2. AUTHENTICATION & SERVERS
18221
18466
 
@@ -18274,8 +18519,16 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18274
18519
  "no such file". Remote URLs are not attachments and there is no @url form.
18275
18520
 
18276
18521
  Session Switching:
18277
- Pass \`--session <id>\` to direct a prompt to a specific existing session
18278
- and make it your new active session.
18522
+ Pass \`--session <id>\` to direct a prompt to an existing session and make it
18523
+ your new active session. The id is verified against the server first: the
18524
+ CLI only ever selects sessions that exist, so an unknown id fails with a
18525
+ hint instead of silently starting a different conversation.
18526
+
18527
+ Pass \`--new\` to start a fresh session (new id, never reused) and chat in
18528
+ it, ignoring the current default. \`--new\` and \`--session\` are mutually
18529
+ exclusive.
18530
+ msc chat "start over" --new # new session, new context
18531
+ msc sessions # pick an existing id for --session
18279
18532
 
18280
18533
  4. USING SKILLS
18281
18534
 
@@ -18316,7 +18569,13 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18316
18569
  including download URLs and model metadata.
18317
18570
 
18318
18571
  msc sessions use <session-id>
18319
- Switch your active session pointer without sending a prompt.
18572
+ Switch your active session pointer to an existing session. The id is
18573
+ verified against the server; an unknown id fails here and leaves the
18574
+ pointer untouched.
18575
+
18576
+ msc sessions use --new
18577
+ Create a new session upstream and switch the pointer to it, without sending
18578
+ a prompt.
18320
18579
 
18321
18580
  Session deletion is not available in the CLI. Open the corresponding session
18322
18581
  in the Media Studio web app and delete it there manually.
@@ -18369,6 +18628,13 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18369
18628
  "nextStep": "relogin" // recommended action keyword
18370
18629
  }
18371
18630
 
18631
+ Session commands:
18632
+ msc sessions --json # list: [{ sessionId, note, ... }]
18633
+ msc sessions use <id> --json # { "sessionId": "string" }
18634
+ msc sessions use --new --json # { "sessionId": "string" } (newly created)
18635
+ \`--session\`/\`use <id>\` verify the id upstream; an unknown id exits 1 with
18636
+ the \`--new\` hint and leaves the default session untouched.
18637
+
18372
18638
  Recommended next steps:
18373
18639
  relogin Session expired or missing; run \`msc login\`
18374
18640
  retry_later Rate limit reached; wait before re-sending
@@ -20361,18 +20627,63 @@ var artifactsCommand = defineCommand({
20361
20627
  var useSessionCommand = defineCommand({
20362
20628
  meta: {
20363
20629
  name: "use",
20364
- description: "Switch default session (local pointer only, no network request)"
20630
+ description: "Switch the default session to an existing session, or create a fresh one with --new"
20365
20631
  },
20366
20632
  args: {
20367
20633
  sessionId: {
20368
20634
  type: "positional",
20369
- required: true,
20370
- description: "Session id to set as default"
20635
+ required: false,
20636
+ description: "Existing session id to set as default"
20637
+ },
20638
+ new: {
20639
+ type: "boolean",
20640
+ description: "Create a new session and make it the default"
20641
+ },
20642
+ json: {
20643
+ type: "boolean",
20644
+ alias: "j",
20645
+ description: "Output machine-readable JSON: { sessionId }"
20371
20646
  }
20372
20647
  },
20373
- run: ({ args }) => {
20374
- saveLastSessionId(args.sessionId);
20375
- consola.success(`Switched default session to ${args.sessionId}`);
20648
+ run: async ({ args }) => {
20649
+ try {
20650
+ const sessionId = args.sessionId;
20651
+ const apiBaseUrl = resolveApiBaseUrl();
20652
+ if (args.new) {
20653
+ if (sessionId) {
20654
+ throw new Error("--new and <session-id> cannot be combined: pass one, not both.");
20655
+ }
20656
+ const result = await provisionSession({
20657
+ apiBaseUrl,
20658
+ apiKey: resolveApiKey()
20659
+ });
20660
+ const created = finalizeProvision(result);
20661
+ if (args.json) {
20662
+ process.stdout.write(`${JSON.stringify({ sessionId: created })}
20663
+ `);
20664
+ return;
20665
+ }
20666
+ consola.success(`Created and switched to session ${created}`);
20667
+ return;
20668
+ }
20669
+ if (!sessionId) {
20670
+ throw new Error("Pass the session id to switch to, or --new to create a new session.");
20671
+ }
20672
+ await ensureSessionExists({
20673
+ apiBaseUrl,
20674
+ apiKey: resolveApiKey(),
20675
+ sessionId
20676
+ });
20677
+ saveLastSessionId(sessionId);
20678
+ if (args.json) {
20679
+ process.stdout.write(`${JSON.stringify({ sessionId })}
20680
+ `);
20681
+ return;
20682
+ }
20683
+ consola.success(`Switched default session to ${sessionId}`);
20684
+ } catch (err) {
20685
+ reportCommandError(err, { json: args.json });
20686
+ }
20376
20687
  }
20377
20688
  });
20378
20689
  var sessionsCommand = defineCommand({
@@ -20752,6 +21063,8 @@ AGENT QUICKSTART
20752
21063
  1. msc login # a human must finish this in a browser; instant if
20753
21064
  # that browser is already signed in to the web app
20754
21065
  2. msc chat "a red car" --json # one turn; the conversation continues across runs
21066
+ # (--new starts a fresh session, --session <id> picks
21067
+ # an existing one; both are verified)
20755
21068
  3. msc skills --plaza --json # find a skill, then reference it by leading the
20756
21069
  # prompt with /name or %%/name%%
20757
21070
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.1-beta.0",
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",