@runtypelabs/cli 0.1.10 → 0.2.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/README.md CHANGED
@@ -10,6 +10,12 @@ Command-line interface for the Runtype AI platform.
10
10
  npm install -g @runtypelabs/cli
11
11
  ```
12
12
 
13
+ Or run without installing:
14
+
15
+ ```bash
16
+ npx @runtypelabs/cli <command>
17
+ ```
18
+
13
19
  ## Quick Start
14
20
 
15
21
  ### 1. Create Account & Authenticate
@@ -54,6 +60,27 @@ runtype records create --name "My Record" --type "document"
54
60
  runtype records list --type document
55
61
  ```
56
62
 
63
+ ### 4. Run Multi-Session Agent Tasks
64
+
65
+ Use `runtype marathon` (or `runtype agents task`) to run long-running, multi-session agent tasks with real-time streaming output.
66
+
67
+ ```bash
68
+ # Run a task — agent output streams to your terminal in real time
69
+ runtype marathon "Code Builder" -m "Refactor the auth module to use JWT tokens"
70
+
71
+ # Set a budget and session limit, save progress with a custom name
72
+ runtype marathon agent_abc123 -m "Write integration tests for the API" \
73
+ --max-sessions 10 --max-cost 2.50 --name "api-tests"
74
+
75
+ # Resume an interrupted task (picks up where it left off)
76
+ runtype marathon "Code Builder" -m "Refactor the auth module to use JWT tokens" \
77
+ --resume --name "auth-refactor" --debug
78
+
79
+ # Sync progress to a Runtype record (visible in the dashboard)
80
+ runtype marathon "Code Builder" -m "Build the payments integration" \
81
+ --max-sessions 20 --track --name "payments"
82
+ ```
83
+
57
84
  ## Chat Commands
58
85
 
59
86
  During a chat session, you can use these special commands:
package/dist/index.js CHANGED
@@ -2783,7 +2783,7 @@ var import_chalk16 = __toESM(require("chalk"));
2783
2783
  var import_ora11 = __toESM(require("ora"));
2784
2784
  var import_sdk6 = require("@runtypelabs/sdk");
2785
2785
 
2786
- // src/commands/agents-run-task.ts
2786
+ // src/commands/agents-task.ts
2787
2787
  init_cjs_shims();
2788
2788
  var import_commander11 = require("commander");
2789
2789
  var import_chalk15 = __toESM(require("chalk"));
@@ -2810,206 +2810,221 @@ function loadState(filePath) {
2810
2810
  function saveState(filePath, state) {
2811
2811
  const dir = path4.dirname(filePath);
2812
2812
  fs3.mkdirSync(dir, { recursive: true });
2813
- state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2814
2813
  fs3.writeFileSync(filePath, JSON.stringify(state, null, 2));
2815
2814
  }
2816
- var runTaskCommand = new import_commander11.Command("run-task").description("Run a long-task agent with local state persistence").argument("<agent>", "Agent ID or name").requiredOption("-m, --message <text>", "Task message for the agent").option("--max-sessions <n>", "Maximum sessions", "50").option("--max-cost <n>", "Budget in USD").option("--name <name>", "Task name (used for state file, defaults to agent ID)").option("--state-dir <path>", "Directory for state files (default: .runtype/tasks/)").option("--resume", "Resume from existing local state").option("--track-progress", "Also sync progress to the Runtype dashboard").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").action(
2817
- async (agent, options) => {
2818
- const apiKey = await ensureAuth();
2819
- if (!apiKey) return;
2820
- const client = new import_sdk5.RuntypeClient({
2821
- apiKey,
2822
- baseUrl: getApiUrl()
2823
- });
2824
- let agentId = agent;
2825
- if (!agent.startsWith("agent_")) {
2826
- const spinner = (0, import_ora10.default)("Looking up agent by name...").start();
2827
- try {
2828
- const list = await client.agents.list();
2829
- const found = list.data.find(
2830
- (a) => a.name.toLowerCase() === agent.toLowerCase()
2831
- );
2832
- if (found) {
2833
- agentId = found.id;
2834
- spinner.succeed(`Found agent: ${import_chalk15.default.green(agentId)}`);
2835
- } else {
2836
- spinner.fail(`No agent found with name "${agent}"`);
2837
- console.log(import_chalk15.default.gray(` Create one with: runtype agents create -n "${agent}"`));
2838
- process.exit(1);
2839
- }
2840
- } catch (error) {
2841
- spinner.fail("Failed to look up agent");
2842
- const message = error instanceof Error ? error.message : "Unknown error";
2843
- console.error(import_chalk15.default.red(message));
2815
+ async function taskAction(agent, options) {
2816
+ const apiKey = await ensureAuth();
2817
+ if (!apiKey) return;
2818
+ const client = new import_sdk5.RuntypeClient({
2819
+ apiKey,
2820
+ baseUrl: getApiUrl()
2821
+ });
2822
+ let agentId = agent;
2823
+ if (!agent.startsWith("agent_")) {
2824
+ const spinner = (0, import_ora10.default)("Looking up agent by name...").start();
2825
+ try {
2826
+ const list = await client.agents.list();
2827
+ const found = list.data.find(
2828
+ (a) => a.name.toLowerCase() === agent.toLowerCase()
2829
+ );
2830
+ if (found) {
2831
+ agentId = found.id;
2832
+ spinner.succeed(`Found agent: ${import_chalk15.default.green(agentId)}`);
2833
+ } else {
2834
+ spinner.fail(`No agent found with name "${agent}"`);
2835
+ console.log(import_chalk15.default.gray(` Create one with: runtype agents create -n "${agent}"`));
2844
2836
  process.exit(1);
2845
2837
  }
2838
+ } catch (error) {
2839
+ spinner.fail("Failed to look up agent");
2840
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
2841
+ console.error(import_chalk15.default.red(errMsg));
2842
+ process.exit(1);
2846
2843
  }
2847
- const taskName = options.name || agentId;
2848
- const filePath = stateFilePath(taskName, options.stateDir);
2849
- const maxSessions = parseInt(options.maxSessions, 10);
2850
- const maxCost = options.maxCost ? parseFloat(options.maxCost) : void 0;
2851
- let localState;
2852
- let priorSessionCount = 0;
2853
- let priorCost = 0;
2854
- if (options.resume) {
2855
- const existing = loadState(filePath);
2856
- if (!existing) {
2857
- console.error(import_chalk15.default.red(`No state file found at ${filePath}`));
2858
- console.log(import_chalk15.default.gray(" Run without --resume to start a new task."));
2859
- process.exit(1);
2860
- }
2861
- if (existing.status === "complete") {
2862
- console.log(import_chalk15.default.yellow("This task already completed."));
2863
- if (options.json) printJson(existing);
2864
- return;
2865
- }
2866
- localState = existing;
2867
- localState.status = "running";
2868
- priorSessionCount = localState.sessionCount;
2869
- priorCost = localState.totalCost;
2870
- console.log(
2871
- import_chalk15.default.cyan(
2872
- `Resuming from session ${priorSessionCount} ($${priorCost.toFixed(4)} spent)`
2873
- )
2874
- );
2875
- } else {
2876
- localState = {
2877
- agentId,
2878
- taskName,
2879
- message: options.message,
2880
- status: "running",
2881
- sessionCount: 0,
2882
- totalCost: 0,
2883
- context: {},
2884
- lastOutput: "",
2885
- sessions: [],
2886
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2887
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2888
- };
2844
+ }
2845
+ const taskName = options.name || agentId;
2846
+ const filePath = stateFilePath(taskName, options.stateDir);
2847
+ const maxSessions = parseInt(options.maxSessions, 10);
2848
+ const maxCost = options.maxCost ? parseFloat(options.maxCost) : void 0;
2849
+ let priorSessionCount = 0;
2850
+ let priorCost = 0;
2851
+ if (options.resume) {
2852
+ const existing = loadState(filePath);
2853
+ if (!existing) {
2854
+ console.error(import_chalk15.default.red(`No state file found at ${filePath}`));
2855
+ console.log(import_chalk15.default.gray(" Run without --resume to start a new task."));
2856
+ process.exit(1);
2857
+ }
2858
+ if (existing.status === "complete") {
2859
+ console.log(import_chalk15.default.yellow("This task already completed."));
2860
+ if (options.json) printJson(existing);
2861
+ return;
2889
2862
  }
2890
- let interrupted = false;
2891
- const onSigint = () => {
2892
- if (interrupted) process.exit(1);
2893
- interrupted = true;
2894
- console.log(import_chalk15.default.yellow("\n\nInterrupted \u2014 saving state..."));
2895
- localState.status = "paused";
2896
- saveState(filePath, localState);
2863
+ priorSessionCount = existing.sessionCount;
2864
+ priorCost = existing.totalCost;
2865
+ console.log(
2866
+ import_chalk15.default.cyan(
2867
+ `Resuming from session ${priorSessionCount} ($${priorCost.toFixed(4)} spent)`
2868
+ )
2869
+ );
2870
+ }
2871
+ let lastKnownState = null;
2872
+ let interrupted = false;
2873
+ const onSigint = () => {
2874
+ if (interrupted) process.exit(1);
2875
+ interrupted = true;
2876
+ console.log(import_chalk15.default.yellow("\n\nInterrupted \u2014 saving state..."));
2877
+ if (lastKnownState) {
2878
+ lastKnownState.status = "paused";
2879
+ saveState(filePath, lastKnownState);
2897
2880
  console.log(import_chalk15.default.green(`State saved to ${filePath}`));
2898
- console.log(
2899
- import_chalk15.default.gray(
2900
- ` Resume with: runtype agents run-task ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
2901
- )
2902
- );
2903
- process.exit(0);
2904
- };
2905
- process.on("SIGINT", onSigint);
2906
- const remainingSessions = maxSessions - priorSessionCount;
2907
- console.log(import_chalk15.default.cyan(`
2908
- Running task "${taskName}" on ${agentId}`));
2881
+ }
2909
2882
  console.log(
2910
2883
  import_chalk15.default.gray(
2911
- ` Sessions: ${priorSessionCount > 0 ? `${priorSessionCount} done, ` : ""}${remainingSessions} remaining${maxCost ? ` | Budget: $${maxCost.toFixed(2)}` : ""}`
2884
+ ` Resume with: runtype marathon ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
2912
2885
  )
2913
2886
  );
2914
- console.log(import_chalk15.default.gray(` State: ${filePath}
2887
+ process.exit(0);
2888
+ };
2889
+ process.on("SIGINT", onSigint);
2890
+ const remainingSessions = maxSessions - priorSessionCount;
2891
+ const remainingCost = maxCost ? maxCost - priorCost : void 0;
2892
+ console.log(import_chalk15.default.cyan(`
2893
+ Running task "${taskName}" on ${agentId}`));
2894
+ console.log(
2895
+ import_chalk15.default.gray(
2896
+ ` Sessions: ${priorSessionCount > 0 ? `${priorSessionCount} done, ` : ""}${remainingSessions} remaining${maxCost ? ` | Budget: $${maxCost.toFixed(2)}` : ""}`
2897
+ )
2898
+ );
2899
+ console.log(import_chalk15.default.gray(` State: ${filePath}
2915
2900
  `));
2916
- try {
2917
- const result = await client.agents.runTask(agentId, {
2918
- buildMessages: (state) => {
2919
- const merged = { ...localState.context, ...state.context };
2920
- const isFirstSession = state.sessionCount === 1;
2921
- const totalSession = priorSessionCount + state.sessionCount;
2922
- if (isFirstSession && !options.resume) {
2923
- return [{ role: "user", content: options.message }];
2924
- }
2925
- const progress = merged.progressNotes || merged.progress;
2926
- const progressStr = progress ? typeof progress === "string" ? progress : JSON.stringify(progress) : "No structured progress captured yet";
2927
- const resumeCtx = options.resume && isFirstSession ? `
2928
-
2929
- Resuming from session ${priorSessionCount}. Previous context: ${JSON.stringify(localState.context)}` : "";
2930
- return [
2931
- {
2932
- role: "user",
2933
- content: `${options.message}${resumeCtx}
2934
-
2935
- Progress so far: ${progressStr}
2936
- Session ${totalSession} of ${maxSessions}.`
2937
- }
2938
- ];
2939
- },
2940
- maxSessions: remainingSessions,
2941
- maxCost,
2942
- trackProgress: options.trackProgress ? taskName : void 0,
2943
- debugMode: options.debug,
2944
- onSession: (session) => {
2945
- const sessionCost = session.sessionCount > 1 ? session.totalCost - localState.sessions.slice(-session.sessionCount + 1).reduce((s, e) => s + e.cost, 0) : session.totalCost;
2946
- const thisCost = sessionCost > 0 ? sessionCost : session.totalCost / session.sessionCount;
2947
- localState.sessionCount = priorSessionCount + session.sessionCount;
2948
- localState.totalCost = priorCost + session.totalCost;
2949
- Object.assign(localState.context, session.context);
2950
- localState.lastOutput = session.lastOutput;
2951
- localState.status = session.status;
2952
- localState.sessions.push({
2953
- index: localState.sessionCount,
2954
- cost: thisCost,
2955
- stopReason: session.lastStopReason,
2956
- outputPreview: session.lastOutput.slice(0, 200),
2957
- at: (/* @__PURE__ */ new Date()).toISOString()
2958
- });
2959
- saveState(filePath, localState);
2960
- const total = localState.sessionCount;
2961
- const costStr = import_chalk15.default.yellow(`$${localState.totalCost.toFixed(4)}`);
2962
- const reasonColor = session.lastStopReason === "complete" ? import_chalk15.default.green : import_chalk15.default.gray;
2901
+ try {
2902
+ let currentSession = priorSessionCount;
2903
+ const streamCallbacks = {
2904
+ onAgentStart: () => {
2905
+ currentSession++;
2906
+ console.log(import_chalk15.default.cyan(`
2907
+ \u2500\u2500 Session ${currentSession} \u2500\u2500
2908
+ `));
2909
+ },
2910
+ onTurnDelta: (event) => {
2911
+ if (event.contentType === "text") {
2912
+ process.stdout.write(event.delta);
2913
+ } else if (event.contentType === "thinking" && options.debug) {
2914
+ process.stdout.write(import_chalk15.default.dim(event.delta));
2915
+ }
2916
+ },
2917
+ onToolStart: (event) => {
2918
+ if (options.debug) {
2919
+ console.log(import_chalk15.default.gray(`
2920
+ [tool] ${event.toolName}...`));
2921
+ }
2922
+ },
2923
+ onToolComplete: (event) => {
2924
+ if (options.debug) {
2925
+ const status = event.success ? import_chalk15.default.green("ok") : import_chalk15.default.red("failed");
2926
+ console.log(import_chalk15.default.gray(` [tool] ${event.toolName} \u2192 ${status}`));
2927
+ }
2928
+ },
2929
+ onError: (event) => {
2930
+ console.error(import_chalk15.default.red(`
2931
+ Error: ${event.error}`));
2932
+ }
2933
+ };
2934
+ const result = await client.agents.runTask(agentId, {
2935
+ message: options.message,
2936
+ maxSessions: remainingSessions,
2937
+ maxCost: remainingCost,
2938
+ debugMode: options.debug,
2939
+ stream: true,
2940
+ streamCallbacks,
2941
+ trackProgress: options.track ? taskName : void 0,
2942
+ onSession: (state) => {
2943
+ const adjustedState = {
2944
+ ...state,
2945
+ sessionCount: priorSessionCount + state.sessionCount,
2946
+ totalCost: priorCost + state.totalCost
2947
+ };
2948
+ lastKnownState = adjustedState;
2949
+ saveState(filePath, adjustedState);
2950
+ const latest = state.sessions[state.sessions.length - 1];
2951
+ if (latest) {
2952
+ const total = adjustedState.sessionCount;
2953
+ const costStr = import_chalk15.default.yellow(`$${adjustedState.totalCost.toFixed(4)}`);
2954
+ const reasonColor = latest.stopReason === "complete" ? import_chalk15.default.green : import_chalk15.default.gray;
2963
2955
  console.log(
2964
- ` ${import_chalk15.default.dim(`[${total}/${maxSessions}]`)} ${reasonColor(session.lastStopReason)} | total: ${costStr}`
2956
+ `
2957
+ ${import_chalk15.default.dim(`[${total}/${maxSessions}]`)} ${reasonColor(latest.stopReason)} | total: ${costStr}`
2965
2958
  );
2966
- if (session.lastOutput) {
2967
- const preview = session.lastOutput.slice(0, 120).replace(/\n/g, " ");
2968
- console.log(
2969
- ` ${import_chalk15.default.dim(preview)}${session.lastOutput.length > 120 ? "..." : ""}`
2970
- );
2971
- }
2972
2959
  }
2973
- });
2974
- localState.status = result.status;
2975
- localState.totalCost = priorCost + result.totalCost;
2976
- Object.assign(localState.context, result.context);
2977
- saveState(filePath, localState);
2978
- process.removeListener("SIGINT", onSigint);
2979
- console.log();
2980
- const statusColor = result.status === "complete" ? import_chalk15.default.green : result.status === "budget_exceeded" ? import_chalk15.default.red : import_chalk15.default.yellow;
2981
- console.log(`Status: ${statusColor(localState.status)}`);
2982
- console.log(`Sessions: ${localState.sessionCount}`);
2983
- console.log(`Total cost: ${import_chalk15.default.yellow(`$${localState.totalCost.toFixed(4)}`)}`);
2984
- console.log(`State: ${import_chalk15.default.gray(filePath)}`);
2985
- if (localState.status === "paused" || localState.status === "max_sessions") {
2986
- console.log(
2987
- import_chalk15.default.gray(
2988
- `
2989
- Resume: runtype agents run-task ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
2990
- )
2991
- );
2992
- }
2993
- if (options.json) {
2994
- console.log();
2995
- printJson(localState);
2960
+ if (interrupted) return false;
2996
2961
  }
2997
- } catch (error) {
2998
- localState.status = "paused";
2999
- saveState(filePath, localState);
3000
- process.removeListener("SIGINT", onSigint);
3001
- const message = error instanceof Error ? error.message : "Unknown error";
3002
- console.error(import_chalk15.default.red(`
3003
- Task failed: ${message}`));
3004
- console.log(import_chalk15.default.gray(`State saved to ${filePath} \u2014 resume with --resume`));
3005
- process.exit(1);
2962
+ });
2963
+ const finalState = {
2964
+ agentId,
2965
+ agentName: result.sessions[0]?.stopReason ? agentId : agentId,
2966
+ taskName,
2967
+ status: result.status,
2968
+ sessionCount: priorSessionCount + result.sessionCount,
2969
+ totalCost: priorCost + result.totalCost,
2970
+ lastOutput: result.lastOutput,
2971
+ lastStopReason: result.sessions[result.sessions.length - 1]?.stopReason || "complete",
2972
+ sessions: result.sessions,
2973
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2974
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2975
+ };
2976
+ saveState(filePath, finalState);
2977
+ process.removeListener("SIGINT", onSigint);
2978
+ console.log();
2979
+ const statusColor = result.status === "complete" ? import_chalk15.default.green : result.status === "budget_exceeded" ? import_chalk15.default.red : import_chalk15.default.yellow;
2980
+ console.log(`Status: ${statusColor(result.status)}`);
2981
+ console.log(`Sessions: ${finalState.sessionCount}`);
2982
+ console.log(`Total cost: ${import_chalk15.default.yellow(`$${finalState.totalCost.toFixed(4)}`)}`);
2983
+ console.log(`State: ${import_chalk15.default.gray(filePath)}`);
2984
+ if (result.recordId) {
2985
+ console.log(`Record: ${import_chalk15.default.gray(result.recordId)}`);
2986
+ }
2987
+ if (result.status === "paused" || result.status === "max_sessions") {
2988
+ console.log(
2989
+ import_chalk15.default.gray(
2990
+ `
2991
+ Resume: runtype marathon ${agent} -m "${options.message}" --resume${options.name ? ` --name ${options.name}` : ""}`
2992
+ )
2993
+ );
2994
+ }
2995
+ if (options.json) {
2996
+ console.log();
2997
+ printJson(finalState);
3006
2998
  }
2999
+ } catch (error) {
3000
+ const stateAtError = lastKnownState;
3001
+ if (stateAtError) {
3002
+ stateAtError.status = "paused";
3003
+ saveState(filePath, stateAtError);
3004
+ }
3005
+ process.removeListener("SIGINT", onSigint);
3006
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
3007
+ console.error(import_chalk15.default.red(`
3008
+ Task failed: ${errMsg}`));
3009
+ console.log(import_chalk15.default.gray(`State saved to ${filePath} \u2014 resume with --resume`));
3010
+ process.exit(1);
3007
3011
  }
3012
+ }
3013
+ function applyTaskOptions(cmd) {
3014
+ return cmd.argument("<agent>", "Agent ID or name").requiredOption("-m, --message <text>", "Task message for the agent").option("--max-sessions <n>", "Maximum sessions", "50").option("--max-cost <n>", "Budget in USD").option("--name <name>", "Task name (used for state file, defaults to agent name)").option("--state-dir <path>", "Directory for state files (default: .runtype/tasks/)").option("--resume", "Resume from existing local state").option("--track", "Sync progress to a Runtype record (visible in dashboard)").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").action(taskAction);
3015
+ }
3016
+ var taskCommand = applyTaskOptions(
3017
+ new import_commander11.Command("task").description("Run a multi-session agent task")
3008
3018
  );
3019
+ function createMarathonCommand() {
3020
+ return applyTaskOptions(
3021
+ new import_commander11.Command("marathon").description("Run a multi-session agent task (alias for agents task)")
3022
+ );
3023
+ }
3009
3024
 
3010
3025
  // src/commands/agents.ts
3011
3026
  var agentsCommand = new import_commander12.Command("agents").description("Manage agents");
3012
- agentsCommand.addCommand(runTaskCommand);
3027
+ agentsCommand.addCommand(taskCommand);
3013
3028
  agentsCommand.command("list").description("List all agents").option("--json", "Output as JSON").option("--limit <n>", "Limit results", "20").action(async (options) => {
3014
3029
  const apiKey = await ensureAuth();
3015
3030
  if (!apiKey) return;
@@ -4077,6 +4092,7 @@ program.addCommand(apiKeysCommand);
4077
4092
  program.addCommand(analyticsCommand);
4078
4093
  program.addCommand(billingCommand);
4079
4094
  program.addCommand(flowVersionsCommand);
4095
+ program.addCommand(createMarathonCommand());
4080
4096
  program.exitOverride();
4081
4097
  try {
4082
4098
  if (!process.argv.slice(2).length) {