@vm0/cli 9.2.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +755 -153
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command as Command48 } from "commander";
5
- import chalk48 from "chalk";
5
+ import chalk50 from "chalk";
6
6
 
7
7
  // src/lib/api/auth.ts
8
8
  import chalk from "chalk";
@@ -1807,19 +1807,11 @@ var MODEL_PROVIDER_TYPES = {
1807
1807
  label: "Anthropic API Key",
1808
1808
  credentialLabel: "API key",
1809
1809
  helpText: "Get your API key at: https://console.anthropic.com/settings/keys"
1810
- },
1811
- "openai-api-key": {
1812
- framework: "codex",
1813
- credentialName: "OPENAI_API_KEY",
1814
- label: "OpenAI API Key",
1815
- credentialLabel: "API key",
1816
- helpText: "Get your API key at: https://platform.openai.com/api-keys"
1817
1810
  }
1818
1811
  };
1819
1812
  var modelProviderTypeSchema = z14.enum([
1820
1813
  "claude-code-oauth-token",
1821
- "anthropic-api-key",
1822
- "openai-api-key"
1814
+ "anthropic-api-key"
1823
1815
  ]);
1824
1816
  var modelProviderFrameworkSchema = z14.enum(["claude-code", "codex"]);
1825
1817
  var modelProviderResponseSchema = z14.object({
@@ -2296,6 +2288,27 @@ var realtimeTokenContract = c14.router({
2296
2288
  summary: "Get Ably token for run event subscription"
2297
2289
  }
2298
2290
  });
2291
+ var runnerRealtimeTokenContract = c14.router({
2292
+ /**
2293
+ * POST /api/runners/realtime/token
2294
+ * Get an Ably token to subscribe to a runner group's job notification channel
2295
+ */
2296
+ create: {
2297
+ method: "POST",
2298
+ path: "/api/runners/realtime/token",
2299
+ headers: authHeadersSchema,
2300
+ body: z17.object({
2301
+ group: runnerGroupSchema
2302
+ }),
2303
+ responses: {
2304
+ 200: ablyTokenRequestSchema,
2305
+ 401: apiErrorSchema,
2306
+ 403: apiErrorSchema,
2307
+ 500: apiErrorSchema
2308
+ },
2309
+ summary: "Get Ably token for runner group job notifications"
2310
+ }
2311
+ });
2299
2312
 
2300
2313
  // ../../packages/core/src/contracts/platform.ts
2301
2314
  import { z as z19 } from "zod";
@@ -3136,10 +3149,10 @@ async function getRawHeaders() {
3136
3149
  }
3137
3150
  return headers;
3138
3151
  }
3139
- async function httpGet(path15) {
3152
+ async function httpGet(path16) {
3140
3153
  const baseUrl = await getBaseUrl();
3141
3154
  const headers = await getRawHeaders();
3142
- return fetch(`${baseUrl}${path15}`, {
3155
+ return fetch(`${baseUrl}${path16}`, {
3143
3156
  method: "GET",
3144
3157
  headers
3145
3158
  });
@@ -3619,49 +3632,49 @@ var cliComposeSchema = z24.object({
3619
3632
  function formatZodError(error) {
3620
3633
  const issue = error.issues[0];
3621
3634
  if (!issue) return "Validation failed";
3622
- const path15 = issue.path.join(".");
3635
+ const path16 = issue.path.join(".");
3623
3636
  const message = issue.message;
3624
- if (!path15) return message;
3637
+ if (!path16) return message;
3625
3638
  if (issue.code === "invalid_type") {
3626
3639
  const received = issue.received;
3627
3640
  const isMissing = received === "undefined" || message.includes("received undefined") || message === "Required";
3628
- if (path15 === "version" && isMissing) {
3641
+ if (path16 === "version" && isMissing) {
3629
3642
  return "Missing config.version";
3630
3643
  }
3631
- if (path15 === "agents" && isMissing) {
3644
+ if (path16 === "agents" && isMissing) {
3632
3645
  return "Missing agents object in config";
3633
3646
  }
3634
- if (path15.startsWith("volumes.") && path15.endsWith(".name")) {
3635
- const volumeKey = path15.split(".")[1];
3647
+ if (path16.startsWith("volumes.") && path16.endsWith(".name")) {
3648
+ const volumeKey = path16.split(".")[1];
3636
3649
  return `Volume "${volumeKey}" must have a 'name' field (string)`;
3637
3650
  }
3638
- if (path15.startsWith("volumes.") && path15.endsWith(".version")) {
3639
- const volumeKey = path15.split(".")[1];
3651
+ if (path16.startsWith("volumes.") && path16.endsWith(".version")) {
3652
+ const volumeKey = path16.split(".")[1];
3640
3653
  return `Volume "${volumeKey}" must have a 'version' field (string)`;
3641
3654
  }
3642
3655
  if (issue.expected === "array") {
3643
- const fieldName = path15.replace(/^agents\.[^.]+\./, "agent.");
3656
+ const fieldName = path16.replace(/^agents\.[^.]+\./, "agent.");
3644
3657
  return `${fieldName} must be an array`;
3645
3658
  }
3646
3659
  if (issue.expected === "string" && received === "number") {
3647
- const fieldName = path15.replace(/^agents\.[^.]+\./, "agent.");
3660
+ const fieldName = path16.replace(/^agents\.[^.]+\./, "agent.");
3648
3661
  const match = fieldName.match(/^(agent\.[^.]+)\.\d+$/);
3649
3662
  if (match) {
3650
3663
  return `Each entry in ${match[1]?.replace("agent.", "")} must be a string`;
3651
3664
  }
3652
3665
  }
3653
3666
  }
3654
- if (issue.code === "invalid_key" && path15.startsWith("agents.")) {
3667
+ if (issue.code === "invalid_key" && path16.startsWith("agents.")) {
3655
3668
  return "Invalid agent name format. Must be 3-64 characters, letters, numbers, and hyphens only. Must start and end with letter or number.";
3656
3669
  }
3657
- if (message === "Invalid key in record" && path15.startsWith("agents.")) {
3670
+ if (message === "Invalid key in record" && path16.startsWith("agents.")) {
3658
3671
  return "Invalid agent name format. Must be 3-64 characters, letters, numbers, and hyphens only. Must start and end with letter or number.";
3659
3672
  }
3660
3673
  if (issue.code === "custom") {
3661
3674
  return message;
3662
3675
  }
3663
- if (path15.startsWith("agents.")) {
3664
- const cleanPath = path15.replace(/^agents\.[^.]+\./, "agent.");
3676
+ if (path16.startsWith("agents.")) {
3677
+ const cleanPath = path16.replace(/^agents\.[^.]+\./, "agent.");
3665
3678
  if (message.startsWith("Invalid input:")) {
3666
3679
  const match = message.match(/expected (\w+), received (\w+)/);
3667
3680
  if (match && match[1] === "string" && match[2] === "number") {
@@ -3673,7 +3686,7 @@ function formatZodError(error) {
3673
3686
  }
3674
3687
  return `${cleanPath}: ${message}`;
3675
3688
  }
3676
- return `${path15}: ${message}`;
3689
+ return `${path16}: ${message}`;
3677
3690
  }
3678
3691
  function validateAgentName(name) {
3679
3692
  return cliAgentNameSchema.safeParse(name).success;
@@ -5849,7 +5862,7 @@ var ApiClient = class {
5849
5862
  /**
5850
5863
  * Generic GET request
5851
5864
  */
5852
- async get(path15) {
5865
+ async get(path16) {
5853
5866
  const baseUrl = await this.getBaseUrl();
5854
5867
  const token = await getToken();
5855
5868
  if (!token) {
@@ -5862,7 +5875,7 @@ var ApiClient = class {
5862
5875
  if (bypassSecret) {
5863
5876
  headers["x-vercel-protection-bypass"] = bypassSecret;
5864
5877
  }
5865
- return fetch(`${baseUrl}${path15}`, {
5878
+ return fetch(`${baseUrl}${path16}`, {
5866
5879
  method: "GET",
5867
5880
  headers
5868
5881
  });
@@ -5870,7 +5883,7 @@ var ApiClient = class {
5870
5883
  /**
5871
5884
  * Generic POST request
5872
5885
  */
5873
- async post(path15, options) {
5886
+ async post(path16, options) {
5874
5887
  const baseUrl = await this.getBaseUrl();
5875
5888
  const token = await getToken();
5876
5889
  if (!token) {
@@ -5886,7 +5899,7 @@ var ApiClient = class {
5886
5899
  if (bypassSecret) {
5887
5900
  headers["x-vercel-protection-bypass"] = bypassSecret;
5888
5901
  }
5889
- return fetch(`${baseUrl}${path15}`, {
5902
+ return fetch(`${baseUrl}${path16}`, {
5890
5903
  method: "POST",
5891
5904
  headers,
5892
5905
  body: options?.body
@@ -5895,7 +5908,7 @@ var ApiClient = class {
5895
5908
  /**
5896
5909
  * Generic DELETE request
5897
5910
  */
5898
- async delete(path15) {
5911
+ async delete(path16) {
5899
5912
  const baseUrl = await this.getBaseUrl();
5900
5913
  const token = await getToken();
5901
5914
  if (!token) {
@@ -5908,7 +5921,7 @@ var ApiClient = class {
5908
5921
  if (bypassSecret) {
5909
5922
  headers["x-vercel-protection-bypass"] = bypassSecret;
5910
5923
  }
5911
- return fetch(`${baseUrl}${path15}`, {
5924
+ return fetch(`${baseUrl}${path16}`, {
5912
5925
  method: "DELETE",
5913
5926
  headers
5914
5927
  });
@@ -7730,7 +7743,7 @@ cookCmd.argument("[prompt]", "Prompt for the agent").option(
7730
7743
  // eslint-disable-next-line complexity -- TODO: refactor complex function
7731
7744
  async (prompt, options) => {
7732
7745
  if (!options.noAutoUpdate) {
7733
- const shouldExit = await checkAndUpgrade("9.2.0", prompt);
7746
+ const shouldExit = await checkAndUpgrade("9.3.0", prompt);
7734
7747
  if (shouldExit) {
7735
7748
  process.exit(0);
7736
7749
  }
@@ -10446,18 +10459,708 @@ var setDefaultCommand = new Command44().name("set-default").description("Set a m
10446
10459
  var modelProviderCommand = new Command45().name("model-provider").description("Manage model providers for agent runs").addCommand(listCommand6).addCommand(setupCommand2).addCommand(deleteCommand3).addCommand(setDefaultCommand);
10447
10460
 
10448
10461
  // src/commands/onboard.ts
10449
- import { Command as Command47 } from "commander";
10450
- import chalk47 from "chalk";
10451
- import prompts3 from "prompts";
10462
+ import { Command as Command46 } from "commander";
10463
+ import chalk48 from "chalk";
10452
10464
  import { mkdir as mkdir8 } from "fs/promises";
10453
10465
  import { existsSync as existsSync10 } from "fs";
10454
10466
 
10455
- // src/commands/setup-claude.ts
10456
- import { Command as Command46 } from "commander";
10467
+ // src/lib/ui/welcome-box.ts
10457
10468
  import chalk46 from "chalk";
10469
+ function renderWelcomeBox(lines, width) {
10470
+ const maxLineLength = Math.max(...lines.map((line) => line.length));
10471
+ const boxWidth = width ?? maxLineLength + 4;
10472
+ const innerWidth = boxWidth - 2;
10473
+ const horizontalLine = "\u2500".repeat(innerWidth);
10474
+ const topBorder = `\u250C${horizontalLine}\u2510`;
10475
+ const bottomBorder = `\u2514${horizontalLine}\u2518`;
10476
+ console.log(chalk46.cyan(topBorder));
10477
+ for (const line of lines) {
10478
+ const padding = innerWidth - line.length;
10479
+ const leftPad = Math.floor(padding / 2);
10480
+ const rightPad = padding - leftPad;
10481
+ const centeredLine = " ".repeat(leftPad) + line + " ".repeat(rightPad);
10482
+ console.log(chalk46.cyan("\u2502") + centeredLine + chalk46.cyan("\u2502"));
10483
+ }
10484
+ console.log(chalk46.cyan(bottomBorder));
10485
+ }
10486
+ function renderOnboardWelcome() {
10487
+ renderWelcomeBox([
10488
+ "",
10489
+ "Welcome to VM0!",
10490
+ "",
10491
+ "Let's set up your first agent.",
10492
+ ""
10493
+ ]);
10494
+ }
10495
+
10496
+ // src/lib/ui/progress-line.ts
10497
+ import chalk47 from "chalk";
10498
+ var STATUS_SYMBOLS = {
10499
+ completed: "\u25CF",
10500
+ "in-progress": "\u25D0",
10501
+ pending: "\u25CB",
10502
+ failed: "\u2717"
10503
+ };
10504
+ function getStatusColor(status) {
10505
+ switch (status) {
10506
+ case "completed":
10507
+ return chalk47.green;
10508
+ case "in-progress":
10509
+ return chalk47.yellow;
10510
+ case "failed":
10511
+ return chalk47.red;
10512
+ case "pending":
10513
+ default:
10514
+ return chalk47.dim;
10515
+ }
10516
+ }
10517
+ function renderProgressLine(steps) {
10518
+ for (let i = 0; i < steps.length; i++) {
10519
+ const step = steps[i];
10520
+ if (!step) continue;
10521
+ const symbol = STATUS_SYMBOLS[step.status];
10522
+ const color = getStatusColor(step.status);
10523
+ console.log(color(`${symbol} ${step.label}`));
10524
+ if (i < steps.length - 1) {
10525
+ console.log(chalk47.dim("\u2502"));
10526
+ }
10527
+ }
10528
+ }
10529
+ function createOnboardProgress() {
10530
+ const steps = [
10531
+ { label: "Authentication", status: "pending" },
10532
+ { label: "Model Provider Setup", status: "pending" },
10533
+ { label: "Create Agent", status: "pending" },
10534
+ { label: "Complete", status: "pending" }
10535
+ ];
10536
+ return {
10537
+ steps,
10538
+ render: () => renderProgressLine(steps),
10539
+ update: (index, status) => {
10540
+ const step = steps[index];
10541
+ if (step) {
10542
+ step.status = status;
10543
+ }
10544
+ }
10545
+ };
10546
+ }
10547
+
10548
+ // src/lib/domain/onboard/auth.ts
10549
+ function buildHeaders2() {
10550
+ const headers = {
10551
+ "Content-Type": "application/json"
10552
+ };
10553
+ const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
10554
+ if (bypassSecret) {
10555
+ headers["x-vercel-protection-bypass"] = bypassSecret;
10556
+ }
10557
+ return headers;
10558
+ }
10559
+ async function requestDeviceCode2(apiUrl) {
10560
+ const response = await fetch(`${apiUrl}/api/cli/auth/device`, {
10561
+ method: "POST",
10562
+ headers: buildHeaders2(),
10563
+ body: JSON.stringify({})
10564
+ });
10565
+ if (!response.ok) {
10566
+ throw new Error(`Failed to request device code: ${response.statusText}`);
10567
+ }
10568
+ return response.json();
10569
+ }
10570
+ async function exchangeToken2(apiUrl, deviceCode) {
10571
+ const response = await fetch(`${apiUrl}/api/cli/auth/token`, {
10572
+ method: "POST",
10573
+ headers: buildHeaders2(),
10574
+ body: JSON.stringify({ device_code: deviceCode })
10575
+ });
10576
+ return response.json();
10577
+ }
10578
+ function delay2(ms) {
10579
+ return new Promise((resolve) => setTimeout(resolve, ms));
10580
+ }
10581
+ async function isAuthenticated() {
10582
+ const token = await getToken();
10583
+ return !!token;
10584
+ }
10585
+ function handleTokenResult(tokenResult) {
10586
+ if (tokenResult.access_token) {
10587
+ return tokenResult.access_token;
10588
+ }
10589
+ if (tokenResult.error === "authorization_pending") {
10590
+ return null;
10591
+ }
10592
+ if (tokenResult.error === "expired_token") {
10593
+ throw new Error("The device code has expired. Please try again.");
10594
+ }
10595
+ if (tokenResult.error) {
10596
+ throw new Error(
10597
+ `Authentication failed: ${tokenResult.error_description ?? tokenResult.error}`
10598
+ );
10599
+ }
10600
+ return null;
10601
+ }
10602
+ async function pollForToken(apiUrl, deviceAuth, callbacks) {
10603
+ const startTime = Date.now();
10604
+ const maxWaitTime = deviceAuth.expires_in * 1e3;
10605
+ const pollInterval = (deviceAuth.interval || 5) * 1e3;
10606
+ let isFirstPoll = true;
10607
+ while (Date.now() - startTime < maxWaitTime) {
10608
+ if (!isFirstPoll) {
10609
+ await delay2(pollInterval);
10610
+ }
10611
+ isFirstPoll = false;
10612
+ const tokenResult = await exchangeToken2(apiUrl, deviceAuth.device_code);
10613
+ const accessToken = handleTokenResult(tokenResult);
10614
+ if (accessToken) {
10615
+ return accessToken;
10616
+ }
10617
+ callbacks?.onPolling?.();
10618
+ }
10619
+ throw new Error("Authentication timed out. Please try again.");
10620
+ }
10621
+ async function runAuthFlow(callbacks, apiUrl) {
10622
+ const targetApiUrl = apiUrl ?? await getApiUrl();
10623
+ callbacks?.onInitiating?.();
10624
+ const deviceAuth = await requestDeviceCode2(targetApiUrl);
10625
+ const verificationUrl = `${targetApiUrl}${deviceAuth.verification_path}`;
10626
+ const expiresInMinutes = Math.floor(deviceAuth.expires_in / 60);
10627
+ callbacks?.onDeviceCodeReady?.(
10628
+ verificationUrl,
10629
+ deviceAuth.user_code,
10630
+ expiresInMinutes
10631
+ );
10632
+ try {
10633
+ const accessToken = await pollForToken(targetApiUrl, deviceAuth, callbacks);
10634
+ await saveConfig({
10635
+ token: accessToken,
10636
+ apiUrl: targetApiUrl
10637
+ });
10638
+ callbacks?.onSuccess?.();
10639
+ } catch (error) {
10640
+ callbacks?.onError?.(error);
10641
+ throw error;
10642
+ }
10643
+ }
10644
+
10645
+ // src/lib/domain/onboard/model-provider.ts
10646
+ async function checkModelProviderStatus() {
10647
+ const response = await listModelProviders();
10648
+ return {
10649
+ hasProvider: response.modelProviders.length > 0,
10650
+ providers: response.modelProviders
10651
+ };
10652
+ }
10653
+ function getProviderChoices() {
10654
+ return Object.keys(MODEL_PROVIDER_TYPES).map(
10655
+ (type) => ({
10656
+ type,
10657
+ label: MODEL_PROVIDER_TYPES[type].label,
10658
+ helpText: MODEL_PROVIDER_TYPES[type].helpText,
10659
+ credentialLabel: MODEL_PROVIDER_TYPES[type].credentialLabel
10660
+ })
10661
+ );
10662
+ }
10663
+ async function setupModelProvider(type, credential, options) {
10664
+ const response = await upsertModelProvider({
10665
+ type,
10666
+ credential,
10667
+ convert: options?.convert
10668
+ });
10669
+ return {
10670
+ provider: response.provider,
10671
+ created: response.created,
10672
+ isDefault: response.provider.isDefault,
10673
+ framework: response.provider.framework
10674
+ };
10675
+ }
10676
+
10677
+ // src/lib/domain/onboard/claude-setup.ts
10458
10678
  import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
10459
10679
  import path14 from "path";
10460
10680
  var SKILL_DIR = ".claude/skills/vm0-agent-builder";
10681
+ var SKILL_FILE = "SKILL.md";
10682
+ function getSkillContent() {
10683
+ return `---
10684
+ name: vm0-agent-builder
10685
+ description: Build VM0 agents by creating AGENTS.md and vm0.yaml. Use when users describe what agent they want to build.
10686
+ ---
10687
+
10688
+ # VM0 Agent Builder
10689
+
10690
+ Build AI agents that run in VM0's secure sandbox environment. This skill helps you create the two essential files: \`AGENTS.md\` (agent instructions) and \`vm0.yaml\` (configuration).
10691
+
10692
+ ## Workflow
10693
+
10694
+ ### Step 1: Understand the Goal
10695
+
10696
+ First, clarify what the user wants their agent to do:
10697
+ - What task should the agent accomplish?
10698
+ - What inputs does it need? (files, APIs, websites)
10699
+ - What outputs should it produce? (reports, files, notifications)
10700
+ - Should it run once or on a schedule?
10701
+
10702
+ ### Step 2: Create AGENTS.md
10703
+
10704
+ Write clear, step-by-step instructions. The agent will follow these exactly.
10705
+
10706
+ **Template:**
10707
+
10708
+ \`\`\`markdown
10709
+ # [Agent Name]
10710
+
10711
+ You are a [role description].
10712
+
10713
+ ## Workflow
10714
+
10715
+ 1. [First action - be specific]
10716
+ 2. [Second action - include details]
10717
+ 3. [Continue with clear steps...]
10718
+
10719
+ ## Output
10720
+
10721
+ Write results to \`[filename]\` in the current directory.
10722
+ \`\`\`
10723
+
10724
+ **Writing Tips:**
10725
+ - Be specific: "Read the top 10 stories" not "Read some stories"
10726
+ - One action per step: Keep steps focused and atomic
10727
+ - Specify output: Exact filenames and formats
10728
+ - Use active voice: "Create a file" not "A file should be created"
10729
+
10730
+ ### Step 3: Create vm0.yaml
10731
+
10732
+ Configure the agent with required skills and environment variables.
10733
+
10734
+ \`\`\`yaml
10735
+ version: "1.0"
10736
+
10737
+ agents:
10738
+ [agent-name]:
10739
+ framework: claude-code
10740
+ instructions: AGENTS.md
10741
+ # Pre-install GitHub CLI (optional)
10742
+ apps:
10743
+ - github
10744
+ # Add skills the agent needs (optional)
10745
+ skills:
10746
+ - https://github.com/vm0-ai/vm0-skills/tree/main/[skill-name]
10747
+ # Mount volumes for input files (optional)
10748
+ volumes:
10749
+ - my-volume:/home/user/input
10750
+ # Environment variables (optional)
10751
+ environment:
10752
+ API_KEY: "\${{ secrets.API_KEY }}"
10753
+ \`\`\`
10754
+
10755
+ ### Step 4: Test the Agent
10756
+
10757
+ After creating both files, the user runs:
10758
+
10759
+ \`\`\`bash
10760
+ vm0 cook "start working"
10761
+ \`\`\`
10762
+
10763
+ This command:
10764
+ 1. Uploads the configuration to VM0
10765
+ 2. Runs the agent in a secure sandbox
10766
+ 3. Downloads results to the \`artifact/\` directory
10767
+
10768
+ ## Available Skills
10769
+
10770
+ Skills give agents access to external services. Add them to vm0.yaml when needed.
10771
+
10772
+ **Popular Skills:**
10773
+
10774
+ | Skill | Use Case |
10775
+ |-------|----------|
10776
+ | \`github\` | Read/write issues, PRs, files |
10777
+ | \`slack\` | Send messages to channels |
10778
+ | \`notion\` | Access Notion pages/databases |
10779
+ | \`firecrawl\` | Scrape and extract web content |
10780
+ | \`supabase\` | Database operations |
10781
+ | \`google-sheets\` | Read/write spreadsheets |
10782
+ | \`linear\` | Project management |
10783
+ | \`discord\` | Send Discord messages |
10784
+ | \`gmail\` | Send emails |
10785
+ | \`openai\` | Embeddings, additional AI calls |
10786
+
10787
+ **All 79 skills:** https://github.com/vm0-ai/vm0-skills
10788
+
10789
+ **Skill URL format:**
10790
+ \`\`\`
10791
+ https://github.com/vm0-ai/vm0-skills/tree/main/[skill-name]
10792
+ \`\`\`
10793
+
10794
+ ## Examples
10795
+
10796
+ ### HackerNews Curator
10797
+
10798
+ **AGENTS.md:**
10799
+ \`\`\`markdown
10800
+ # HackerNews AI Curator
10801
+
10802
+ You are a content curator that finds AI-related articles on HackerNews.
10803
+
10804
+ ## Workflow
10805
+
10806
+ 1. Go to https://news.ycombinator.com
10807
+ 2. Read the top 30 stories
10808
+ 3. Filter for AI, ML, and LLM related content
10809
+ 4. For each relevant article, extract:
10810
+ - Title and URL
10811
+ - 2-3 sentence summary
10812
+ - Why it matters
10813
+ 5. Write findings to \`daily-digest.md\`
10814
+
10815
+ ## Output
10816
+
10817
+ Create \`daily-digest.md\` with today's date as the header.
10818
+ Format as a bulleted list with links.
10819
+ \`\`\`
10820
+
10821
+ **vm0.yaml:**
10822
+ \`\`\`yaml
10823
+ version: "1.0"
10824
+
10825
+ agents:
10826
+ hn-curator:
10827
+ framework: claude-code
10828
+ instructions: AGENTS.md
10829
+ \`\`\`
10830
+
10831
+ ### GitHub Issue Reporter
10832
+
10833
+ **AGENTS.md:**
10834
+ \`\`\`markdown
10835
+ # GitHub Issue Reporter
10836
+
10837
+ You are a GitHub analyst that creates issue summary reports.
10838
+
10839
+ ## Workflow
10840
+
10841
+ 1. List all open issues in the repository
10842
+ 2. Group by labels: bug, feature, documentation, other
10843
+ 3. For each group, report:
10844
+ - Total count
10845
+ - Oldest issue (with age in days)
10846
+ - Most commented issue
10847
+ 4. Write report to \`issue-report.md\`
10848
+
10849
+ ## Output
10850
+
10851
+ Create \`issue-report.md\` with sections for each label group.
10852
+ Include links to referenced issues.
10853
+ \`\`\`
10854
+
10855
+ **vm0.yaml:**
10856
+ \`\`\`yaml
10857
+ version: "1.0"
10858
+
10859
+ agents:
10860
+ issue-reporter:
10861
+ framework: claude-code
10862
+ instructions: AGENTS.md
10863
+ skills:
10864
+ - https://github.com/vm0-ai/vm0-skills/tree/main/github
10865
+ environment:
10866
+ GITHUB_REPO: "\${{ vars.GITHUB_REPO }}"
10867
+ \`\`\`
10868
+
10869
+ ### Slack Daily Digest
10870
+
10871
+ **AGENTS.md:**
10872
+ \`\`\`markdown
10873
+ # Slack Daily Digest
10874
+
10875
+ You are an assistant that posts daily summaries to Slack.
10876
+
10877
+ ## Workflow
10878
+
10879
+ 1. Read the contents of \`updates.md\` from the input volume
10880
+ 2. Summarize the key points (max 5 bullets)
10881
+ 3. Format as a Slack message with emoji headers
10882
+ 4. Post to the #daily-updates channel
10883
+
10884
+ ## Output
10885
+
10886
+ Post the summary to Slack. Write a copy to \`sent-message.md\`.
10887
+ \`\`\`
10888
+
10889
+ **vm0.yaml:**
10890
+ \`\`\`yaml
10891
+ version: "1.0"
10892
+
10893
+ agents:
10894
+ slack-digest:
10895
+ framework: claude-code
10896
+ instructions: AGENTS.md
10897
+ skills:
10898
+ - https://github.com/vm0-ai/vm0-skills/tree/main/slack
10899
+ environment:
10900
+ SLACK_CHANNEL: "\${{ vars.SLACK_CHANNEL }}"
10901
+ \`\`\`
10902
+
10903
+ ## Environment Variables
10904
+
10905
+ Use environment variables for sensitive data and configuration:
10906
+
10907
+ \`\`\`yaml
10908
+ environment:
10909
+ # Secrets (encrypted, for API keys)
10910
+ API_KEY: "\${{ secrets.API_KEY }}"
10911
+
10912
+ # Variables (plain text, for config)
10913
+ REPO_NAME: "\${{ vars.REPO_NAME }}"
10914
+
10915
+ # Credentials (from vm0 credential storage)
10916
+ MY_TOKEN: "\${{ credentials.MY_TOKEN }}"
10917
+ \`\`\`
10918
+
10919
+ Set credentials with (names must be UPPERCASE):
10920
+ \`\`\`bash
10921
+ vm0 credential set API_KEY "your-api-key"
10922
+ \`\`\`
10923
+
10924
+ ## Troubleshooting
10925
+
10926
+ **Agent doesn't follow instructions:**
10927
+ - Make steps more specific and explicit
10928
+ - Add "Do not..." constraints for unwanted behavior
10929
+ - Break complex steps into smaller sub-steps
10930
+
10931
+ **Agent can't access a service:**
10932
+ - Add the required skill to vm0.yaml
10933
+ - Set up credentials with \`vm0 credential set\`
10934
+
10935
+ **Output is in wrong format:**
10936
+ - Provide an exact template in the instructions
10937
+ - Include a small example of expected output
10938
+
10939
+ ## Next Steps After Creating Files
10940
+
10941
+ \`\`\`bash
10942
+ # Run your agent
10943
+ vm0 cook "start working"
10944
+
10945
+ # View logs if needed
10946
+ vm0 logs [run-id]
10947
+
10948
+ # Results are in artifact/ directory
10949
+ ls artifact/
10950
+
10951
+ # Continue from where agent left off
10952
+ vm0 cook continue "keep going"
10953
+
10954
+ # Resume from a checkpoint
10955
+ vm0 cook resume "try again"
10956
+ \`\`\`
10957
+ `;
10958
+ }
10959
+ async function installClaudeSkill(targetDir = process.cwd()) {
10960
+ const skillDirPath = path14.join(targetDir, SKILL_DIR);
10961
+ const skillFilePath = path14.join(skillDirPath, SKILL_FILE);
10962
+ await mkdir7(skillDirPath, { recursive: true });
10963
+ await writeFile7(skillFilePath, getSkillContent());
10964
+ return {
10965
+ skillDir: skillDirPath,
10966
+ skillFile: skillFilePath
10967
+ };
10968
+ }
10969
+
10970
+ // src/commands/onboard.ts
10971
+ var DEFAULT_AGENT_NAME = "my-vm0-agent";
10972
+ async function handleAuthentication(ctx) {
10973
+ ctx.updateProgress(0, "in-progress");
10974
+ const authenticated = await isAuthenticated();
10975
+ if (authenticated) {
10976
+ ctx.updateProgress(0, "completed");
10977
+ return;
10978
+ }
10979
+ if (!ctx.interactive) {
10980
+ console.error(chalk48.red("Error: Not authenticated"));
10981
+ console.error("Run 'vm0 auth login' first or set VM0_TOKEN");
10982
+ process.exit(1);
10983
+ }
10984
+ console.log(chalk48.dim("Authentication required..."));
10985
+ console.log();
10986
+ await runAuthFlow({
10987
+ onInitiating: () => {
10988
+ console.log("Initiating authentication...");
10989
+ },
10990
+ onDeviceCodeReady: (url, code, expiresIn) => {
10991
+ console.log(chalk48.green("\nDevice code generated"));
10992
+ console.log(chalk48.cyan(`
10993
+ To authenticate, visit: ${url}`));
10994
+ console.log(`And enter this code: ${chalk48.bold(code)}`);
10995
+ console.log(`
10996
+ The code expires in ${expiresIn} minutes.`);
10997
+ console.log("\nWaiting for authentication...");
10998
+ },
10999
+ onPolling: () => {
11000
+ process.stdout.write(chalk48.dim("."));
11001
+ },
11002
+ onSuccess: () => {
11003
+ console.log(chalk48.green("\nAuthentication successful!"));
11004
+ console.log("Your credentials have been saved.");
11005
+ },
11006
+ onError: (error) => {
11007
+ console.error(chalk48.red(`
11008
+ ${error.message}`));
11009
+ process.exit(1);
11010
+ }
11011
+ });
11012
+ ctx.updateProgress(0, "completed");
11013
+ }
11014
+ async function handleModelProvider(ctx) {
11015
+ ctx.updateProgress(1, "in-progress");
11016
+ const providerStatus = await checkModelProviderStatus();
11017
+ if (providerStatus.hasProvider) {
11018
+ ctx.updateProgress(1, "completed");
11019
+ return;
11020
+ }
11021
+ if (!ctx.interactive) {
11022
+ console.error(chalk48.red("Error: No model provider configured"));
11023
+ console.error("Run 'vm0 model-provider setup' first");
11024
+ process.exit(1);
11025
+ }
11026
+ console.log(chalk48.dim("Model provider setup required..."));
11027
+ console.log();
11028
+ const choices = getProviderChoices();
11029
+ const providerType = await promptSelect(
11030
+ "Select provider type:",
11031
+ choices.map((c20) => ({
11032
+ title: c20.label,
11033
+ value: c20.type,
11034
+ description: c20.helpText
11035
+ }))
11036
+ );
11037
+ if (!providerType) {
11038
+ process.exit(0);
11039
+ }
11040
+ const selectedChoice = choices.find((c20) => c20.type === providerType);
11041
+ if (selectedChoice) {
11042
+ console.log();
11043
+ console.log(chalk48.dim(selectedChoice.helpText));
11044
+ console.log();
11045
+ }
11046
+ const credential = await promptPassword(
11047
+ `Enter your ${selectedChoice?.credentialLabel ?? "credential"}:`
11048
+ );
11049
+ if (!credential) {
11050
+ console.log(chalk48.dim("Cancelled"));
11051
+ process.exit(0);
11052
+ }
11053
+ const result = await setupModelProvider(providerType, credential);
11054
+ console.log(
11055
+ chalk48.green(
11056
+ `
11057
+ \u2713 Model provider "${providerType}" ${result.created ? "created" : "updated"}${result.isDefault ? ` (default for ${result.framework})` : ""}`
11058
+ )
11059
+ );
11060
+ ctx.updateProgress(1, "completed");
11061
+ }
11062
+ async function handleAgentCreation(ctx) {
11063
+ ctx.updateProgress(2, "in-progress");
11064
+ let agentName = ctx.options.name ?? DEFAULT_AGENT_NAME;
11065
+ if (!ctx.options.yes && !ctx.options.name && ctx.interactive) {
11066
+ const inputName = await promptText(
11067
+ "Enter agent name:",
11068
+ DEFAULT_AGENT_NAME,
11069
+ (value) => {
11070
+ if (!validateAgentName(value)) {
11071
+ return "Invalid name: 3-64 chars, alphanumeric + hyphens, start/end with letter/number";
11072
+ }
11073
+ return true;
11074
+ }
11075
+ );
11076
+ if (!inputName) {
11077
+ process.exit(0);
11078
+ }
11079
+ agentName = inputName;
11080
+ }
11081
+ if (!validateAgentName(agentName)) {
11082
+ console.error(
11083
+ chalk48.red(
11084
+ "Invalid agent name: must be 3-64 chars, alphanumeric + hyphens"
11085
+ )
11086
+ );
11087
+ process.exit(1);
11088
+ }
11089
+ if (existsSync10(agentName)) {
11090
+ console.error(chalk48.red(`
11091
+ \u2717 ${agentName}/ already exists`));
11092
+ console.log();
11093
+ console.log("Remove it first or choose a different name:");
11094
+ console.log(chalk48.cyan(` rm -rf ${agentName}`));
11095
+ process.exit(1);
11096
+ }
11097
+ if (!ctx.options.yes && ctx.interactive) {
11098
+ const confirmed = await promptConfirm(`Create ${agentName}/?`, true);
11099
+ if (!confirmed) {
11100
+ console.log(chalk48.dim("Cancelled"));
11101
+ process.exit(0);
11102
+ }
11103
+ }
11104
+ await mkdir8(agentName, { recursive: true });
11105
+ console.log(chalk48.green(`\u2713 Created ${agentName}/`));
11106
+ ctx.updateProgress(2, "completed");
11107
+ return agentName;
11108
+ }
11109
+ async function handleSkillInstallation(ctx, agentName) {
11110
+ ctx.updateProgress(3, "in-progress");
11111
+ const skillResult = await installClaudeSkill(agentName);
11112
+ console.log(
11113
+ chalk48.green(
11114
+ `\u2713 Installed vm0-agent-builder skill to ${skillResult.skillDir}`
11115
+ )
11116
+ );
11117
+ ctx.updateProgress(3, "completed");
11118
+ }
11119
+ function printNextSteps(agentName) {
11120
+ console.log();
11121
+ console.log(chalk48.bold("Next step:"));
11122
+ console.log();
11123
+ console.log(
11124
+ ` ${chalk48.cyan(`cd ${agentName} && claude "/vm0-agent-builder I want to build an agent that..."`)}`
11125
+ );
11126
+ console.log();
11127
+ }
11128
+ var onboardCommand = new Command46().name("onboard").description("Guided setup for new VM0 users").option("-y, --yes", "Skip confirmation prompts").option("--name <name>", `Agent name (default: ${DEFAULT_AGENT_NAME})`).action(async (options) => {
11129
+ const interactive = isInteractive();
11130
+ if (interactive) {
11131
+ console.log();
11132
+ renderOnboardWelcome();
11133
+ console.log();
11134
+ }
11135
+ const progress = createOnboardProgress();
11136
+ const updateProgress = (index, status) => {
11137
+ progress.update(index, status);
11138
+ if (interactive) {
11139
+ console.clear();
11140
+ renderOnboardWelcome();
11141
+ console.log();
11142
+ progress.render();
11143
+ console.log();
11144
+ }
11145
+ };
11146
+ if (interactive) {
11147
+ progress.render();
11148
+ console.log();
11149
+ }
11150
+ const ctx = { interactive, options, updateProgress };
11151
+ await handleAuthentication(ctx);
11152
+ await handleModelProvider(ctx);
11153
+ const agentName = await handleAgentCreation(ctx);
11154
+ await handleSkillInstallation(ctx, agentName);
11155
+ printNextSteps(agentName);
11156
+ });
11157
+
11158
+ // src/commands/setup-claude.ts
11159
+ import { Command as Command47 } from "commander";
11160
+ import chalk49 from "chalk";
11161
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "fs/promises";
11162
+ import path15 from "path";
11163
+ var SKILL_DIR2 = ".claude/skills/vm0-agent-builder";
10461
11164
  var SKILL_CONTENT = `---
10462
11165
  name: vm0-agent-builder
10463
11166
  description: Guide for building VM0 agents with Claude's help. Use this skill when users want to create or improve their agent's AGENTS.md and vm0.yaml configuration.
@@ -10665,132 +11368,31 @@ Write results to the artifact directory.
10665
11368
  - Provide exact templates for output files
10666
11369
  - Include example output in the instructions
10667
11370
  `;
10668
- var setupClaudeCommand = new Command46().name("setup-claude").description("Add/update Claude skill for agent building").action(async () => {
10669
- console.log(chalk46.dim("Installing vm0-agent-builder skill..."));
10670
- await mkdir7(SKILL_DIR, { recursive: true });
10671
- await writeFile7(path14.join(SKILL_DIR, "SKILL.md"), SKILL_CONTENT);
11371
+ var setupClaudeCommand = new Command47().name("setup-claude").description("Add/update Claude skill for agent building").option(
11372
+ "--agent-dir <dir>",
11373
+ "Agent directory (shown in next step instructions)"
11374
+ ).action(async (options) => {
11375
+ console.log(chalk49.dim("Installing vm0-agent-builder skill..."));
11376
+ await mkdir9(SKILL_DIR2, { recursive: true });
11377
+ await writeFile8(path15.join(SKILL_DIR2, "SKILL.md"), SKILL_CONTENT);
10672
11378
  console.log(
10673
- chalk46.green(`Done Installed vm0-agent-builder skill to ${SKILL_DIR}`)
11379
+ chalk49.green(`Done Installed vm0-agent-builder skill to ${SKILL_DIR2}`)
10674
11380
  );
10675
11381
  console.log();
10676
11382
  console.log("Next step:");
11383
+ const cdPrefix = options.agentDir ? `cd ${options.agentDir} && ` : "";
10677
11384
  console.log(
10678
- chalk46.cyan(
10679
- ' claude /vm0-agent-builder "I want to build an agent that..."'
11385
+ chalk49.cyan(
11386
+ ` ${cdPrefix}claude "/vm0-agent-builder I want to build an agent that..."`
10680
11387
  )
10681
11388
  );
10682
11389
  });
10683
11390
 
10684
- // src/commands/onboard.ts
10685
- var DEMO_AGENT_DIR = "vm0-demo-agent";
10686
- var DEMO_AGENT_NAME = "vm0-demo-agent";
10687
- var onboardCommand = new Command47().name("onboard").description("Guided setup for new VM0 users").option("-y, --yes", "Skip confirmation prompts").option(
10688
- "--method <method>",
10689
- "Agent building method: claude or manual",
10690
- void 0
10691
- ).action(async (options) => {
10692
- const token = await getToken();
10693
- if (token) {
10694
- console.log(chalk47.green("Done Authenticated"));
10695
- } else {
10696
- console.log(chalk47.dim("Authentication required..."));
10697
- console.log();
10698
- await loginCommand.parseAsync([], { from: "user" });
10699
- }
10700
- try {
10701
- const result = await listModelProviders();
10702
- if (result.modelProviders.length > 0) {
10703
- console.log(chalk47.green("Done Model provider configured"));
10704
- } else {
10705
- console.log(chalk47.dim("Model provider setup required..."));
10706
- console.log();
10707
- await setupCommand2.parseAsync([], { from: "user" });
10708
- }
10709
- } catch {
10710
- console.log(chalk47.dim("Setting up model provider..."));
10711
- console.log();
10712
- await setupCommand2.parseAsync([], { from: "user" });
10713
- }
10714
- let createAgent = options.yes;
10715
- if (!createAgent && isInteractive()) {
10716
- const response = await prompts3(
10717
- {
10718
- type: "confirm",
10719
- name: "create",
10720
- message: `Create ${DEMO_AGENT_DIR}?`,
10721
- initial: true
10722
- },
10723
- { onCancel: () => process.exit(0) }
10724
- );
10725
- createAgent = response.create;
10726
- }
10727
- if (!createAgent) {
10728
- console.log(chalk47.dim("Skipped agent creation"));
10729
- return;
10730
- }
10731
- if (existsSync10(DEMO_AGENT_DIR)) {
10732
- console.log(chalk47.red(`x ${DEMO_AGENT_DIR}/ already exists`));
10733
- console.log();
10734
- console.log("Remove it first or use a different directory:");
10735
- console.log(chalk47.cyan(` rm -rf ${DEMO_AGENT_DIR}`));
10736
- process.exit(1);
10737
- }
10738
- await mkdir8(DEMO_AGENT_DIR, { recursive: true });
10739
- const originalDir = process.cwd();
10740
- process.chdir(DEMO_AGENT_DIR);
10741
- try {
10742
- await initCommand3.parseAsync(["--name", DEMO_AGENT_NAME], {
10743
- from: "user"
10744
- });
10745
- } finally {
10746
- process.chdir(originalDir);
10747
- }
10748
- console.log();
10749
- let method = options.method;
10750
- if (!method && isInteractive()) {
10751
- const response = await prompts3(
10752
- {
10753
- type: "select",
10754
- name: "method",
10755
- message: "How would you like to build your agent?",
10756
- choices: [
10757
- {
10758
- title: "Use `vm0 setup-claude` to let Claude help (Recommended)",
10759
- value: "claude"
10760
- },
10761
- {
10762
- title: "I will do it myself (Edit `AGENTS.md` and `vm0.yaml`)",
10763
- value: "manual"
10764
- }
10765
- ]
10766
- },
10767
- { onCancel: () => process.exit(0) }
10768
- );
10769
- method = response.method;
10770
- }
10771
- if (method === "claude") {
10772
- process.chdir(DEMO_AGENT_DIR);
10773
- try {
10774
- await setupClaudeCommand.parseAsync([], { from: "user" });
10775
- } finally {
10776
- process.chdir(originalDir);
10777
- }
10778
- } else {
10779
- console.log("Next steps:");
10780
- console.log(` 1. ${chalk47.cyan(`cd ${DEMO_AGENT_DIR}`)}`);
10781
- console.log(
10782
- ` 2. Edit ${chalk47.cyan("AGENTS.md")} to define your agent's workflow`
10783
- );
10784
- console.log(` 3. Edit ${chalk47.cyan("vm0.yaml")} to configure skills`);
10785
- console.log(` 4. Run ${chalk47.cyan('vm0 cook "start working"')} to test`);
10786
- }
10787
- });
10788
-
10789
11391
  // src/index.ts
10790
11392
  var program = new Command48();
10791
- program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.2.0");
11393
+ program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.3.0");
10792
11394
  program.command("info").description("Display environment information").action(async () => {
10793
- console.log(chalk48.bold("System Information:"));
11395
+ console.log(chalk50.bold("System Information:"));
10794
11396
  console.log(`Node Version: ${process.version}`);
10795
11397
  console.log(`Platform: ${process.platform}`);
10796
11398
  console.log(`Architecture: ${process.arch}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",