@mindstudio-ai/agent 0.0.16 → 0.0.18

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 CHANGED
@@ -46,8 +46,8 @@ Every method is fully typed — your editor will autocomplete available paramete
46
46
  ### CLI
47
47
 
48
48
  ```bash
49
- # Set your API key
50
- export MINDSTUDIO_API_KEY=your-api-key
49
+ # Authenticate (opens browser, saves key locally)
50
+ mindstudio login
51
51
 
52
52
  # Execute a step with named flags
53
53
  mindstudio generate-image --prompt "A mountain landscape at sunset"
@@ -96,9 +96,15 @@ All 120+ step methods are exposed as MCP tools with full JSON Schema input defin
96
96
 
97
97
  ## Authentication
98
98
 
99
- The SDK supports two authentication modes:
99
+ The fastest way to authenticate is the interactive login:
100
100
 
101
- **API Key** — for external apps, scripts, and CLI usage:
101
+ ```bash
102
+ mindstudio login
103
+ ```
104
+
105
+ This opens your browser, authenticates with MindStudio, and saves your API key to `~/.mindstudio/config.json`. All subsequent CLI and SDK usage will pick it up automatically.
106
+
107
+ You can also authenticate via environment variable or constructor parameter:
102
108
 
103
109
  ```typescript
104
110
  // Pass directly
@@ -109,15 +115,19 @@ const agent = new MindStudioAgent({ apiKey: 'your-api-key' });
109
115
  const agent = new MindStudioAgent();
110
116
  ```
111
117
 
112
- **Managed mode**automatically available inside MindStudio custom functions:
118
+ MindStudio routes to the correct AI provider (OpenAI, Google, Anthropic, etc.) server-side you do not need separate provider API keys.
113
119
 
114
- ```typescript
115
- // Inside a MindStudio custom function, auth and base URL are automatic
116
- // (CALLBACK_TOKEN and REMOTE_HOSTNAME are set by the runtime)
117
- const agent = new MindStudioAgent();
120
+ Other auth commands:
121
+
122
+ ```bash
123
+ # Check current auth status and verify credentials
124
+ mindstudio whoami
125
+
126
+ # Clear stored credentials
127
+ mindstudio logout
118
128
  ```
119
129
 
120
- Resolution order: constructor `apiKey` > `MINDSTUDIO_API_KEY` env > `CALLBACK_TOKEN` env.
130
+ Resolution order: constructor `apiKey` > `MINDSTUDIO_API_KEY` env > `~/.mindstudio/config.json` > `CALLBACK_TOKEN` env.
121
131
 
122
132
  ## Thread persistence
123
133
 
@@ -248,7 +258,7 @@ const { services } = await agent.listConnectors();
248
258
 
249
259
  ```typescript
250
260
  const agent = new MindStudioAgent({
251
- // API key (or set MINDSTUDIO_API_KEY env var)
261
+ // API key (or set MINDSTUDIO_API_KEY env var, or run `mindstudio login`)
252
262
  apiKey: 'your-api-key',
253
263
 
254
264
  // Base URL (or set MINDSTUDIO_BASE_URL env var)
@@ -322,6 +332,9 @@ import { blockTypeAliases } from '@mindstudio-ai/agent';
322
332
  Usage: mindstudio <command | method> [options]
323
333
 
324
334
  Commands:
335
+ login Authenticate with MindStudio (opens browser)
336
+ logout Clear stored credentials
337
+ whoami Show current authentication status
325
338
  <method> [json | --flags] Execute a step method
326
339
  exec <method> [json | --flags] Execute a step method (same as above)
327
340
  list [--json] List available methods
package/dist/cli.js CHANGED
@@ -201,6 +201,27 @@ var init_metadata = __esm({
201
201
  inputSchema: { "type": "object", "properties": { "input": { "type": "string", "description": "Text to scan for personally identifiable information" }, "language": { "type": "string", "description": 'Language code of the input text (e.g. "en")' }, "entities": { "type": "array", "items": { "type": "string" }, "description": 'PII entity types to scan for (e.g. ["PHONE_NUMBER", "EMAIL_ADDRESS"]). Empty array means nothing is scanned.' }, "detectedStepId": { "type": "string", "description": "Step to transition to if PII is detected (workflow mode)" }, "notDetectedStepId": { "type": "string", "description": "Step to transition to if no PII is detected (workflow mode)" }, "outputLogVariable": { "type": "string", "description": "Variable name to store the raw detection results" } }, "required": ["input", "language", "entities"] },
202
202
  outputSchema: { "type": "object", "properties": { "detected": { "type": "boolean", "description": "Whether any PII was found in the input text" }, "detections": { "type": "array", "items": { "type": "object", "properties": { "entity_type": { "type": "string", "description": 'PII entity type (e.g. "PHONE_NUMBER", "EMAIL_ADDRESS", "PERSON")' }, "start": { "type": "number", "description": "Start character index in the input text" }, "end": { "type": "number", "description": "End character index in the input text" }, "score": { "type": "number", "description": "Confidence score between 0 and 1" } }, "required": ["entity_type", "start", "end", "score"] }, "description": "List of detected PII entities with type, location, and confidence" } }, "required": ["detected", "detections"] }
203
203
  },
204
+ "discordEditMessage": {
205
+ stepType: "discordEditMessage",
206
+ description: "Edit a previously sent Discord channel message. Use with the message ID returned by Send Discord Message.",
207
+ usageNotes: "- Only messages sent by the bot can be edited.\n- The messageId is returned by the Send Discord Message step.\n- Optionally attach a file by providing a URL to attachmentUrl. The file is downloaded and uploaded to Discord.\n- When editing with an attachment, the new attachment replaces any previous attachments on the message.\n- URLs in the text are automatically embedded by Discord (link previews for images, videos, etc.).",
208
+ inputSchema: { "type": "object", "properties": { "botToken": { "type": "string", "description": "Discord bot token for authentication" }, "channelId": { "type": "string", "description": "Discord channel ID containing the message" }, "messageId": { "type": "string", "description": "ID of the message to edit (returned by Send Discord Message)" }, "text": { "type": "string", "description": "New message text to replace the existing content" }, "attachmentUrl": { "type": "string", "description": "URL of a file to download and attach to the message (replaces any previous attachments)" } }, "required": ["botToken", "channelId", "messageId", "text"] },
209
+ outputSchema: { "description": "This step does not produce output data." }
210
+ },
211
+ "discordSendFollowUp": {
212
+ stepType: "discordSendFollowUp",
213
+ description: "Send a follow-up message to a Discord slash command interaction.",
214
+ usageNotes: "- Requires the applicationId and interactionToken from the Discord trigger variables.\n- Follow-up messages appear as new messages in the channel after the initial response.\n- Returns the sent message ID.\n- Interaction tokens expire after 15 minutes.\n- Optionally attach a file by providing a URL to attachmentUrl. The file is downloaded and uploaded to Discord.\n- URLs in the text are automatically embedded by Discord (link previews for images, videos, etc.).",
215
+ inputSchema: { "type": "object", "properties": { "applicationId": { "type": "string", "description": "Discord application ID from the bot registration" }, "interactionToken": { "type": "string", "description": "Interaction token provided by the Discord trigger \u2014 expires after 15 minutes" }, "text": { "type": "string", "description": "Message text to send as a follow-up" }, "attachmentUrl": { "type": "string", "description": "URL of a file to download and attach to the message" } }, "required": ["applicationId", "interactionToken", "text"] },
216
+ outputSchema: { "type": "object", "properties": { "messageId": { "type": "string", "description": "ID of the sent follow-up message" } }, "required": ["messageId"] }
217
+ },
218
+ "discordSendMessage": {
219
+ stepType: "discordSendMessage",
220
+ description: "Send a message to Discord \u2014 either edit the loading message or send a new channel message.",
221
+ usageNotes: '- mode "edit" replaces the loading message (interaction response) with the final result. Uses applicationId and interactionToken from trigger variables. No bot permissions required.\n- mode "send" sends a new message to a channel. Uses botToken and channelId from trigger variables. Returns a messageId that can be used with Edit Discord Message.\n- Optionally attach a file by providing a URL to attachmentUrl. The file is downloaded and uploaded to Discord.\n- URLs in the text are automatically embedded by Discord (link previews for images, videos, etc.).\n- Interaction tokens expire after 15 minutes.',
222
+ inputSchema: { "type": "object", "properties": { "mode": { "enum": ["edit", "send"], "type": "string", "description": '"edit" replaces the loading message, "send" sends a new channel message' }, "text": { "type": "string", "description": "Message text to send" }, "applicationId": { "type": "string", "description": 'Discord application ID from the bot registration (required for "reply" mode)' }, "interactionToken": { "type": "string", "description": 'Interaction token provided by the Discord trigger \u2014 expires after 15 minutes (required for "reply" mode)' }, "botToken": { "type": "string", "description": 'Discord bot token for authentication (required for "send" mode)' }, "channelId": { "type": "string", "description": 'Discord channel ID to send the message to (required for "send" mode)' }, "attachmentUrl": { "type": "string", "description": "URL of a file to download and attach to the message" } }, "required": ["mode", "text"] },
223
+ outputSchema: { "type": "object", "properties": { "messageId": { "type": "string", "description": 'ID of the sent Discord message, only present in "send" mode (use with Edit Discord Message)' } } }
224
+ },
204
225
  "downloadVideo": {
205
226
  stepType: "downloadVideo",
206
227
  description: "Download a video file",
@@ -528,9 +549,13 @@ var init_metadata = __esm({
528
549
  },
529
550
  "logic": {
530
551
  stepType: "logic",
531
- description: "Use an AI model to evaluate which condition from a list is most true, given a context prompt.",
532
- usageNotes: '- This is "fuzzy" logic evaluated by an AI model, not computational logic. The model picks the most accurate statement.\n- All possible cases must be specified \u2014 there is no default/fallback case.\n- Requires at least two cases.\n- In workflow mode, transitions to the destinationStepId of the winning case. In direct execution, returns the winning case ID and condition.',
533
- inputSchema: { "type": "object", "properties": { "context": { "type": "string", "description": "Prompt text providing context for the AI evaluation" }, "cases": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "id": { "type": "string", "description": "Unique case identifier" }, "condition": { "type": "string", "description": 'The statement to evaluate (e.g., "User selected a dog")' }, "destinationStepId": { "type": "string", "description": "Step to transition to if this case wins (workflow mode only)" } }, "required": ["id", "condition"] }, { "type": "string" }] }, "description": "List of conditions to evaluate (objects for managed UIs, strings for code)" } }, "required": ["context", "cases"], "description": "Configuration for the logic evaluation step" },
552
+ description: "Route execution to different branches based on AI evaluation, comparison operators, or workflow jumps.",
553
+ usageNotes: `- Supports two modes: "ai" (default) uses an AI model to pick the most accurate statement; "comparison" uses operator-based checks.
554
+ - In AI mode, the model picks the most accurate statement from the list. All possible cases must be specified.
555
+ - In comparison mode, the context is the left operand and each case's condition is the right operand. First matching case wins. Use operator "default" as a fallback.
556
+ - Requires at least two cases.
557
+ - Each case can transition to a step in the current workflow (destinationStepId) or jump to another workflow (destinationWorkflowId).`,
558
+ inputSchema: { "type": "object", "properties": { "mode": { "enum": ["ai", "comparison"], "type": "string", "description": "Evaluation mode: 'ai' for LLM-based, 'comparison' for operator-based. Default: 'ai'" }, "context": { "type": "string", "description": "AI mode: prompt context. Comparison mode: left operand (resolved via variables)." }, "cases": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "id": { "type": "string", "description": "Unique case identifier" }, "condition": { "type": "string", "description": "AI mode: statement to evaluate. Comparison mode: right operand value." }, "operator": { "enum": ["eq", "neq", "gt", "lt", "gte", "lte", "exists", "not_exists", "contains", "not_contains", "default"], "type": "string", "description": "Comparison operator (comparison mode only)" }, "destinationStepId": { "type": "string", "description": "Step to transition to if this case wins (workflow mode only)" }, "destinationWorkflowId": { "type": "string", "description": "Workflow to jump to if this case wins (uses that workflow's initial step)" } }, "required": ["id", "condition"] }, { "type": "string" }] }, "description": "List of conditions to evaluate (objects for managed UIs, strings for code)" }, "modelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Optional model settings override; uses the organization default if not specified (AI mode only)" } }, "required": ["context", "cases"], "description": "Configuration for the router step" },
534
559
  outputSchema: { "type": "object", "properties": { "selectedCase": { "type": "number", "description": "The index of the winning case" } }, "required": ["selectedCase"] }
535
560
  },
536
561
  "makeDotComRunScenario": {
@@ -845,9 +870,9 @@ var init_metadata = __esm({
845
870
  },
846
871
  "sendSMS": {
847
872
  stepType: "sendSMS",
848
- description: "Send an SMS text message to a phone number configured via OAuth connection.",
849
- usageNotes: "- User is responsible for configuring the connection to the number (MindStudio requires double opt-in to prevent spam)",
850
- inputSchema: { "type": "object", "properties": { "body": { "type": "string", "description": "SMS message body text" }, "connectionId": { "type": "string", "description": "OAuth connection ID for the recipient phone number" } }, "required": ["body"] },
873
+ description: "Send an SMS or MMS message to a phone number configured via OAuth connection.",
874
+ usageNotes: "- User is responsible for configuring the connection to the number (MindStudio requires double opt-in to prevent spam)\n- If mediaUrls are provided, the message is sent as MMS instead of SMS\n- MMS supports up to 10 media URLs (images, video, audio, PDF) with a 5MB limit per file\n- MMS is only supported on US and Canadian carriers; international numbers will receive SMS only (media silently dropped)",
875
+ inputSchema: { "type": "object", "properties": { "body": { "type": "string", "description": "SMS message body text" }, "connectionId": { "type": "string", "description": "OAuth connection ID for the recipient phone number" }, "mediaUrls": { "type": "array", "items": { "type": "string" }, "description": "Optional array of media URLs to send as MMS (up to 10, 5MB each)" } }, "required": ["body"] },
851
876
  outputSchema: { "description": "This step does not produce output data." }
852
877
  },
853
878
  "setRunTitle": {
@@ -864,6 +889,20 @@ var init_metadata = __esm({
864
889
  inputSchema: { "type": "object", "properties": { "value": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] } }, "required": ["value"], "description": "Configuration for the set variable step" },
865
890
  outputSchema: { "type": "object" }
866
891
  },
892
+ "telegramEditMessage": {
893
+ stepType: "telegramEditMessage",
894
+ description: "Edit a previously sent Telegram message. Use with the message ID returned by Send Telegram Message.",
895
+ usageNotes: '- Only text messages sent by the bot can be edited.\n- The messageId is returned by the Send Telegram Message step.\n- Common pattern: send a "Processing..." message, do work, then edit it with the result.',
896
+ inputSchema: { "type": "object", "properties": { "botToken": { "type": "string", "description": 'Telegram bot token in "botId:token" format' }, "chatId": { "type": "string", "description": "Telegram chat ID containing the message" }, "messageId": { "type": "string", "description": "ID of the message to edit" }, "text": { "type": "string", "description": "New message text (MarkdownV2 formatting supported)" } }, "required": ["botToken", "chatId", "messageId", "text"] },
897
+ outputSchema: { "description": "This step does not produce output data." }
898
+ },
899
+ "telegramReplyToMessage": {
900
+ stepType: "telegramReplyToMessage",
901
+ description: "Send a reply to a specific Telegram message. The reply will be visually threaded in the chat.",
902
+ usageNotes: "- Use the rawMessage.message_id from the incoming trigger variables to reply to the user's message.\n- Especially useful in group chats where replies provide context.\n- Returns the sent message ID, which can be used with Edit Telegram Message.",
903
+ inputSchema: { "type": "object", "properties": { "botToken": { "type": "string", "description": 'Telegram bot token in "botId:token" format' }, "chatId": { "type": "string", "description": "Telegram chat ID to send the reply to" }, "replyToMessageId": { "type": "string", "description": "ID of the message to reply to" }, "text": { "type": "string", "description": "Reply text (MarkdownV2 formatting supported)" } }, "required": ["botToken", "chatId", "replyToMessageId", "text"] },
904
+ outputSchema: { "type": "object", "properties": { "messageId": { "type": "number", "description": "ID of the sent reply message" } }, "required": ["messageId"] }
905
+ },
867
906
  "telegramSendAudio": {
868
907
  stepType: "telegramSendAudio",
869
908
  description: "Send an audio file to a Telegram chat as music or a voice note via a bot.",
@@ -888,9 +927,9 @@ var init_metadata = __esm({
888
927
  "telegramSendMessage": {
889
928
  stepType: "telegramSendMessage",
890
929
  description: "Send a text message to a Telegram chat via a bot.",
891
- usageNotes: '- Messages are sent using MarkdownV2 formatting. Special characters are auto-escaped.\n- botToken format is "botId:token" \u2014 both parts are required.',
930
+ usageNotes: '- Messages are sent using MarkdownV2 formatting. Special characters are auto-escaped.\n- botToken format is "botId:token" \u2014 both parts are required.\n- Returns the sent message ID, which can be used with Edit Telegram Message to update the message later.',
892
931
  inputSchema: { "type": "object", "properties": { "botToken": { "type": "string", "description": 'Telegram bot token in "botId:token" format' }, "chatId": { "type": "string", "description": "Telegram chat ID to send the message to" }, "text": { "type": "string", "description": "Message text to send (MarkdownV2 formatting supported)" } }, "required": ["botToken", "chatId", "text"] },
893
- outputSchema: { "description": "This step does not produce output data." }
932
+ outputSchema: { "type": "object", "properties": { "messageId": { "type": "number", "description": "ID of the sent Telegram message" } }, "required": ["messageId"] }
894
933
  },
895
934
  "telegramSendVideo": {
896
935
  stepType: "telegramSendVideo",
@@ -1159,6 +1198,48 @@ var init_rate_limit = __esm({
1159
1198
  }
1160
1199
  });
1161
1200
 
1201
+ // src/config.ts
1202
+ var config_exports = {};
1203
+ __export(config_exports, {
1204
+ clearConfig: () => clearConfig,
1205
+ getConfigPath: () => getConfigPath,
1206
+ loadConfig: () => loadConfig,
1207
+ saveConfig: () => saveConfig
1208
+ });
1209
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
1210
+ import { join } from "path";
1211
+ import { homedir } from "os";
1212
+ function getConfigPath() {
1213
+ return CONFIG_PATH;
1214
+ }
1215
+ function loadConfig() {
1216
+ try {
1217
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
1218
+ return JSON.parse(raw);
1219
+ } catch {
1220
+ return {};
1221
+ }
1222
+ }
1223
+ function saveConfig(config) {
1224
+ mkdirSync(CONFIG_DIR, { recursive: true });
1225
+ writeFileSync(
1226
+ CONFIG_PATH,
1227
+ JSON.stringify(config, null, 2) + "\n",
1228
+ "utf-8"
1229
+ );
1230
+ }
1231
+ function clearConfig() {
1232
+ saveConfig({});
1233
+ }
1234
+ var CONFIG_DIR, CONFIG_PATH;
1235
+ var init_config = __esm({
1236
+ "src/config.ts"() {
1237
+ "use strict";
1238
+ CONFIG_DIR = join(homedir(), ".mindstudio");
1239
+ CONFIG_PATH = join(CONFIG_DIR, "config.json");
1240
+ }
1241
+ });
1242
+
1162
1243
  // src/generated/steps.ts
1163
1244
  var steps_exports = {};
1164
1245
  __export(steps_exports, {
@@ -1244,6 +1325,15 @@ function applyStepMethods(AgentClass) {
1244
1325
  proto.detectPII = function(step, options) {
1245
1326
  return this.executeStep("detectPII", step, options);
1246
1327
  };
1328
+ proto.discordEditMessage = function(step, options) {
1329
+ return this.executeStep("discordEditMessage", step, options);
1330
+ };
1331
+ proto.discordSendFollowUp = function(step, options) {
1332
+ return this.executeStep("discordSendFollowUp", step, options);
1333
+ };
1334
+ proto.discordSendMessage = function(step, options) {
1335
+ return this.executeStep("discordSendMessage", step, options);
1336
+ };
1247
1337
  proto.downloadVideo = function(step, options) {
1248
1338
  return this.executeStep("downloadVideo", step, options);
1249
1339
  };
@@ -1520,6 +1610,12 @@ function applyStepMethods(AgentClass) {
1520
1610
  proto.setVariable = function(step, options) {
1521
1611
  return this.executeStep("setVariable", step, options);
1522
1612
  };
1613
+ proto.telegramEditMessage = function(step, options) {
1614
+ return this.executeStep("telegramEditMessage", step, options);
1615
+ };
1616
+ proto.telegramReplyToMessage = function(step, options) {
1617
+ return this.executeStep("telegramReplyToMessage", step, options);
1618
+ };
1523
1619
  proto.telegramSendAudio = function(step, options) {
1524
1620
  return this.executeStep("telegramSendAudio", step, options);
1525
1621
  };
@@ -1624,14 +1720,16 @@ __export(client_exports, {
1624
1720
  function sleep2(ms) {
1625
1721
  return new Promise((resolve) => setTimeout(resolve, ms));
1626
1722
  }
1627
- function resolveToken(provided) {
1723
+ function resolveToken(provided, config) {
1628
1724
  if (provided) return { token: provided, authType: "apiKey" };
1629
1725
  if (process.env.MINDSTUDIO_API_KEY)
1630
1726
  return { token: process.env.MINDSTUDIO_API_KEY, authType: "apiKey" };
1727
+ if (config?.apiKey)
1728
+ return { token: config.apiKey, authType: "apiKey" };
1631
1729
  if (process.env.CALLBACK_TOKEN)
1632
1730
  return { token: process.env.CALLBACK_TOKEN, authType: "internal" };
1633
1731
  throw new MindStudioError(
1634
- "No API key provided. Pass `apiKey` to the MindStudioAgent constructor, or set the MINDSTUDIO_API_KEY environment variable.",
1732
+ "No API key provided. Run `mindstudio login`, pass `apiKey` to the constructor, or set the MINDSTUDIO_API_KEY environment variable.",
1635
1733
  "missing_api_key",
1636
1734
  401
1637
1735
  );
@@ -1643,6 +1741,7 @@ var init_client = __esm({
1643
1741
  init_http();
1644
1742
  init_errors();
1645
1743
  init_rate_limit();
1744
+ init_config();
1646
1745
  init_steps();
1647
1746
  init_helpers();
1648
1747
  DEFAULT_BASE_URL = "https://v1.mindstudio-api.com";
@@ -1655,8 +1754,9 @@ var init_client = __esm({
1655
1754
  /** @internal */
1656
1755
  _threadId;
1657
1756
  constructor(options = {}) {
1658
- const { token, authType } = resolveToken(options.apiKey);
1659
- const baseUrl = options.baseUrl ?? process.env.MINDSTUDIO_BASE_URL ?? process.env.REMOTE_HOSTNAME ?? DEFAULT_BASE_URL;
1757
+ const config = loadConfig();
1758
+ const { token, authType } = resolveToken(options.apiKey, config);
1759
+ const baseUrl = options.baseUrl ?? process.env.MINDSTUDIO_BASE_URL ?? process.env.REMOTE_HOSTNAME ?? config.baseUrl ?? DEFAULT_BASE_URL;
1660
1760
  this._reuseThreadId = options.reuseThreadId ?? /^(true|1)$/i.test(process.env.MINDSTUDIO_REUSE_THREAD_ID ?? "");
1661
1761
  this._httpConfig = {
1662
1762
  baseUrl,
@@ -1858,7 +1958,7 @@ async function startMcpServer(options) {
1858
1958
  capabilities: { tools: {} },
1859
1959
  serverInfo: {
1860
1960
  name: "mindstudio-agent",
1861
- version: "0.0.16"
1961
+ version: "0.0.18"
1862
1962
  }
1863
1963
  });
1864
1964
  break;
@@ -2024,9 +2124,13 @@ var init_mcp = __esm({
2024
2124
 
2025
2125
  // src/cli.ts
2026
2126
  import { parseArgs } from "util";
2127
+ import { execSync } from "child_process";
2027
2128
  var HELP = `Usage: mindstudio <command | method> [options]
2028
2129
 
2029
2130
  Commands:
2131
+ login Authenticate with MindStudio (opens browser)
2132
+ logout Clear stored credentials
2133
+ whoami Show current authentication status
2030
2134
  <method> [json | --flags] Execute a step method (shorthand for exec)
2031
2135
  exec <method> [json | --flags] Execute a step method
2032
2136
  list [--json] List available methods
@@ -2048,6 +2152,7 @@ Options:
2048
2152
  --help Show this help
2049
2153
 
2050
2154
  Examples:
2155
+ mindstudio login
2051
2156
  mindstudio generate-image --prompt "a sunset"
2052
2157
  mindstudio generate-image --prompt "a sunset" --output-key imageUrl
2053
2158
  mindstudio generate-text --message "hello" --no-meta
@@ -2358,6 +2463,300 @@ async function cmdRun(appId, variables, options) {
2358
2463
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2359
2464
  }
2360
2465
  }
2466
+ var ansi = {
2467
+ cyan: (s) => `\x1B[36m${s}\x1B[0m`,
2468
+ cyanBright: (s) => `\x1B[96m${s}\x1B[0m`,
2469
+ cyanBold: (s) => `\x1B[96;1m${s}\x1B[0m`,
2470
+ dim: (s) => `\x1B[2m${s}\x1B[0m`,
2471
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
2472
+ greenBold: (s) => `\x1B[32;1m${s}\x1B[0m`,
2473
+ gray: (s) => `\x1B[90m${s}\x1B[0m`,
2474
+ bold: (s) => `\x1B[1m${s}\x1B[0m`
2475
+ };
2476
+ var UPDATE_CHECK_INTERVAL = 60 * 60 * 1e3;
2477
+ function isNewerVersion(current, latest) {
2478
+ const c = current.split(".").map(Number);
2479
+ const l = latest.split(".").map(Number);
2480
+ for (let i = 0; i < Math.max(c.length, l.length); i++) {
2481
+ const cv = c[i] ?? 0;
2482
+ const lv = l[i] ?? 0;
2483
+ if (lv > cv) return true;
2484
+ if (lv < cv) return false;
2485
+ }
2486
+ return false;
2487
+ }
2488
+ async function checkForUpdate() {
2489
+ const currentVersion = "0.0.18";
2490
+ if (!currentVersion) return null;
2491
+ try {
2492
+ const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2493
+ const config = loadConfig2();
2494
+ if (config._updateCheck) {
2495
+ const age = Date.now() - config._updateCheck.checkedAt;
2496
+ if (age < UPDATE_CHECK_INTERVAL) {
2497
+ return isNewerVersion(currentVersion, config._updateCheck.latestVersion) ? config._updateCheck.latestVersion : null;
2498
+ }
2499
+ }
2500
+ const res = await fetch(
2501
+ "https://registry.npmjs.org/@mindstudio-ai/agent/latest",
2502
+ { signal: AbortSignal.timeout(5e3) }
2503
+ );
2504
+ if (!res.ok) return null;
2505
+ const data = await res.json();
2506
+ const latestVersion = data.version;
2507
+ if (!latestVersion) return null;
2508
+ saveConfig2({
2509
+ ...config,
2510
+ _updateCheck: { latestVersion, checkedAt: Date.now() }
2511
+ });
2512
+ return isNewerVersion(currentVersion, latestVersion) ? latestVersion : null;
2513
+ } catch {
2514
+ return null;
2515
+ }
2516
+ }
2517
+ function printUpdateNotice(latestVersion) {
2518
+ const currentVersion = "0.0.18";
2519
+ process.stderr.write(
2520
+ `
2521
+ ${ansi.cyanBright("Update available")} ${ansi.gray(currentVersion + " \u2192")} ${ansi.cyanBold(latestVersion)}
2522
+ ${ansi.gray("Run")} npm install -g @mindstudio-ai/agent ${ansi.gray("to update")}
2523
+ `
2524
+ );
2525
+ }
2526
+ var LOGO = ` .=+-. :++.
2527
+ *@@@@@+ :%@@@@%:
2528
+ .%@@@@@@#..@@@@@@@=
2529
+ .*@@@@@@@--@@@@@@@#.**.
2530
+ *@@@@@@@.-@@@@@@@@.#@@*
2531
+ .#@@@@@@@-.@@@@@@@* #@@@@%.
2532
+ =@@@@@@@-.@@@@@@@#.-@@@@@@+
2533
+ :@@@@@@: +@@@@@#. .@@@@@@:
2534
+ .++: .-*-. .++:`;
2535
+ function printLogo() {
2536
+ const lines = LOGO.split("\n");
2537
+ for (const line of lines) {
2538
+ const colored = line.replace(
2539
+ /[^\s]/g,
2540
+ (ch) => ch === "." || ch === ":" || ch === "-" || ch === "+" || ch === "=" ? `\x1B[36m${ch}\x1B[0m` : `\x1B[96;1m${ch}\x1B[0m`
2541
+ );
2542
+ process.stderr.write(` ${colored}
2543
+ `);
2544
+ }
2545
+ }
2546
+ function openBrowser(url) {
2547
+ try {
2548
+ if (process.platform === "darwin") execSync(`open "${url}"`);
2549
+ else if (process.platform === "win32") execSync(`start "" "${url}"`);
2550
+ else execSync(`xdg-open "${url}"`);
2551
+ } catch {
2552
+ }
2553
+ }
2554
+ function sleep3(ms) {
2555
+ return new Promise((resolve) => setTimeout(resolve, ms));
2556
+ }
2557
+ function waitForKeypress() {
2558
+ return new Promise((resolve) => {
2559
+ if (!process.stdin.isTTY) {
2560
+ resolve();
2561
+ return;
2562
+ }
2563
+ process.stdin.setRawMode(true);
2564
+ process.stdin.resume();
2565
+ process.stdin.once("data", () => {
2566
+ process.stdin.setRawMode(false);
2567
+ process.stdin.pause();
2568
+ resolve();
2569
+ });
2570
+ });
2571
+ }
2572
+ function maskKey(key) {
2573
+ if (key.length <= 8) return "****";
2574
+ return key.slice(0, 4) + "..." + key.slice(-4);
2575
+ }
2576
+ var DEFAULT_BASE_URL2 = "https://v1.mindstudio-api.com";
2577
+ var SPINNER_FRAMES = [
2578
+ "\u28FE",
2579
+ "\u28FD",
2580
+ "\u28FB",
2581
+ "\u28BF",
2582
+ "\u287F",
2583
+ "\u28DF",
2584
+ "\u28EF",
2585
+ "\u28F7"
2586
+ ];
2587
+ async function cmdLogin(options) {
2588
+ const baseUrl = options.baseUrl ?? process.env.MINDSTUDIO_BASE_URL ?? process.env.REMOTE_HOSTNAME ?? DEFAULT_BASE_URL2;
2589
+ process.stderr.write("\n");
2590
+ printLogo();
2591
+ process.stderr.write("\n");
2592
+ const ver = "0.0.18";
2593
+ process.stderr.write(
2594
+ ` ${ansi.bold("MindStudio")} ${ansi.gray("CLI")}${ver ? " " + ansi.gray("v" + ver) : ""}
2595
+ `
2596
+ );
2597
+ process.stderr.write(
2598
+ ` ${ansi.gray("Connect your MindStudio account to get started.")}
2599
+
2600
+ `
2601
+ );
2602
+ process.stderr.write(
2603
+ ` ${ansi.cyanBright("Press any key to open the browser...")}
2604
+
2605
+
2606
+
2607
+ `
2608
+ );
2609
+ await waitForKeypress();
2610
+ process.stderr.write("\x1B[4A\r\x1B[J");
2611
+ process.stderr.write(
2612
+ ` ${ansi.gray("Requesting authorization...")}
2613
+ `
2614
+ );
2615
+ const authRes = await fetch(
2616
+ `${baseUrl}/developer/v2/request-auth-url`,
2617
+ {
2618
+ headers: {
2619
+ "Content-Type": "application/json",
2620
+ "User-Agent": "@mindstudio-ai/agent"
2621
+ }
2622
+ }
2623
+ );
2624
+ if (!authRes.ok) {
2625
+ fatal(
2626
+ `Failed to request auth URL: ${authRes.status} ${authRes.statusText}`
2627
+ );
2628
+ }
2629
+ const { url, token } = await authRes.json();
2630
+ openBrowser(url);
2631
+ process.stderr.write(
2632
+ ` ${ansi.cyanBright("Opening browser to authenticate...")}
2633
+
2634
+ ${ansi.gray("If the browser didn't open, visit:")}
2635
+ ${ansi.cyan(url)}
2636
+
2637
+ `
2638
+ );
2639
+ const POLL_INTERVAL = 2e3;
2640
+ const MAX_ATTEMPTS = 60;
2641
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
2642
+ await sleep3(POLL_INTERVAL);
2643
+ const frame = SPINNER_FRAMES[attempt % SPINNER_FRAMES.length];
2644
+ const remaining = Math.ceil(
2645
+ MAX_ATTEMPTS * POLL_INTERVAL / 1e3 - (attempt + 1) * POLL_INTERVAL / 1e3
2646
+ );
2647
+ process.stderr.write(
2648
+ `\r ${ansi.cyan(frame)} Waiting for browser authorization... ${ansi.gray(`(${remaining}s)`)}`
2649
+ );
2650
+ const pollRes = await fetch(`${baseUrl}/developer/v2/poll-auth-url`, {
2651
+ method: "POST",
2652
+ headers: {
2653
+ "Content-Type": "application/json",
2654
+ "User-Agent": "@mindstudio-ai/agent"
2655
+ },
2656
+ body: JSON.stringify({ token })
2657
+ });
2658
+ if (!pollRes.ok) {
2659
+ process.stderr.write("\n");
2660
+ fatal(
2661
+ `Poll request failed: ${pollRes.status} ${pollRes.statusText}`
2662
+ );
2663
+ }
2664
+ const result = await pollRes.json();
2665
+ if (result.status === "completed" && result.apiKey) {
2666
+ process.stderr.write("\r\x1B[K");
2667
+ const { saveConfig: saveConfig2, getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2668
+ const config = {
2669
+ apiKey: result.apiKey
2670
+ };
2671
+ if (baseUrl !== DEFAULT_BASE_URL2) {
2672
+ config.baseUrl = baseUrl;
2673
+ }
2674
+ saveConfig2(config);
2675
+ process.stderr.write(
2676
+ ` ${ansi.greenBold("\u2714")} Authenticated successfully!
2677
+ ${ansi.gray("Credentials saved to")} ${getConfigPath2()}
2678
+
2679
+ `
2680
+ );
2681
+ return;
2682
+ }
2683
+ if (result.status === "expired") {
2684
+ process.stderr.write("\r\x1B[K");
2685
+ fatal("Authorization expired. Please try again.");
2686
+ }
2687
+ }
2688
+ process.stderr.write("\r\x1B[K");
2689
+ fatal("Authorization timed out. Please try again.");
2690
+ }
2691
+ async function cmdLogout() {
2692
+ const { loadConfig: loadConfig2, clearConfig: clearConfig2, getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2693
+ const config = loadConfig2();
2694
+ if (!config.apiKey) {
2695
+ process.stderr.write(
2696
+ ` ${ansi.gray("Not currently logged in.")}
2697
+ `
2698
+ );
2699
+ return;
2700
+ }
2701
+ clearConfig2();
2702
+ process.stderr.write(
2703
+ ` ${ansi.greenBold("\u2714")} Logged out. Credentials removed from ${ansi.gray(getConfigPath2())}
2704
+ `
2705
+ );
2706
+ }
2707
+ async function cmdWhoami(options) {
2708
+ let source;
2709
+ let detail = [];
2710
+ if (options.apiKey) {
2711
+ source = `${ansi.bold("--api-key flag")} ${ansi.gray("(CLI argument)")}`;
2712
+ } else if (process.env.MINDSTUDIO_API_KEY) {
2713
+ source = `${ansi.bold("MINDSTUDIO_API_KEY")} ${ansi.gray("(environment variable)")}`;
2714
+ detail.push(
2715
+ ` ${ansi.gray("Key:")} ${maskKey(process.env.MINDSTUDIO_API_KEY)}`
2716
+ );
2717
+ } else {
2718
+ const { loadConfig: loadConfig2, getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2719
+ const config = loadConfig2();
2720
+ if (config.apiKey) {
2721
+ source = `${ansi.bold("config file")} ${ansi.gray("(mindstudio login)")}`;
2722
+ detail.push(` ${ansi.gray("File:")} ${getConfigPath2()}`);
2723
+ detail.push(` ${ansi.gray("Key:")} ${maskKey(config.apiKey)}`);
2724
+ if (config.baseUrl) {
2725
+ detail.push(` ${ansi.gray("URL:")} ${config.baseUrl}`);
2726
+ }
2727
+ } else if (process.env.CALLBACK_TOKEN) {
2728
+ source = `${ansi.bold("CALLBACK_TOKEN")} ${ansi.gray("(managed/internal mode)")}`;
2729
+ } else {
2730
+ process.stderr.write(
2731
+ ` ${ansi.gray("\u25CB")} Not authenticated. Run ${ansi.cyan("mindstudio login")} to get started.
2732
+ `
2733
+ );
2734
+ return;
2735
+ }
2736
+ }
2737
+ process.stderr.write(` ${ansi.gray("Auth:")} ${source}
2738
+ `);
2739
+ for (const line of detail) process.stderr.write(line + "\n");
2740
+ process.stderr.write(` ${ansi.gray("Verifying...")} `);
2741
+ try {
2742
+ const { MindStudioAgent: MindStudioAgent2 } = await Promise.resolve().then(() => (init_client(), client_exports));
2743
+ const agent = new MindStudioAgent2({
2744
+ apiKey: options.apiKey,
2745
+ baseUrl: options.baseUrl
2746
+ });
2747
+ const result = await agent.listAgents();
2748
+ process.stderr.write(
2749
+ `\r\x1B[K ${ansi.greenBold("\u25CF")} ${ansi.green("Connected")} ${ansi.gray("\u2014")} ${result.orgName} ${ansi.gray("(" + result.orgId + ")")}
2750
+ `
2751
+ );
2752
+ } catch (err) {
2753
+ const message = err instanceof Error ? err.message : String(err);
2754
+ process.stderr.write(
2755
+ `\r\x1B[K ${ansi.dim("\u25CF")} ${ansi.dim("Not connected")} ${ansi.gray("\u2014")} ${message}
2756
+ `
2757
+ );
2758
+ }
2759
+ }
2361
2760
  function parseStepFlags(argv) {
2362
2761
  const result = {};
2363
2762
  for (let i = 0; i < argv.length; i++) {
@@ -2431,7 +2830,25 @@ async function main() {
2431
2830
  process.exit(positionals.length === 0 ? 1 : 0);
2432
2831
  }
2433
2832
  const command = positionals[0];
2833
+ const updatePromise = command !== "mcp" && command !== "login" ? checkForUpdate() : Promise.resolve(null);
2434
2834
  try {
2835
+ if (command === "login") {
2836
+ await cmdLogin({
2837
+ baseUrl: values["base-url"]
2838
+ });
2839
+ return;
2840
+ }
2841
+ if (command === "logout") {
2842
+ await cmdLogout();
2843
+ return;
2844
+ }
2845
+ if (command === "whoami") {
2846
+ await cmdWhoami({
2847
+ apiKey: values["api-key"],
2848
+ baseUrl: values["base-url"]
2849
+ });
2850
+ return;
2851
+ }
2435
2852
  if (command === "list") {
2436
2853
  await cmdList(values.json);
2437
2854
  return;
@@ -2551,6 +2968,9 @@ async function main() {
2551
2968
  } catch (err) {
2552
2969
  const message = err instanceof Error ? err.message : String(err);
2553
2970
  fatal(message);
2971
+ } finally {
2972
+ const latestVersion = await updatePromise;
2973
+ if (latestVersion) printUpdateNotice(latestVersion);
2554
2974
  }
2555
2975
  }
2556
2976
  main();