@302ai/media-studio-cli 0.1.0 → 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 +22 -8
  2. package/dist/index.js +353 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,10 +42,12 @@ name:
42
42
  ```bash
43
43
  msc chat "画一只猫" -r # -r/--show-reasoning = 展示模型推理过程
44
44
  msc chat "hi" --json # machine-readable output
45
- msc chat "hi" --session <id> # 切换到指定会话并设为默认
45
+ msc chat "hi" --session <id> # 切换到已有会话(先校验存在)并设为默认
46
+ msc chat "hi" --new # 新建会话并设为默认(id 不复用)
46
47
  msc sessions # 列出所有会话(标记当前默认 `(current)`)
47
48
  msc sessions --json # 机器可读的原始 JSON 数组
48
- msc sessions use <id> # 纯本地切换默认会话(零请求、不校验)
49
+ msc sessions use <id> # 校验存在后切换默认会话
50
+ msc sessions use --new # 新建会话并设为默认
49
51
  msc skills # 列出技能库(--plaza 浏览技能广场)
50
52
  msc skills add <slug> # 从广场安装技能(如 brainstorming,见 --plaza --json 的 slug)
51
53
  msc skills rm <command> # 从技能库移除(-y/--yes 跳过确认)
@@ -79,10 +81,20 @@ whose browser can reach the loopback callback.
79
81
  loses a live session.
80
82
  - **Later chats** continue that session — upstream remembers the
81
83
  conversation by session id, so no history is resent.
82
- - If the session was deleted elsewhere, the CLI transparently provisions a
83
- fresh one and continues — you are never stranded.
84
- - `--session <id>` switches to a specific session (including web-created
85
- 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.
86
98
 
87
99
  Manual proof of continuity:
88
100
 
@@ -317,8 +329,10 @@ msc chat "刚才的数字是几?" # answers 42
317
329
 
318
330
  # Session management verification:
319
331
  msc sessions # shows session list with (current) marker
320
- msc chat "hi" --session <id> # switch to specific session
321
- 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
322
336
  ```
323
337
 
324
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",
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",
@@ -10027,6 +10172,15 @@ function attempt2(func) {
10027
10172
  return [error, null];
10028
10173
  }
10029
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
+ }
10030
10184
  // ../../node_modules/ts-pattern/dist/index.js
10031
10185
  var t2 = Symbol.for("@ts-pattern/matcher");
10032
10186
  var e2 = Symbol.for("@ts-pattern/isVariadic");
@@ -16969,6 +17123,15 @@ class ClientSessions {
16969
17123
  const url = `${this.baseUrl}/api/sessions${search ? `?${search}` : ""}`;
16970
17124
  return requestJson(this.customFetch, url, { method: "GET", headers: this.authHeaders() }, ListSessionsResponseSchema, (status) => `Failed to fetch sessions with status ${status}`);
16971
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
+ }
16972
17135
  async artifacts(query) {
16973
17136
  const params = new URLSearchParams({ sessionId: query.sessionId });
16974
17137
  if (query.taskId !== undefined)
@@ -17006,12 +17169,17 @@ class ClientSessions {
17006
17169
  signal
17007
17170
  });
17008
17171
  if (!response.ok) {
17009
- await response.body?.cancel().catch(() => {
17010
- return;
17011
- });
17172
+ const body = await response.text().catch(() => "");
17012
17173
  const retryable = response.status >= 500 || response.status === 429;
17013
- 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
+ }
17014
17181
  break;
17182
+ }
17015
17183
  continue;
17016
17184
  }
17017
17185
  await this.consumePrewarmStream(response, state, {
@@ -17030,7 +17198,10 @@ class ClientSessions {
17030
17198
  }
17031
17199
  if (state.outcome)
17032
17200
  return state.outcome;
17033
- 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
+ };
17034
17205
  }
17035
17206
  async consumePrewarmStream(response, state, callbacks) {
17036
17207
  if (!response.body)
@@ -17061,12 +17232,18 @@ class ClientSessions {
17061
17232
  }
17062
17233
  case "done": {
17063
17234
  PrewarmDoneSchema.parse(data);
17064
- 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
+ };
17065
17239
  return;
17066
17240
  }
17067
17241
  case "error": {
17068
- PrewarmErrorSchema.parse(data);
17069
- 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
+ };
17070
17247
  return;
17071
17248
  }
17072
17249
  default:
@@ -17812,29 +17989,56 @@ async function resolvePromptAttachments(options) {
17812
17989
  }
17813
17990
 
17814
17991
  // src/services/session.ts
17992
+ var SESSION_GONE_ERR_CODES = [
17993
+ ErrCode.SESSION_NOT_FOUND,
17994
+ ErrCode.CHAT_SESSION_EXPIRED
17995
+ ];
17815
17996
  function isSessionGoneError(err) {
17816
17997
  if (!(err instanceof ChatUpstreamError))
17817
17998
  return false;
17999
+ if (err.errCode !== null) {
18000
+ return SESSION_GONE_ERR_CODES.includes(err.errCode);
18001
+ }
17818
18002
  if (err.status === 404)
17819
18003
  return true;
17820
18004
  return err.body.includes(ERR_SESSION_EXPIRED);
17821
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
+ }
17822
18026
  async function provisionSession(options) {
17823
- const sessionId = nanoid3();
17824
18027
  const client = new MediaStudioClient({
17825
18028
  baseUrl: options.apiBaseUrl,
17826
18029
  apiKey: options.apiKey
17827
18030
  });
18031
+ const sessionId = nanoid3();
17828
18032
  const outcome = await client.sessions.prewarm({
17829
18033
  sessionId,
17830
18034
  source: "cli",
17831
18035
  onSessionReady: () => saveLastSessionId(sessionId),
17832
- ...options.onStageChange ? { onStageChange: options.onStageChange } : {}
18036
+ onStageChange: options.onStageChange
17833
18037
  });
17834
- return M(outcome).with({ status: "ready" }, { status: "fallback" }, ({ status }) => ({
17835
- status,
17836
- sessionId
17837
- })).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 };
17838
18042
  }
17839
18043
 
17840
18044
  // src/lib/skill-refs.ts
@@ -17963,34 +18167,34 @@ function printTerminalMediaAssets(mediaAssets, fullText) {
17963
18167
  process.stdout.write(`====================
17964
18168
  `);
17965
18169
  }
17966
- function finalizeProvision(result, spinner) {
17967
- spinner.stop();
17968
- if (result.status === "failed") {
17969
- throw new Error("Session provisioning failed before any session was created upstream. Try again.");
17970
- }
17971
- if (result.status === "fallback") {
17972
- consola.warn("Session created, but sandbox/skill setup did not fully complete. Continuing with chat.");
17973
- }
17974
- 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);
17975
18178
  }
17976
18179
  async function resolveChatSession(options) {
18180
+ if (options.newSession) {
18181
+ return {
18182
+ sessionId: await provisionNewSession(options),
18183
+ recreateOnGone: false
18184
+ };
18185
+ }
17977
18186
  if (options.sessionFlag) {
17978
18187
  saveLastSessionId(options.sessionFlag);
17979
- return { sessionId: options.sessionFlag, recreateOnGone: true };
18188
+ return { sessionId: options.sessionFlag, recreateOnGone: false };
17980
18189
  }
17981
18190
  const pointer = readLastSessionId();
17982
18191
  if (pointer) {
17983
18192
  return { sessionId: pointer, recreateOnGone: true };
17984
18193
  }
17985
- const result = await provisionSession({
17986
- apiBaseUrl: options.apiBaseUrl,
17987
- apiKey: options.apiKey,
17988
- ...options.quiet ? {} : {
17989
- onStageChange: (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage])
17990
- }
17991
- });
17992
- const sessionId = finalizeProvision(result, options.spinner);
17993
- return { sessionId, recreateOnGone: false };
18194
+ return {
18195
+ sessionId: await provisionNewSession(options),
18196
+ recreateOnGone: false
18197
+ };
17994
18198
  }
17995
18199
  async function streamTurn(options) {
17996
18200
  const { args, sessionId, spinner, autoEnabled, attachments } = options;
@@ -18012,6 +18216,9 @@ async function streamTurn(options) {
18012
18216
  const mediaAssets = [];
18013
18217
  let hasFinished = false;
18014
18218
  for await (const ev of stream) {
18219
+ if (ev.type !== "finish" && ev.type !== "error") {
18220
+ options.progress.emitted = true;
18221
+ }
18015
18222
  M(ev).with({ type: "text-delta", delta: z2.string }, ({ delta }) => {
18016
18223
  isThinking = false;
18017
18224
  spinner.stop();
@@ -18113,17 +18320,28 @@ async function runTurn(options) {
18113
18320
  try {
18114
18321
  await streamTurn(options);
18115
18322
  } catch (err) {
18116
- 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) {
18117
18333
  consola.warn(`Session ${options.sessionId} is no longer available. Provisioning a new one...`);
18118
- const result = await provisionSession({
18334
+ const sessionId = await provisionNewSession({
18119
18335
  apiBaseUrl: options.apiBaseUrl,
18120
18336
  apiKey: resolveApiKey(),
18121
- ...options.args.json ? {} : {
18122
- onStageChange: (stage) => options.spinner.start(PROVISION_STAGE_LABELS[stage])
18123
- }
18337
+ spinner: options.spinner,
18338
+ quiet: options.args.json === true
18339
+ });
18340
+ await streamTurn({
18341
+ ...options,
18342
+ sessionId,
18343
+ apiKey: resolveApiKey()
18124
18344
  });
18125
- const sessionId = finalizeProvision(result, options.spinner);
18126
- await streamTurn({ ...options, sessionId, apiKey: resolveApiKey() });
18127
18345
  return;
18128
18346
  }
18129
18347
  throw err;
@@ -18147,7 +18365,11 @@ var chatCommand = defineCommand({
18147
18365
  },
18148
18366
  session: {
18149
18367
  type: "string",
18150
- 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"
18151
18373
  },
18152
18374
  "show-reasoning": {
18153
18375
  type: "boolean",
@@ -18159,6 +18381,16 @@ var chatCommand = defineCommand({
18159
18381
  const spinner = new AgentSpinner;
18160
18382
  try {
18161
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
+ }
18162
18394
  const { text, skillUrls, autoEnabled } = await resolvePromptSkills({
18163
18395
  prompt: args.prompt,
18164
18396
  apiBaseUrl,
@@ -18177,6 +18409,7 @@ var chatCommand = defineCommand({
18177
18409
  });
18178
18410
  const resolved = await resolveChatSession({
18179
18411
  sessionFlag: args.session,
18412
+ newSession: args.new,
18180
18413
  apiBaseUrl,
18181
18414
  apiKey: resolveApiKey(),
18182
18415
  spinner,
@@ -18187,6 +18420,7 @@ var chatCommand = defineCommand({
18187
18420
  prompt: promptWithAttachments,
18188
18421
  sessionId: resolved.sessionId,
18189
18422
  recreateOnGone: resolved.recreateOnGone,
18423
+ progress: { emitted: false },
18190
18424
  apiBaseUrl,
18191
18425
  apiKey: resolveApiKey(),
18192
18426
  spinner,
@@ -18285,8 +18519,16 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18285
18519
  "no such file". Remote URLs are not attachments and there is no @url form.
18286
18520
 
18287
18521
  Session Switching:
18288
- Pass \`--session <id>\` to direct a prompt to a specific existing session
18289
- 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
18290
18532
 
18291
18533
  4. USING SKILLS
18292
18534
 
@@ -18327,7 +18569,13 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url} (${r.des
18327
18569
  including download URLs and model metadata.
18328
18570
 
18329
18571
  msc sessions use <session-id>
18330
- 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.
18331
18579
 
18332
18580
  Session deletion is not available in the CLI. Open the corresponding session
18333
18581
  in the Media Studio web app and delete it there manually.
@@ -18380,6 +18628,13 @@ ${getAvailableRegions().map((r) => ` ${r.key.padEnd(8)} - ${r.url}`).join(`
18380
18628
  "nextStep": "relogin" // recommended action keyword
18381
18629
  }
18382
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
+
18383
18638
  Recommended next steps:
18384
18639
  relogin Session expired or missing; run \`msc login\`
18385
18640
  retry_later Rate limit reached; wait before re-sending
@@ -20372,18 +20627,63 @@ var artifactsCommand = defineCommand({
20372
20627
  var useSessionCommand = defineCommand({
20373
20628
  meta: {
20374
20629
  name: "use",
20375
- 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"
20376
20631
  },
20377
20632
  args: {
20378
20633
  sessionId: {
20379
20634
  type: "positional",
20380
- required: true,
20381
- 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 }"
20382
20646
  }
20383
20647
  },
20384
- run: ({ args }) => {
20385
- saveLastSessionId(args.sessionId);
20386
- 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
+ }
20387
20687
  }
20388
20688
  });
20389
20689
  var sessionsCommand = defineCommand({
@@ -20763,6 +21063,8 @@ AGENT QUICKSTART
20763
21063
  1. msc login # a human must finish this in a browser; instant if
20764
21064
  # that browser is already signed in to the web app
20765
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)
20766
21068
  3. msc skills --plaza --json # find a skill, then reference it by leading the
20767
21069
  # prompt with /name or %%/name%%
20768
21070
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.0",
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",