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