@vm0/cli 9.132.11 → 9.135.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "9.132.11",
3
+ "version": "9.135.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",
package/zero.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  Option,
12
12
  allowsCustomModel,
13
13
  completeSlackFileUpload,
14
+ completeTelegramFileUpload,
14
15
  configureGlobalProxyFromEnv,
15
16
  connectorTypeSchema,
16
17
  createSkill,
@@ -30,6 +31,7 @@ import {
30
31
  deployZeroSchedule,
31
32
  disableZeroSchedule,
32
33
  downloadSlackFile,
34
+ downloadTelegramFile,
33
35
  downloadWebFile,
34
36
  enableZeroSchedule,
35
37
  extractSecretNamesFromApis,
@@ -58,6 +60,7 @@ import {
58
60
  hasAuthMethods,
59
61
  hasModelSelection,
60
62
  initSlackFileUpload,
63
+ initTelegramFileUpload,
61
64
  inviteZeroOrgMember,
62
65
  isInteractive,
63
66
  isUUID,
@@ -112,7 +115,7 @@ import {
112
115
  upsertZeroOrgModelProvider,
113
116
  withErrorHandler,
114
117
  zeroAgentCustomSkillNameSchema
115
- } from "./chunk-S72CJD3V.js";
118
+ } from "./chunk-7C3QXZRH.js";
116
119
  import {
117
120
  __toESM,
118
121
  init_esm_shims
@@ -13922,7 +13925,7 @@ var FEATURE_SWITCHES = {
13922
13925
  },
13923
13926
  ["auditLink" /* AuditLink */]: {
13924
13927
  maintainer: "ethan@vm0.ai",
13925
- description: "Show audit log links in Slack messages",
13928
+ description: "Show audit log links in integration replies",
13926
13929
  enabled: false
13927
13930
  },
13928
13931
  ["audioInput" /* AudioInput */]: {
@@ -13950,6 +13953,12 @@ var FEATURE_SWITCHES = {
13950
13953
  description: "Replace the Invite people button in the agent chat page header with a New button that creates a new chat thread",
13951
13954
  enabled: false
13952
13955
  },
13956
+ ["chatArtifactsDrawer" /* ChatArtifactsDrawer */]: {
13957
+ maintainer: "ethan@vm0.ai",
13958
+ description: "Show an artifacts button in the chat header that opens a drawer listing uploaded files grouped by run",
13959
+ enabled: false,
13960
+ enabledOrgIdHashes: STAFF_ORG_ID_HASHES
13961
+ },
13953
13962
  ["chatThreadReadIndicator" /* ChatThreadReadIndicator */]: {
13954
13963
  maintainer: "ethan@vm0.ai",
13955
13964
  description: "Show the unread watermark dot and bold title for chat threads with unread messages in the sidebar",
@@ -16729,6 +16738,165 @@ Examples:
16729
16738
  Download a file: zero slack download-file <file-id> -o /tmp/out.png`
16730
16739
  );
16731
16740
 
16741
+ // src/commands/zero/telegram/index.ts
16742
+ init_esm_shims();
16743
+
16744
+ // src/commands/zero/telegram/download-file.ts
16745
+ init_esm_shims();
16746
+ import { join as join2 } from "path";
16747
+ import { tmpdir as tmpdir2 } from "os";
16748
+ function defaultOutPath2(fileId) {
16749
+ return join2(tmpdir2(), `telegram-${fileId}`);
16750
+ }
16751
+ var downloadFileCommand2 = new Command().name("download-file").description("Download a Telegram file by id using the bot token").argument("<file-id>", "Telegram file id from a [Telegram file] block").option(
16752
+ "-o, --out <path>",
16753
+ "Output path for the downloaded file (default: /tmp/telegram-<file-id>)"
16754
+ ).requiredOption(
16755
+ "--bot-id <bot-id>",
16756
+ "Telegram bot id from the [Telegram file] block"
16757
+ ).addHelpText(
16758
+ "after",
16759
+ `
16760
+ Examples:
16761
+ Download to default temp path: zero telegram download-file AgACAgUAAxkBAA --bot-id 123456789
16762
+ Download to explicit path: zero telegram download-file AgACAgUAAxkBAA --bot-id 123456789 -o /tmp/photo.jpg
16763
+
16764
+ Output:
16765
+ Prints a JSON object to stdout on success:
16766
+ {"path":"/tmp/telegram-AgACAgUAAxkBAA","mimetype":"image/jpeg","size":12345}
16767
+
16768
+ How to read the downloaded file:
16769
+ - Images (png/jpg/gif/webp/svg): open the file path with your image viewing tool
16770
+ - Videos (mp4/mov/webm): extract frames first with
16771
+ ffmpeg -i <path> -vf "fps=1" -q:v 2 /tmp/<file-id>_frame_%03d.jpg
16772
+ then view the extracted frames
16773
+ - PDF/text/csv/json/markdown: read the file directly
16774
+
16775
+ Notes:
16776
+ - Uses the Telegram bot token on the server side
16777
+ - Streams the file bytes directly to disk`
16778
+ ).action(
16779
+ withErrorHandler(
16780
+ async (fileId, options) => {
16781
+ const outPath = options.out ?? defaultOutPath2(fileId);
16782
+ const result = await downloadTelegramFile(
16783
+ fileId,
16784
+ options.botId,
16785
+ outPath
16786
+ );
16787
+ console.log(JSON.stringify(result));
16788
+ }
16789
+ )
16790
+ );
16791
+
16792
+ // src/commands/zero/telegram/upload-file.ts
16793
+ init_esm_shims();
16794
+ import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
16795
+ import { basename as basename2, extname } from "path";
16796
+ var MIME_BY_EXTENSION = {
16797
+ ".png": "image/png",
16798
+ ".jpg": "image/jpeg",
16799
+ ".jpeg": "image/jpeg",
16800
+ ".gif": "image/gif",
16801
+ ".webp": "image/webp",
16802
+ ".svg": "image/svg+xml",
16803
+ ".mp4": "video/mp4",
16804
+ ".webm": "video/webm",
16805
+ ".mov": "video/quicktime",
16806
+ ".pdf": "application/pdf",
16807
+ ".txt": "text/plain",
16808
+ ".csv": "text/csv",
16809
+ ".md": "text/markdown",
16810
+ ".json": "application/json"
16811
+ };
16812
+ function inferContentType(localPath) {
16813
+ const ext = extname(localPath).toLowerCase();
16814
+ return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
16815
+ }
16816
+ function parseMessageThreadId(value) {
16817
+ if (!value) return void 0;
16818
+ const parsed = Number(value);
16819
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
16820
+ throw new Error("message-thread-id must be a positive integer");
16821
+ }
16822
+ return parsed;
16823
+ }
16824
+ var uploadFileCommand2 = new Command().name("upload-file").description("Upload a local file to a Telegram chat as the bot").requiredOption("-f, --file <path>", "Local file path to upload").requiredOption("--bot-id <bot-id>", "Telegram bot id to send through").requiredOption("-c, --chat-id <chat-id>", "Telegram chat id or @channel").option("--caption <text>", "Caption to accompany the file").option("--message-thread-id <id>", "Forum topic message thread id").option("--content-type <mime>", "Override inferred content type").addHelpText(
16825
+ "after",
16826
+ `
16827
+ Examples:
16828
+ Upload a file: zero telegram upload-file -f /tmp/report.pdf --bot-id 123456789 -c -1001234567890
16829
+ Upload to a topic: zero telegram upload-file -f /tmp/log.txt --bot-id 123456789 -c -1001234567890 --message-thread-id 42
16830
+ With a caption: zero telegram upload-file -f /tmp/data.csv --bot-id 123456789 -c @channel --caption "Daily report"
16831
+
16832
+ Output:
16833
+ Prints a JSON object to stdout on success:
16834
+ {"messageId":123,"chatId":"-1001234567890","fileId":"...","filename":"report.pdf","mimetype":"application/pdf","size":12345,"url":"https://..."}
16835
+
16836
+ Notes:
16837
+ - Uses the Telegram bot token on the server side
16838
+ - Uploads through VM0 storage first, then asks Telegram to fetch the file URL
16839
+ - VM0 does not apply file type or size restrictions before calling Telegram`
16840
+ ).action(
16841
+ withErrorHandler(
16842
+ async (options) => {
16843
+ let fileSize;
16844
+ try {
16845
+ const stat = statSync2(options.file);
16846
+ if (!stat.isFile()) {
16847
+ throw new Error(`Not a regular file: ${options.file}`);
16848
+ }
16849
+ fileSize = stat.size;
16850
+ } catch (error) {
16851
+ if (error instanceof Error && error.message.startsWith("Not ")) {
16852
+ throw error;
16853
+ }
16854
+ throw new Error(`File not found: ${options.file}`);
16855
+ }
16856
+ if (fileSize === 0) {
16857
+ throw new Error("File is empty");
16858
+ }
16859
+ const filename = basename2(options.file);
16860
+ const contentType = options.contentType ?? inferContentType(options.file);
16861
+ const messageThreadId = parseMessageThreadId(options.messageThreadId);
16862
+ const prepared = await initTelegramFileUpload({
16863
+ filename,
16864
+ contentType,
16865
+ length: fileSize
16866
+ });
16867
+ const fileContent = readFileSync7(options.file);
16868
+ const uploadResponse = await fetch(prepared.uploadUrl, {
16869
+ method: "PUT",
16870
+ headers: { "Content-Type": prepared.contentType },
16871
+ body: new Uint8Array(fileContent)
16872
+ });
16873
+ if (!uploadResponse.ok) {
16874
+ throw new Error(
16875
+ `File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`
16876
+ );
16877
+ }
16878
+ const result = await completeTelegramFileUpload({
16879
+ uploadId: prepared.uploadId,
16880
+ botId: options.botId,
16881
+ chatId: options.chatId,
16882
+ contentType: prepared.contentType,
16883
+ caption: options.caption,
16884
+ messageThreadId
16885
+ });
16886
+ console.log(JSON.stringify(result));
16887
+ }
16888
+ )
16889
+ );
16890
+
16891
+ // src/commands/zero/telegram/index.ts
16892
+ var zeroTelegramCommand = new Command().name("telegram").description("Upload and download files from Telegram as the bot").addCommand(downloadFileCommand2).addCommand(uploadFileCommand2).addHelpText(
16893
+ "after",
16894
+ `
16895
+ Examples:
16896
+ Upload a file: zero telegram upload-file -f /tmp/report.pdf --bot-id <bot-id> -c <chat-id>
16897
+ Download a file: zero telegram download-file <file-id> --bot-id <bot-id> -o /tmp/out.jpg`
16898
+ );
16899
+
16732
16900
  // src/commands/zero/variable/index.ts
16733
16901
  init_esm_shims();
16734
16902
 
@@ -16976,8 +17144,8 @@ init_esm_shims();
16976
17144
 
16977
17145
  // src/lib/skill-directory.ts
16978
17146
  init_esm_shims();
16979
- import { readFileSync as readFileSync7, readdirSync } from "fs";
16980
- import { join as join2 } from "path";
17147
+ import { readFileSync as readFileSync8, readdirSync } from "fs";
17148
+ import { join as join3 } from "path";
16981
17149
  var IGNORED_NAMES = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
16982
17150
  function readSkillDirectory(dirPath) {
16983
17151
  const files = [];
@@ -16987,11 +17155,11 @@ function readSkillDirectory(dirPath) {
16987
17155
  if (entry.name.startsWith(".") || IGNORED_NAMES.has(entry.name)) continue;
16988
17156
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
16989
17157
  if (entry.isDirectory()) {
16990
- walk(join2(dir, entry.name), relPath);
17158
+ walk(join3(dir, entry.name), relPath);
16991
17159
  } else {
16992
17160
  files.push({
16993
17161
  path: relPath,
16994
- content: readFileSync7(join2(dir, entry.name), "utf-8")
17162
+ content: readFileSync8(join3(dir, entry.name), "utf-8")
16995
17163
  });
16996
17164
  }
16997
17165
  }
@@ -17753,12 +17921,12 @@ init_esm_shims();
17753
17921
  import { execFile } from "child_process";
17754
17922
  import { readFile, unlink } from "fs/promises";
17755
17923
  import { randomUUID } from "crypto";
17756
- import { join as join3 } from "path";
17757
- import { tmpdir as tmpdir2 } from "os";
17924
+ import { join as join4 } from "path";
17925
+ import { tmpdir as tmpdir3 } from "os";
17758
17926
  import { promisify } from "util";
17759
17927
  var execFileAsync = promisify(execFile);
17760
17928
  async function captureScreenshot() {
17761
- const tmpPath = join3(tmpdir2(), `vm0-screenshot-${randomUUID()}.jpg`);
17929
+ const tmpPath = join4(tmpdir3(), `vm0-screenshot-${randomUUID()}.jpg`);
17762
17930
  try {
17763
17931
  await execFileAsync("screencapture", ["-x", "-t", "jpg", tmpPath]);
17764
17932
  const info = await getScreenInfo();
@@ -17787,7 +17955,7 @@ async function captureScreenshot() {
17787
17955
  }
17788
17956
  }
17789
17957
  async function captureRegionScreenshot(region) {
17790
- const tmpPath = join3(tmpdir2(), `vm0-zoom-${randomUUID()}.jpg`);
17958
+ const tmpPath = join4(tmpdir3(), `vm0-zoom-${randomUUID()}.jpg`);
17791
17959
  try {
17792
17960
  const regionArg = `${region.x},${region.y},${region.width},${region.height}`;
17793
17961
  await execFileAsync("screencapture", [
@@ -18479,7 +18647,7 @@ var hostStopCommand = new Command().name("stop").description("Stop and unregiste
18479
18647
  // src/commands/zero/computer-use/client.ts
18480
18648
  init_esm_shims();
18481
18649
  import { writeFile, mkdir } from "fs/promises";
18482
- import { join as join4 } from "path";
18650
+ import { join as join5 } from "path";
18483
18651
 
18484
18652
  // src/lib/computer-use/client.ts
18485
18653
  init_esm_shims();
@@ -18547,7 +18715,7 @@ var clientScreenshotCommand = new Command().name("screenshot").description("Capt
18547
18715
  const dir = "/tmp/computer-use";
18548
18716
  await mkdir(dir, { recursive: true });
18549
18717
  const timestamp = Date.now();
18550
- const filePath = join4(dir, `screenshot-${timestamp}.${data.format}`);
18718
+ const filePath = join5(dir, `screenshot-${timestamp}.${data.format}`);
18551
18719
  const buffer = Buffer.from(data.image, "base64");
18552
18720
  await writeFile(filePath, buffer);
18553
18721
  process.stdout.write(`${filePath}
@@ -18575,7 +18743,7 @@ var clientZoomCommand = new Command().name("zoom").description("Capture a region
18575
18743
  const dir = "/tmp/computer-use";
18576
18744
  await mkdir(dir, { recursive: true });
18577
18745
  const timestamp = Date.now();
18578
- const filePath = join4(dir, `zoom-${timestamp}.${data.format}`);
18746
+ const filePath = join5(dir, `zoom-${timestamp}.${data.format}`);
18579
18747
  const buffer = Buffer.from(data.image, "base64");
18580
18748
  await writeFile(filePath, buffer);
18581
18749
  process.stdout.write(`${filePath}
@@ -18772,12 +18940,12 @@ init_esm_shims();
18772
18940
 
18773
18941
  // src/commands/zero/web/download-file.ts
18774
18942
  init_esm_shims();
18775
- import { join as join5 } from "path";
18776
- import { tmpdir as tmpdir3 } from "os";
18777
- function defaultOutPath2(fileId) {
18778
- return join5(tmpdir3(), `web-${fileId}`);
18943
+ import { join as join6 } from "path";
18944
+ import { tmpdir as tmpdir4 } from "os";
18945
+ function defaultOutPath3(fileId) {
18946
+ return join6(tmpdir4(), `web-${fileId}`);
18779
18947
  }
18780
- var downloadFileCommand2 = new Command().name("download-file").description("Download a web-uploaded file by id").argument("<file-id>", "File id (UUID returned by the upload API)").option(
18948
+ var downloadFileCommand3 = new Command().name("download-file").description("Download a web-uploaded file by id").argument("<file-id>", "File id (UUID returned by the upload API)").option(
18781
18949
  "-o, --out <path>",
18782
18950
  "Output path for the downloaded file (default: /tmp/web-<file-id>)"
18783
18951
  ).addHelpText(
@@ -18803,7 +18971,7 @@ Notes:
18803
18971
  - Streams the file bytes directly to disk`
18804
18972
  ).action(
18805
18973
  withErrorHandler(async (fileId, options) => {
18806
- const outPath = options.out ?? defaultOutPath2(fileId);
18974
+ const outPath = options.out ?? defaultOutPath3(fileId);
18807
18975
  const result = await downloadWebFile(fileId, outPath);
18808
18976
  console.log(JSON.stringify(result));
18809
18977
  })
@@ -18811,7 +18979,7 @@ Notes:
18811
18979
 
18812
18980
  // src/commands/zero/web/upload-file.ts
18813
18981
  init_esm_shims();
18814
- var uploadFileCommand2 = new Command().name("upload-file").description("Upload a local file and print a permanent URL").requiredOption("-f, --file <path>", "Local file path to upload").option("--content-type <mime>", "Override inferred content type").addHelpText(
18982
+ var uploadFileCommand3 = new Command().name("upload-file").description("Upload a local file and print a permanent URL").requiredOption("-f, --file <path>", "Local file path to upload").option("--content-type <mime>", "Override inferred content type").addHelpText(
18815
18983
  "after",
18816
18984
  `
18817
18985
  Examples:
@@ -18827,7 +18995,7 @@ Notes:
18827
18995
  - Returned URL is permanent (serves a short-lived signed redirect on access)
18828
18996
  - Safe to persist in chat messages or share over external channels
18829
18997
  - Max file size: 1 GB
18830
- - Allowed types: png / jpeg / gif / webp / svg / mp4 / webm / mov / pdf / txt / csv / md / json`
18998
+ - Allowed types: png / jpeg / gif / webp / svg / mp4 / webm / mov / pdf / txt / csv / md / html / json`
18831
18999
  ).action(
18832
19000
  withErrorHandler(
18833
19001
  async (options) => {
@@ -18840,7 +19008,7 @@ Notes:
18840
19008
  );
18841
19009
 
18842
19010
  // src/commands/zero/web/index.ts
18843
- var zeroWebCommand = new Command().name("web").description("Upload and download files via the web chat endpoint").addCommand(downloadFileCommand2).addCommand(uploadFileCommand2).addHelpText(
19011
+ var zeroWebCommand = new Command().name("web").description("Upload and download files via the web chat endpoint").addCommand(downloadFileCommand3).addCommand(uploadFileCommand3).addHelpText(
18844
19012
  "after",
18845
19013
  `
18846
19014
  Examples:
@@ -18860,6 +19028,7 @@ var COMMAND_CAPABILITY_MAP = {
18860
19028
  search: "chat-message:read",
18861
19029
  chat: "chat-message:write",
18862
19030
  slack: "slack:write",
19031
+ telegram: ["telegram:read", "telegram:write"],
18863
19032
  whoami: null,
18864
19033
  "developer-support": null,
18865
19034
  "computer-use": "computer-use:write",
@@ -18876,6 +19045,7 @@ var DEFAULT_COMMANDS = [
18876
19045
  zeroSecretCommand,
18877
19046
  zeroChatCommand,
18878
19047
  zeroSlackCommand,
19048
+ zeroTelegramCommand,
18879
19049
  zeroVariableCommand,
18880
19050
  zeroLogsCommand,
18881
19051
  zeroSearchCommand,
@@ -18889,7 +19059,13 @@ function shouldHideCommand(name, payload) {
18889
19059
  if (!payload) return false;
18890
19060
  const requiredCap = COMMAND_CAPABILITY_MAP[name];
18891
19061
  if (requiredCap === void 0) return true;
18892
- return requiredCap !== null && !payload.capabilities.includes(requiredCap);
19062
+ if (requiredCap === null) return false;
19063
+ if (typeof requiredCap !== "string") {
19064
+ return !requiredCap.some((capability) => {
19065
+ return payload.capabilities.includes(capability);
19066
+ });
19067
+ }
19068
+ return !payload.capabilities.includes(requiredCap);
18893
19069
  }
18894
19070
  function registerZeroCommands(prog, commands) {
18895
19071
  const token = process.env.ZERO_TOKEN;
@@ -18902,12 +19078,14 @@ function registerZeroCommands(prog, commands) {
18902
19078
  var program = new Command();
18903
19079
  program.name("zero").description(
18904
19080
  "Zero CLI \u2014 interact with the zero platform from inside the sandbox"
18905
- ).version("9.132.11").addHelpText(
19081
+ ).version("9.135.0").addHelpText(
18906
19082
  "after",
18907
19083
  `
18908
19084
  Examples:
18909
19085
  Check a connector? zero doctor check-connector --env-name <ENV_NAME>
18910
19086
  Send a Slack message? zero slack message send --help
19087
+ Upload Telegram? zero telegram upload-file --help
19088
+ Download Telegram? zero telegram download-file --help
18911
19089
  Set up a schedule? zero schedule setup --help
18912
19090
  Update yourself? zero agent --help
18913
19091
  Manage custom skills? zero skill --help