@302ai/media-studio-cli 0.1.0-beta.3 → 0.1.0-beta.4

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 +31 -0
  2. package/dist/index.js +516 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -81,6 +81,37 @@ Point at a different server via `MEDIA_STUDIO_API_URL` env var, or
81
81
  `msc login --region local|cn|default` (the server you log into is saved and
82
82
  reused).
83
83
 
84
+ ### Skills
85
+
86
+ Reference a skill in the prompt and the CLI installs it into the session
87
+ sandbox on demand:
88
+
89
+ ```bash
90
+ msc chat "/always-hello 不管我说什么" # leading slash command
91
+ msc chat "%%/always-hello%%不管我说什么" # web composer marker, paste-compatible
92
+ ```
93
+
94
+ Both spellings resolve to the same thing. The upstream installs a skill only
95
+ from the turn's `skill_urls` field, so the CLI looks the reference up in your
96
+ catalog (`GET /api/skills`) and sends the matching package URL — prompt text
97
+ alone triggers nothing.
98
+
99
+ - A **bare `/command` counts only at the start of the prompt**, so mid-sentence
100
+ slashes stay prose (`check /etc/hosts` is not a skill reference). The `%%…%%`
101
+ marker works anywhere. The leading token can be any bare slug, single-segment
102
+ included — `/tmp what is this` resolves `/tmp` — and a reference that matches
103
+ nothing fails the turn with the catalog listed, so a typo never silently
104
+ degrades to a skill-less answer.
105
+ - The catalog is fetched **only when the prompt references a skill**; skill-free
106
+ turns cost no extra request.
107
+ - Unknown skills fail the turn with a non-zero exit before any session is
108
+ provisioned. A **disabled** skill is auto-enabled first — the same ADR-0001
109
+ behavior as the web's "Use in Chat" — because the upstream treats `enabled`
110
+ as a hard precondition for invocation and would otherwise download the
111
+ package and silently never run it. The enable flips the skill's `enabled`
112
+ flag on your account (a persistent change), is reported as
113
+ `Auto-enabled for this turn: …`, and a failed enable aborts the turn.
114
+
84
115
  ### Staging server (expired TLS certificate)
85
116
 
86
117
  The staging server's certificate is expired (ops declines renewal), so the
package/dist/index.js CHANGED
@@ -1467,7 +1467,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
1467
1467
  // package.json
1468
1468
  var package_default = {
1469
1469
  name: "@302ai/media-studio-cli",
1470
- version: "0.1.0-beta.3",
1470
+ version: "0.1.0-beta.4",
1471
1471
  description: "CLI for AI Media Studio — let external agents drive the core media-studio features",
1472
1472
  author: "302.AI",
1473
1473
  license: "Apache-2.0",
@@ -1885,7 +1885,7 @@ function F(t) {
1885
1885
  throw new I(t);
1886
1886
  }
1887
1887
 
1888
- // ../media-studio-core/dist/transport/errors.js
1888
+ // ../media-studio-core/src/transport/errors.ts
1889
1889
  var ERR_SESSION_EXPIRED = "SESSION_EXPIRED";
1890
1890
 
1891
1891
  class ChatUpstreamError extends Error {
@@ -7307,7 +7307,7 @@ function refine(fn, _params = {}) {
7307
7307
  function superRefine(fn, params) {
7308
7308
  return _superRefine(fn, params);
7309
7309
  }
7310
- // ../media-studio-core/dist/client/types.js
7310
+ // ../media-studio-core/src/client/types.ts
7311
7311
  var MeResponseSchema = object({
7312
7312
  uid: string2().nullable().optional(),
7313
7313
  name: string2().nullable().optional(),
@@ -7316,8 +7316,26 @@ var MeResponseSchema = object({
7316
7316
  exp: number2().optional(),
7317
7317
  valid: boolean2()
7318
7318
  });
7319
+ var SkillListItemSchema = object({
7320
+ id: string2(),
7321
+ name: string2(),
7322
+ command: string2(),
7323
+ link: string2(),
7324
+ enabled: boolean2(),
7325
+ added: boolean2(),
7326
+ builtIn: boolean2(),
7327
+ version: string2()
7328
+ });
7329
+ var SkillListResponseSchema = object({
7330
+ success: boolean2(),
7331
+ skills: array(SkillListItemSchema)
7332
+ });
7333
+ var SingleSkillResponseSchema = object({
7334
+ success: boolean2(),
7335
+ skill: SkillListItemSchema
7336
+ });
7319
7337
 
7320
- // ../media-studio-core/dist/client/auth.js
7338
+ // ../media-studio-core/src/client/auth.ts
7321
7339
  class ClientAuth {
7322
7340
  baseUrl;
7323
7341
  apiKey;
@@ -7691,7 +7709,7 @@ function isPotentialField(line, field) {
7691
7709
  }
7692
7710
  return line.length <= field.length || line.charCodeAt(field.length) === 58;
7693
7711
  }
7694
- // ../media-studio-core/dist/media/media-result.js
7712
+ // ../media-studio-core/src/media/media-result.ts
7695
7713
  var MediaResultSchema = object({
7696
7714
  status: string2().optional(),
7697
7715
  domain: string2().optional(),
@@ -7791,7 +7809,7 @@ function addOrMergeMediaResult(assets, incoming) {
7791
7809
  }
7792
7810
  }
7793
7811
 
7794
- // ../media-studio-core/dist/stream/events.js
7812
+ // ../media-studio-core/src/stream/events.ts
7795
7813
  var TextDeltaEventSchema = object({
7796
7814
  type: literal("text-delta"),
7797
7815
  delta: string2(),
@@ -7837,7 +7855,7 @@ var ChatStreamEventSchema = discriminatedUnion("type", [
7837
7855
  StreamErrorEventSchema
7838
7856
  ]);
7839
7857
 
7840
- // ../media-studio-core/dist/stream/stream.js
7858
+ // ../media-studio-core/src/stream/stream.ts
7841
7859
  function abortException(signal) {
7842
7860
  const reason = signal?.reason;
7843
7861
  if (reason instanceof Error)
@@ -7943,7 +7961,9 @@ class ChatStream {
7943
7961
  }
7944
7962
  const retryDelay = initialDelay * 2 ** attemptCount;
7945
7963
  attemptCount++;
7946
- await delay(retryDelay, { signal: this.options.signal });
7964
+ await delay(retryDelay, {
7965
+ ...this.options.signal ? { signal: this.options.signal } : {}
7966
+ });
7947
7967
  }
7948
7968
  }
7949
7969
  }
@@ -8090,7 +8110,7 @@ class ChatStream {
8090
8110
  }
8091
8111
  }
8092
8112
 
8093
- // ../media-studio-core/dist/client/chat.js
8113
+ // ../media-studio-core/src/client/chat.ts
8094
8114
  class ClientChat {
8095
8115
  baseUrl;
8096
8116
  apiKey;
@@ -8122,14 +8142,15 @@ class ClientChat {
8122
8142
  ...isNotNil(options.skillUrls) ? { skill_urls: options.skillUrls } : {},
8123
8143
  ...isNotNil(options.structuredOutput) ? { structured_output: options.structuredOutput } : {}
8124
8144
  };
8125
- if (options.messages.length === 1 && options.messages[0].role === "user" && isString(options.messages[0].content)) {
8126
- body.prompt = options.messages[0].content;
8145
+ const firstMessage = options.messages[0];
8146
+ if (options.messages.length === 1 && firstMessage?.role === "user" && isString(firstMessage.content)) {
8147
+ body.prompt = firstMessage.content;
8127
8148
  }
8128
8149
  return this.customFetch(url, {
8129
8150
  method: "POST",
8130
8151
  headers,
8131
8152
  body: JSON.stringify(body),
8132
- signal: options.signal
8153
+ ...options.signal ? { signal: options.signal } : {}
8133
8154
  });
8134
8155
  }
8135
8156
  async raw(promptOrOptions, maybeOptions) {
@@ -8172,44 +8193,122 @@ class ClientChat {
8172
8193
  }
8173
8194
  }
8174
8195
 
8175
- // ../../node_modules/nanoid/index.js
8176
- import { webcrypto as crypto } from "node:crypto";
8177
-
8178
- // ../../node_modules/nanoid/url-alphabet/index.js
8196
+ // ../media-studio-core/node_modules/nanoid/url-alphabet/index.js
8179
8197
  var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
8180
8198
 
8181
- // ../../node_modules/nanoid/index.js
8182
- var POOL_SIZE_MULTIPLIER = 128;
8183
- var pool;
8184
- var poolOffset;
8185
- function fillPool(bytes) {
8199
+ // ../media-studio-core/node_modules/nanoid/index.js
8200
+ var GET_RANDOM_LIMIT = 65536;
8201
+ function fillRandom(buffer) {
8202
+ let from = 0;
8203
+ while (from < buffer.length) {
8204
+ let to = Math.min(from + GET_RANDOM_LIMIT, buffer.length);
8205
+ crypto.getRandomValues(buffer.subarray(from, to));
8206
+ from = to;
8207
+ }
8208
+ }
8209
+ function random3(bytes) {
8210
+ bytes |= 0;
8186
8211
  if (bytes < 0)
8187
8212
  throw new RangeError("Wrong ID size");
8188
- try {
8189
- if (!pool || pool.length < bytes) {
8190
- pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
8191
- crypto.getRandomValues(pool);
8192
- poolOffset = 0;
8193
- } else if (poolOffset + bytes > pool.length) {
8194
- crypto.getRandomValues(pool);
8195
- poolOffset = 0;
8196
- }
8197
- } catch (e) {
8198
- pool = undefined;
8199
- throw e;
8213
+ let buffer = Buffer.allocUnsafe(bytes);
8214
+ fillRandom(buffer);
8215
+ return buffer;
8216
+ }
8217
+ function customRandom(alphabet, defaultSize, getRandom) {
8218
+ let safeByteCutoff = 256 - 256 % alphabet.length;
8219
+ if (safeByteCutoff === 256) {
8220
+ let mask = alphabet.length - 1;
8221
+ return (size = defaultSize) => {
8222
+ if (!size)
8223
+ return "";
8224
+ let id = "";
8225
+ while (true) {
8226
+ let bytes = getRandom(size);
8227
+ let i = size;
8228
+ while (i--) {
8229
+ id += alphabet[bytes[i] & mask];
8230
+ if (id.length >= size)
8231
+ return id;
8232
+ }
8233
+ }
8234
+ };
8200
8235
  }
8201
- poolOffset += bytes;
8236
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
8237
+ return (size = defaultSize) => {
8238
+ if (!size)
8239
+ return "";
8240
+ let id = "";
8241
+ while (true) {
8242
+ let bytes = getRandom(step);
8243
+ let i = step;
8244
+ while (i--) {
8245
+ if (bytes[i] < safeByteCutoff) {
8246
+ id += alphabet[bytes[i] % alphabet.length];
8247
+ if (id.length >= size)
8248
+ return id;
8249
+ }
8250
+ }
8251
+ }
8252
+ };
8202
8253
  }
8203
- function nanoid2(size = 21) {
8204
- fillPool(size |= 0);
8205
- let id = "";
8206
- for (let i = poolOffset - size;i < poolOffset; i++) {
8207
- id += urlAlphabet[pool[i] & 63];
8254
+ var POOL_MAX = GET_RANDOM_LIMIT / 2;
8255
+ function customAlphabet(alphabet, defaultSize = 21) {
8256
+ if (typeof alphabet !== "string" || !alphabet || alphabet.length > 256) {
8257
+ return customRandom(alphabet, defaultSize, random3);
8258
+ }
8259
+ for (let i = 0;i < alphabet.length; i++) {
8260
+ if (alphabet.charCodeAt(i) > 255) {
8261
+ return customRandom(alphabet, defaultSize, random3);
8262
+ }
8208
8263
  }
8209
- return id;
8264
+ let charCodes = Uint8Array.from(alphabet, (str) => {
8265
+ return str.charCodeAt(0);
8266
+ });
8267
+ let alphabetLen = alphabet.length;
8268
+ let mask = (2 << 31 - Math.clz32(alphabetLen - 1 | 1)) - 1;
8269
+ let pool = "";
8270
+ let poolOffset = 0;
8271
+ let poolNext = 0;
8272
+ return (size = defaultSize) => {
8273
+ size |= 0;
8274
+ if (size < 0)
8275
+ throw new RangeError("Wrong ID size");
8276
+ if (size === 0)
8277
+ return "";
8278
+ if (poolOffset + size > pool.length) {
8279
+ let target = Math.max(poolNext, size);
8280
+ poolNext = Math.min(target * 16, POOL_MAX);
8281
+ let buffer = Buffer.allocUnsafe(target);
8282
+ if (mask === alphabetLen - 1) {
8283
+ fillRandom(buffer);
8284
+ for (let i = 0;i < target; i++) {
8285
+ buffer[i] = charCodes[buffer[i] & mask];
8286
+ }
8287
+ } else {
8288
+ let randomBytes = Buffer.allocUnsafe(Math.ceil(1.6 * (mask + 1) * target / alphabetLen));
8289
+ let accepted = 0;
8290
+ while (accepted < target) {
8291
+ fillRandom(randomBytes);
8292
+ for (let i = 0;i < randomBytes.length; i++) {
8293
+ let index = randomBytes[i] & mask;
8294
+ if (index < alphabetLen) {
8295
+ buffer[accepted++] = charCodes[index];
8296
+ if (accepted === target)
8297
+ break;
8298
+ }
8299
+ }
8300
+ }
8301
+ }
8302
+ pool = buffer.toString("latin1");
8303
+ poolOffset = 0;
8304
+ }
8305
+ poolOffset += size;
8306
+ return pool.substring(poolOffset - size, poolOffset);
8307
+ };
8210
8308
  }
8309
+ var nanoid2 = customAlphabet(urlAlphabet);
8211
8310
 
8212
- // ../media-studio-core/dist/client/session.js
8311
+ // ../media-studio-core/src/client/session.ts
8213
8312
  class ChatSession {
8214
8313
  sessionId;
8215
8314
  chat;
@@ -8281,7 +8380,7 @@ class ChatSession {
8281
8380
  }
8282
8381
  }
8283
8382
 
8284
- // ../media-studio-core/dist/sessions/types.js
8383
+ // ../media-studio-core/src/sessions/types.ts
8285
8384
  var SessionSourceSchema = _enum(["web", "cli"]);
8286
8385
  var ApiResponseBaseSchema = object({
8287
8386
  success: boolean2(),
@@ -8396,7 +8495,7 @@ var PrewarmErrorSchema = object({
8396
8495
  message: string2()
8397
8496
  });
8398
8497
 
8399
- // ../media-studio-core/dist/client/sessions.js
8498
+ // ../media-studio-core/src/client/sessions.ts
8400
8499
  var PREWARM_TIMEOUT_MS = 60000;
8401
8500
  var MAX_NETWORK_RETRIES = 2;
8402
8501
  var RETRY_BASE_DELAY_MS = 500;
@@ -8573,7 +8672,71 @@ class ClientSessions {
8573
8672
  }
8574
8673
  }
8575
8674
 
8576
- // ../media-studio-core/dist/client/client.js
8675
+ // ../media-studio-core/src/client/skills.ts
8676
+ var LIST_PAGE_SIZE = 50;
8677
+ var MAX_LIST_PAGES = 100;
8678
+
8679
+ class ClientSkills {
8680
+ baseUrl;
8681
+ apiKey;
8682
+ customFetch;
8683
+ constructor(baseUrl, apiKey, customFetch) {
8684
+ this.baseUrl = baseUrl;
8685
+ this.apiKey = apiKey;
8686
+ this.customFetch = customFetch ?? fetch;
8687
+ }
8688
+ async list(query) {
8689
+ const url = new URL(`${this.baseUrl}/api/skills`);
8690
+ if (query?.limit !== undefined) {
8691
+ url.searchParams.set("limit", String(query.limit));
8692
+ }
8693
+ if (query?.offset !== undefined) {
8694
+ url.searchParams.set("offset", String(query.offset));
8695
+ }
8696
+ const parsed = await this.request(url.toString(), { method: "GET", headers: this.headers() }, SkillListResponseSchema, (status) => `Skill catalog request failed with status ${status}`);
8697
+ return parsed.skills;
8698
+ }
8699
+ async listAll() {
8700
+ const all = [];
8701
+ const seenIds = new Set;
8702
+ for (let pages = 0;pages < MAX_LIST_PAGES; pages += 1) {
8703
+ const page = await this.list({
8704
+ limit: LIST_PAGE_SIZE,
8705
+ offset: all.length
8706
+ });
8707
+ const fresh = page.filter((skill) => !seenIds.has(skill.id));
8708
+ if (fresh.length === 0)
8709
+ break;
8710
+ for (const skill of fresh)
8711
+ seenIds.add(skill.id);
8712
+ all.push(...fresh);
8713
+ }
8714
+ return all;
8715
+ }
8716
+ async setEnabled(skillId, enabled) {
8717
+ const parsed = await this.request(`${this.baseUrl}/api/skills/enabled`, {
8718
+ method: "POST",
8719
+ headers: { ...this.headers(), "Content-Type": "application/json" },
8720
+ body: JSON.stringify({ skillId, enabled })
8721
+ }, SingleSkillResponseSchema, (status) => `Skill enabled-state request failed with status ${status}`);
8722
+ return parsed.skill;
8723
+ }
8724
+ headers() {
8725
+ return { Authorization: `Bearer ${this.apiKey}` };
8726
+ }
8727
+ async request(url, init, schema, fallbackMessage) {
8728
+ const response = await this.customFetch(url, init);
8729
+ if (!response.ok) {
8730
+ const rawBody = await response.text();
8731
+ const message = extractUpstreamErrorMessage(rawBody) || fallbackMessage(response.status);
8732
+ throw new ChatUpstreamError(message, response.status, rawBody);
8733
+ }
8734
+ const rawJson = await response.json();
8735
+ return schema.parse(rawJson);
8736
+ }
8737
+ }
8738
+
8739
+ // ../media-studio-core/src/client/client.ts
8577
8740
  class MediaStudioClient {
8578
8741
  baseUrl;
8579
8742
  apiKey;
@@ -8581,6 +8744,7 @@ class MediaStudioClient {
8581
8744
  auth;
8582
8745
  chat;
8583
8746
  sessions;
8747
+ skills;
8584
8748
  constructor(options) {
8585
8749
  const trimmedKey = options.apiKey.trim();
8586
8750
  if (!trimmedKey) {
@@ -8592,6 +8756,7 @@ class MediaStudioClient {
8592
8756
  this.auth = new ClientAuth(this.baseUrl, this.apiKey, options.fetch);
8593
8757
  this.chat = new ClientChat(this.baseUrl, this.apiKey, this.locale, options.fetch);
8594
8758
  this.sessions = new ClientSessions(this.baseUrl, this.apiKey, options.fetch);
8759
+ this.skills = new ClientSkills(this.baseUrl, this.apiKey, options.fetch);
8595
8760
  }
8596
8761
  createSession(options) {
8597
8762
  return new ChatSession(this.chat, options);
@@ -8601,7 +8766,7 @@ function createMediaStudioClient(options) {
8601
8766
  return new MediaStudioClient(options);
8602
8767
  }
8603
8768
 
8604
- // ../media-studio-core/dist/format/tool-formatter.js
8769
+ // ../media-studio-core/src/format/tool-formatter.ts
8605
8770
  var ASPECT_RATIO_PATTERN = /aspect_ratio:\s*([^\s,;]+)/i;
8606
8771
  var MODEL_PATTERN = /(?:^|\n)\s*-\s*model:\s*([^\s,;]+)/i;
8607
8772
  var DURATION_PATTERN = /(?:^|\n)\s*-\s*duration:\s*([^\s,;]+)/i;
@@ -8691,8 +8856,8 @@ function formatToolMilestone(toolName, args) {
8691
8856
  }
8692
8857
  };
8693
8858
  }
8694
- if (normalized in CREATE_TOOL_META) {
8695
- const meta = CREATE_TOOL_META[normalized];
8859
+ const meta = CREATE_TOOL_META[normalized];
8860
+ if (meta) {
8696
8861
  const model = isString(input.model) ? input.model : extractFromPrompt(input.prompt, MODEL_PATTERN);
8697
8862
  const aspectRatio = isString(input.aspect_ratio) ? input.aspect_ratio : extractFromPrompt(input.prompt, ASPECT_RATIO_PATTERN);
8698
8863
  const duration = isString(input.duration) ? input.duration : typeof input.duration === "number" ? `${input.duration}s` : isString(input.seconds) ? `${input.seconds}s` : extractFromPrompt(input.prompt, DURATION_PATTERN);
@@ -8865,7 +9030,9 @@ function formatToolMilestone(toolName, args) {
8865
9030
  };
8866
9031
  }
8867
9032
  if (normalized === "skill" || normalized === "skill_created") {
8868
- const skillName = isString(input.skill_name) ? input.skill_name : isString(input.name) ? input.name : "";
9033
+ const dir = isString(input.skills_dir) ? input.skills_dir.replace(/[/\\]+$/, "") : "";
9034
+ const dirName = dir.slice(Math.max(dir.lastIndexOf("/"), dir.lastIndexOf("\\")) + 1);
9035
+ const skillName = isString(input.skill) ? input.skill : dirName;
8869
9036
  return {
8870
9037
  rawName: toolName,
8871
9038
  normalizedName: normalized,
@@ -8885,6 +9052,72 @@ function formatToolMilestone(toolName, args) {
8885
9052
  };
8886
9053
  }
8887
9054
 
9055
+ // ../media-studio-core/src/skills/activation.ts
9056
+ var SKILL_ACTIVATION_MARKER = "%%";
9057
+ var SKILL_ACTIVATION_RE = /%%(\/[^%\s]+)%%/g;
9058
+ function formatSkillActivation(command) {
9059
+ return `${SKILL_ACTIVATION_MARKER}${command}${SKILL_ACTIVATION_MARKER}`;
9060
+ }
9061
+ function findSkillActivationMatches(text) {
9062
+ return Array.from(text.matchAll(SKILL_ACTIVATION_RE)).flatMap((match, index) => {
9063
+ const start = match.index;
9064
+ const command = match[1];
9065
+ if (start === undefined || !command)
9066
+ return [];
9067
+ return [
9068
+ {
9069
+ start,
9070
+ end: start + match[0].length,
9071
+ command,
9072
+ displayText: command,
9073
+ id: `skill-${command.slice(1)}-${index}`
9074
+ }
9075
+ ];
9076
+ });
9077
+ }
9078
+
9079
+ // ../media-studio-core/src/skills/compose-turn.ts
9080
+ function applySkillActivations(text, spans) {
9081
+ const ordered = spans.toSorted((a, b) => b.start - a.start);
9082
+ let composed = text;
9083
+ for (const span of ordered) {
9084
+ composed = composed.slice(0, span.start) + formatSkillActivation(span.command) + composed.slice(span.end);
9085
+ }
9086
+ return composed;
9087
+ }
9088
+ function composeSkillTurn(prompt, spans, skills) {
9089
+ return {
9090
+ text: applySkillActivations(prompt, spans),
9091
+ skillUrls: skills.map((skill) => skill.link).filter((link) => link !== "")
9092
+ };
9093
+ }
9094
+
9095
+ // ../media-studio-core/src/skills/ensure-enabled.ts
9096
+ async function ensureSkillEnabled(skill, deps) {
9097
+ if (skill.enabled)
9098
+ return true;
9099
+ let userSkillId = skill.userSkillId;
9100
+ if (!userSkillId) {
9101
+ if (!deps.add) {
9102
+ deps.onFailure();
9103
+ return false;
9104
+ }
9105
+ try {
9106
+ userSkillId = await deps.add();
9107
+ } catch {
9108
+ deps.onFailure();
9109
+ return false;
9110
+ }
9111
+ }
9112
+ try {
9113
+ await deps.enable(userSkillId);
9114
+ return true;
9115
+ } catch {
9116
+ deps.onFailure();
9117
+ return false;
9118
+ }
9119
+ }
9120
+
8888
9121
  // ../../node_modules/consola/dist/core.mjs
8889
9122
  var LogLevels = {
8890
9123
  silent: Number.NEGATIVE_INFINITY,
@@ -9870,6 +10103,121 @@ import {
9870
10103
  import { homedir } from "node:os";
9871
10104
  import { join as join2 } from "node:path";
9872
10105
 
10106
+ // node_modules/nanoid/url-alphabet/index.js
10107
+ var urlAlphabet2 = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
10108
+
10109
+ // node_modules/nanoid/index.js
10110
+ var GET_RANDOM_LIMIT2 = 65536;
10111
+ function fillRandom2(buffer) {
10112
+ let from = 0;
10113
+ while (from < buffer.length) {
10114
+ let to = Math.min(from + GET_RANDOM_LIMIT2, buffer.length);
10115
+ crypto.getRandomValues(buffer.subarray(from, to));
10116
+ from = to;
10117
+ }
10118
+ }
10119
+ function random4(bytes) {
10120
+ bytes |= 0;
10121
+ if (bytes < 0)
10122
+ throw new RangeError("Wrong ID size");
10123
+ let buffer = Buffer.allocUnsafe(bytes);
10124
+ fillRandom2(buffer);
10125
+ return buffer;
10126
+ }
10127
+ function customRandom2(alphabet, defaultSize, getRandom) {
10128
+ let safeByteCutoff = 256 - 256 % alphabet.length;
10129
+ if (safeByteCutoff === 256) {
10130
+ let mask = alphabet.length - 1;
10131
+ return (size = defaultSize) => {
10132
+ if (!size)
10133
+ return "";
10134
+ let id = "";
10135
+ while (true) {
10136
+ let bytes = getRandom(size);
10137
+ let i = size;
10138
+ while (i--) {
10139
+ id += alphabet[bytes[i] & mask];
10140
+ if (id.length >= size)
10141
+ return id;
10142
+ }
10143
+ }
10144
+ };
10145
+ }
10146
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
10147
+ return (size = defaultSize) => {
10148
+ if (!size)
10149
+ return "";
10150
+ let id = "";
10151
+ while (true) {
10152
+ let bytes = getRandom(step);
10153
+ let i = step;
10154
+ while (i--) {
10155
+ if (bytes[i] < safeByteCutoff) {
10156
+ id += alphabet[bytes[i] % alphabet.length];
10157
+ if (id.length >= size)
10158
+ return id;
10159
+ }
10160
+ }
10161
+ }
10162
+ };
10163
+ }
10164
+ var POOL_MAX2 = GET_RANDOM_LIMIT2 / 2;
10165
+ function customAlphabet2(alphabet, defaultSize = 21) {
10166
+ if (typeof alphabet !== "string" || !alphabet || alphabet.length > 256) {
10167
+ return customRandom2(alphabet, defaultSize, random4);
10168
+ }
10169
+ for (let i = 0;i < alphabet.length; i++) {
10170
+ if (alphabet.charCodeAt(i) > 255) {
10171
+ return customRandom2(alphabet, defaultSize, random4);
10172
+ }
10173
+ }
10174
+ let charCodes = Uint8Array.from(alphabet, (str) => {
10175
+ return str.charCodeAt(0);
10176
+ });
10177
+ let alphabetLen = alphabet.length;
10178
+ let mask = (2 << 31 - Math.clz32(alphabetLen - 1 | 1)) - 1;
10179
+ let pool = "";
10180
+ let poolOffset = 0;
10181
+ let poolNext = 0;
10182
+ return (size = defaultSize) => {
10183
+ size |= 0;
10184
+ if (size < 0)
10185
+ throw new RangeError("Wrong ID size");
10186
+ if (size === 0)
10187
+ return "";
10188
+ if (poolOffset + size > pool.length) {
10189
+ let target = Math.max(poolNext, size);
10190
+ poolNext = Math.min(target * 16, POOL_MAX2);
10191
+ let buffer = Buffer.allocUnsafe(target);
10192
+ if (mask === alphabetLen - 1) {
10193
+ fillRandom2(buffer);
10194
+ for (let i = 0;i < target; i++) {
10195
+ buffer[i] = charCodes[buffer[i] & mask];
10196
+ }
10197
+ } else {
10198
+ let randomBytes = Buffer.allocUnsafe(Math.ceil(1.6 * (mask + 1) * target / alphabetLen));
10199
+ let accepted = 0;
10200
+ while (accepted < target) {
10201
+ fillRandom2(randomBytes);
10202
+ for (let i = 0;i < randomBytes.length; i++) {
10203
+ let index = randomBytes[i] & mask;
10204
+ if (index < alphabetLen) {
10205
+ buffer[accepted++] = charCodes[index];
10206
+ if (accepted === target)
10207
+ break;
10208
+ }
10209
+ }
10210
+ }
10211
+ }
10212
+ pool = buffer.toString("latin1");
10213
+ poolOffset = 0;
10214
+ }
10215
+ poolOffset += size;
10216
+ return pool.substring(poolOffset - size, poolOffset);
10217
+ };
10218
+ }
10219
+ var nanoid3 = customAlphabet2(urlAlphabet2);
10220
+
9873
10221
  // node_modules/zod/v4/core/util.js
9874
10222
  function getEnumValues2(entries) {
9875
10223
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -10754,7 +11102,7 @@ var cuid22 = /^[0-9a-z]+$/;
10754
11102
  var ulid2 = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
10755
11103
  var xid2 = /^[0-9a-vA-V]{20}$/;
10756
11104
  var ksuid2 = /^[A-Za-z0-9]{27}$/;
10757
- var nanoid3 = /^[a-zA-Z0-9_-]{21}$/;
11105
+ var nanoid4 = /^[a-zA-Z0-9_-]{21}$/;
10758
11106
  function nanoidOfLength2(length) {
10759
11107
  return new RegExp(`^[a-zA-Z0-9_-]{${length}}$`);
10760
11108
  }
@@ -11508,7 +11856,7 @@ var $ZodEmoji2 = /* @__PURE__ */ $constructor2("$ZodEmoji", (inst, def) => {
11508
11856
  var $ZodNanoID2 = /* @__PURE__ */ $constructor2("$ZodNanoID", (inst, def) => {
11509
11857
  if (def.length !== undefined && (!Number.isInteger(def.length) || def.length < 1))
11510
11858
  throw new Error(`Invalid nanoid length: ${def.length}`);
11511
- def.pattern ?? (def.pattern = def.length === undefined ? nanoid3 : nanoidOfLength2(def.length));
11859
+ def.pattern ?? (def.pattern = def.length === undefined ? nanoid4 : nanoidOfLength2(def.length));
11512
11860
  $ZodStringFormat2.init(inst, def);
11513
11861
  });
11514
11862
  var $ZodCUID3 = /* @__PURE__ */ $constructor2("$ZodCUID", (inst, def) => {
@@ -15156,7 +15504,7 @@ function saveAuthFile(data) {
15156
15504
  const dir = getConfigDir();
15157
15505
  mkdirSync(dir, { recursive: true });
15158
15506
  const authPath = getAuthFilePath();
15159
- const tmpPath = `${authPath}.${nanoid2()}.tmp`;
15507
+ const tmpPath = `${authPath}.${nanoid3()}.tmp`;
15160
15508
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8");
15161
15509
  chmodSync(tmpPath, 384);
15162
15510
  renameSync(tmpPath, authPath);
@@ -15179,7 +15527,7 @@ function saveConfigFile(data) {
15179
15527
  const dir = getConfigDir();
15180
15528
  mkdirSync(dir, { recursive: true });
15181
15529
  const configPath = getConfigFilePath();
15182
- const tmpPath = `${configPath}.${nanoid2()}.tmp`;
15530
+ const tmpPath = `${configPath}.${nanoid3()}.tmp`;
15183
15531
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8");
15184
15532
  chmodSync(tmpPath, 384);
15185
15533
  renameSync(tmpPath, configPath);
@@ -15301,9 +15649,11 @@ async function* startChatStream(opts) {
15301
15649
  apiKey: opts.apiKey,
15302
15650
  fetch: opts.fetch
15303
15651
  });
15304
- const stream = await client.chat.stream(opts.prompt, {
15652
+ const stream = await client.chat.stream({
15305
15653
  sessionId: opts.sessionId,
15654
+ messages: [{ role: "user", content: opts.prompt }],
15306
15655
  chatTaskId: opts.chatTaskId,
15656
+ ...opts.skillUrls && opts.skillUrls.length > 0 ? { skillUrls: opts.skillUrls } : {},
15307
15657
  signal: opts.signal,
15308
15658
  maxRetries: opts.retries,
15309
15659
  initialRetryDelayMs: opts.initialRetryDelayMs
@@ -15320,7 +15670,7 @@ function isSessionGoneError(err) {
15320
15670
  return err.body.includes(ERR_SESSION_EXPIRED);
15321
15671
  }
15322
15672
  async function provisionSession(options) {
15323
- const sessionId = nanoid2();
15673
+ const sessionId = nanoid3();
15324
15674
  const client = createMediaStudioClient({
15325
15675
  baseUrl: options.apiBaseUrl,
15326
15676
  apiKey: options.apiKey,
@@ -15338,6 +15688,99 @@ async function provisionSession(options) {
15338
15688
  })).with({ status: "failed" }, () => ({ status: "failed" })).exhaustive();
15339
15689
  }
15340
15690
 
15691
+ // src/lib/skill-refs.ts
15692
+ var LEADING_COMMAND_RE = /^\s*(\/[A-Za-z0-9][\w.-]*)(?=\s|$)/;
15693
+ function findSkillRefSpans(prompt) {
15694
+ const spans = findSkillActivationMatches(prompt).map(({ start, end, command }) => ({ start, end, command }));
15695
+ const leading = LEADING_COMMAND_RE.exec(prompt);
15696
+ const command = leading?.[1];
15697
+ if (leading && command) {
15698
+ const end = leading[0].length;
15699
+ spans.unshift({ start: end - command.length, end, command });
15700
+ }
15701
+ return spans.sort((a, b) => a.start - b.start);
15702
+ }
15703
+
15704
+ // src/services/skills.ts
15705
+ class SkillResolutionError extends Error {
15706
+ }
15707
+ function stripControlChars(text) {
15708
+ return [...text].filter((ch) => {
15709
+ const code = ch.codePointAt(0) ?? 0;
15710
+ return code > 31 && (code < 127 || code > 159);
15711
+ }).join("");
15712
+ }
15713
+ function describeUnknownSkill(command, catalog) {
15714
+ const shown = stripControlChars(command);
15715
+ if (catalog.length === 0) {
15716
+ return `Unknown skill "${shown}": your skill library is empty. Add skills in the Media Studio web app first.`;
15717
+ }
15718
+ const available = catalog.map((skill) => stripControlChars(skill.command)).sort().join(", ");
15719
+ return `Unknown skill "${shown}". Available: ${available}`;
15720
+ }
15721
+ function describeNotInLibrary(skill) {
15722
+ return `Skill "${stripControlChars(skill.command)}" is disabled and not in your library yet, so the CLI ` + `cannot enable it. Add it on the Media Studio web app's Skills page, then retry.`;
15723
+ }
15724
+ function describeEnableFailure(skill, cause) {
15725
+ const reason = stripControlChars(cause instanceof Error ? cause.message : String(cause));
15726
+ return `Failed to enable skill "${stripControlChars(skill.command)}": ${reason}
15727
+ ` + `The turn was aborted because upstream ignores disabled skills — sending it ` + `anyway would download the package and then silently do nothing.`;
15728
+ }
15729
+ async function ensureUsable(client, skill) {
15730
+ let cause;
15731
+ const ensured = await ensureSkillEnabled({ enabled: skill.enabled, userSkillId: skill.id }, {
15732
+ enable: async (skillId) => {
15733
+ try {
15734
+ await client.skills.setEnabled(skillId, true);
15735
+ } catch (err) {
15736
+ cause = err;
15737
+ throw err;
15738
+ }
15739
+ },
15740
+ onFailure: () => {}
15741
+ });
15742
+ if (ensured)
15743
+ return;
15744
+ throw new SkillResolutionError(describeEnableFailure(skill, cause));
15745
+ }
15746
+ async function resolvePromptSkills(options) {
15747
+ const spans = findSkillRefSpans(options.prompt);
15748
+ if (spans.length === 0) {
15749
+ return { text: options.prompt, skillUrls: [], autoEnabled: [] };
15750
+ }
15751
+ const client = createMediaStudioClient({
15752
+ baseUrl: options.apiBaseUrl,
15753
+ apiKey: options.apiKey,
15754
+ fetch: options.fetch
15755
+ });
15756
+ const catalog = await client.skills.listAll();
15757
+ const catalogByCommand = new Map;
15758
+ for (const skill of catalog) {
15759
+ if (!catalogByCommand.has(skill.command)) {
15760
+ catalogByCommand.set(skill.command, skill);
15761
+ }
15762
+ }
15763
+ const matched = [];
15764
+ for (const command of new Set(spans.map((span) => span.command))) {
15765
+ const skill = catalogByCommand.get(command);
15766
+ if (!skill) {
15767
+ throw new SkillResolutionError(describeUnknownSkill(command, catalog));
15768
+ }
15769
+ if (!skill.added) {
15770
+ throw new SkillResolutionError(describeNotInLibrary(skill));
15771
+ }
15772
+ matched.push(skill);
15773
+ }
15774
+ const autoEnabled = [];
15775
+ for (const skill of matched) {
15776
+ if (!skill.enabled)
15777
+ autoEnabled.push(stripControlChars(skill.command));
15778
+ await ensureUsable(client, skill);
15779
+ }
15780
+ const composed = composeSkillTurn(options.prompt, spans, matched);
15781
+ return { text: composed.text, skillUrls: composed.skillUrls, autoEnabled };
15782
+ }
15783
+
15341
15784
  // src/commands/chat.ts
15342
15785
  var PROVISION_STAGE_LABELS = {
15343
15786
  creating: "Creating session...",
@@ -15402,7 +15845,7 @@ async function resolveChatSession(options) {
15402
15845
  return { sessionId, recreateOnGone: false };
15403
15846
  }
15404
15847
  async function streamTurn(options) {
15405
- const { args, sessionId, spinner } = options;
15848
+ const { args, sessionId, spinner, autoEnabled } = options;
15406
15849
  if (!args.json) {
15407
15850
  spinner.start("Connecting to Media Studio...");
15408
15851
  }
@@ -15410,7 +15853,8 @@ async function streamTurn(options) {
15410
15853
  apiBaseUrl: options.apiBaseUrl,
15411
15854
  apiKey: options.apiKey,
15412
15855
  sessionId,
15413
- prompt: args.prompt
15856
+ prompt: options.prompt,
15857
+ skillUrls: options.skillUrls
15414
15858
  });
15415
15859
  let fullText = "";
15416
15860
  let reasoning = "";
@@ -15486,6 +15930,7 @@ async function streamTurn(options) {
15486
15930
  text: fullText,
15487
15931
  steps,
15488
15932
  media: mediaAssets,
15933
+ ...autoEnabled.length > 0 ? { autoEnabled } : {},
15489
15934
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
15490
15935
  };
15491
15936
  process.stdout.write(`${JSON.stringify(output, null, 2)}
@@ -15506,6 +15951,7 @@ async function streamTurn(options) {
15506
15951
  text: fullText,
15507
15952
  steps,
15508
15953
  media: mediaAssets,
15954
+ ...autoEnabled.length > 0 ? { autoEnabled } : {},
15509
15955
  ...args["show-reasoning"] && reasoning ? { reasoning } : {}
15510
15956
  };
15511
15957
  process.stdout.write(`${JSON.stringify(output, null, 2)}
@@ -15568,6 +16014,14 @@ var chatCommand = defineCommand({
15568
16014
  try {
15569
16015
  const apiKey = resolveApiKey();
15570
16016
  const apiBaseUrl = resolveApiBaseUrl();
16017
+ const { text, skillUrls, autoEnabled } = await resolvePromptSkills({
16018
+ prompt: args.prompt,
16019
+ apiBaseUrl,
16020
+ apiKey
16021
+ });
16022
+ if (autoEnabled.length > 0 && args.json !== true) {
16023
+ consola.info(`Auto-enabled for this turn: ${autoEnabled.join(", ")}`);
16024
+ }
15571
16025
  const resolved = await resolveChatSession({
15572
16026
  sessionFlag: args.session,
15573
16027
  apiBaseUrl,
@@ -15577,11 +16031,14 @@ var chatCommand = defineCommand({
15577
16031
  });
15578
16032
  await runTurn({
15579
16033
  args,
16034
+ prompt: text,
15580
16035
  sessionId: resolved.sessionId,
15581
16036
  recreateOnGone: resolved.recreateOnGone,
15582
16037
  apiBaseUrl,
15583
16038
  apiKey,
15584
- spinner
16039
+ spinner,
16040
+ skillUrls,
16041
+ autoEnabled
15585
16042
  });
15586
16043
  } catch (err) {
15587
16044
  spinner.stop();
@@ -16521,7 +16978,7 @@ import fs3 from "node:fs";
16521
16978
  // ../../node_modules/is-inside-container/index.js
16522
16979
  import fs2 from "node:fs";
16523
16980
 
16524
- // ../../node_modules/is-inside-container/node_modules/is-docker/index.js
16981
+ // ../../node_modules/is-docker/index.js
16525
16982
  import fs from "node:fs";
16526
16983
  var isDockerCached;
16527
16984
  function hasDockerEnv() {
@@ -17185,7 +17642,7 @@ function shutdownServer(server) {
17185
17642
  server.close();
17186
17643
  }
17187
17644
  async function runLoopbackLogin(opts) {
17188
- const state = opts.state ?? nanoid2();
17645
+ const state = opts.state ?? nanoid3();
17189
17646
  const opener = opts.openBrowser ?? defaultOpenBrowser;
17190
17647
  const timeoutMs = opts.timeoutMs ?? 120000;
17191
17648
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@302ai/media-studio-cli",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
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",