shortcutxl 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,59 +1,26 @@
1
- # Shortcut Desktop Agent
1
+ # ShortcutXL
2
2
 
3
- A general agent that lives on your computer and has Excel superpowers through [Shortcut](https://shortcut.ai).
3
+ An AI agent that lives on your computer and has Excel superpowers. Made by the Shortcut team [Shortcut](https://shortcut.ai).
4
4
 
5
5
  ```bash
6
6
  npm install -g shortcutxl
7
7
  shortcut
8
8
  ```
9
9
 
10
- > **Important:** This is a CLI tool. You must install it globally with `-g`. Do not use `npm install shortcutxl` without the `-g` flag — it won't work as intended.
10
+ > **Important:** Install globally with `-g`. Do not use `npm install shortcutxl` without it.
11
11
 
12
12
  ## Capabilities
13
13
 
14
- Shortcut Desktop Agent full power compared to any Excel plugin:
15
-
16
- - **Data lives locally** - Your workbooks, files, and skills stay on your machine and are never sent to our servers.
14
+ - **Data lives locally** Workbooks, files, and skills stay on your machine.
17
15
  - **Multi-workbook operations** — Read and write across multiple open workbooks simultaneously.
18
- - **Deep feature control** — Full manipulation of pivots, charts, sensitivity tables, iterative recalculations, and more.
16
+ - **Deep feature control** — Pivots, charts, sensitivity tables, iterative recalculations, and more.
19
17
  - **VBA access** — Read, write, and run macros. Create VBA modules programmatically.
20
18
  - **File system access** — Save to arbitrary paths, open files from disk, export PDFs.
21
- - **Extensible** — Integrate any API or data source (Daloopa, Box, internal tools, etc.) by adding a skill file or a custom tool extension. Your API key is local.
22
- - **User-defined functions (UDFs)** — Create custom Excel formulas powered by Python to pull live data from APIs, run calculations, or query databases.
19
+ - **Extensible** — Integrate any API or data source by adding a skill file or a custom tool extension.
20
+ - **User-defined functions (UDFs)** — Custom Excel formulas powered by Python for live data, calculations, or database queries.
23
21
  - **External data connections** — ODBC, OLE DB, QueryTables, Power Query.
24
22
 
25
- ## Quick Start
26
-
27
- ### Prerequisites
28
-
29
- - **Windows 10 or 11** with **Microsoft Excel 2016 or later** (64-bit)
30
- - **Node.js >= 20** — This gives you `npm`, the package manager used to install the agent.
31
-
32
- If you don't have Node.js installed, open a terminal (Command Prompt, PowerShell, or Windows Terminal) and run:
33
-
34
- ```
35
- winget install OpenJS.NodeJS.LTS
36
- ```
37
-
38
- This will prompt you to approve a Windows admin dialog (UAC) — click **Yes**. After it finishes, **close and reopen your terminal** so the `npm` command is available.
39
-
40
- > **No winget?** Download the installer directly from [nodejs.org](https://nodejs.org) and run it — accept the defaults.
41
-
42
- ### Install
43
-
44
- ```bash
45
- npm install -g shortcutxl
46
- shortcut
47
- ```
48
-
49
- On first launch the agent guides you through setup step by step:
50
-
51
- 1. **Git Bash** — Required for the agent's shell. If missing, the agent installs it for you via `winget` (admin approval needed).
52
- 2. **Python 3.12** — The agent checks for Python 3.12 and installs it if needed (with your permission, per-user, no admin required).
53
- 3. **Excel add-in** — Registered with Excel so it loads automatically on startup. The agent opens Excel for you after this step.
54
-
55
- After setup, just run `shortcut` — Excel opens automatically with the agent connected.
56
-
57
- ## Links
23
+ ## Prerequisites
58
24
 
59
- - [shortcut.ai](https://shortcut.ai)
25
+ - **Windows 10/11** with **Excel 2016+** (64-bit)
26
+ - **Node.js >= 20** — If missing: `winget install OpenJS.NodeJS.LTS`
@@ -11,7 +11,7 @@ import { sleep } from '../utils/sleep.js';
11
11
  // ============================================================================
12
12
  // Error Classification
13
13
  // ============================================================================
14
- const RETRYABLE_ERROR_PATTERN = /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay/i;
14
+ const RETRYABLE_ERROR_PATTERN = /overloaded|rate.?limit|too many requests|429|500|502|503|504|524|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay/i;
15
15
  export function isRetryableError(message, isContextOverflow) {
16
16
  if (message.stopReason !== 'error' || !message.errorMessage)
17
17
  return false;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Clone subagent — a full copy of the action agent that can spawn leaf subagents
3
+ * (general, document_reader) but cannot spawn other clones.
4
+ */
5
+ import { BASH, EDIT, EXCEL_EXEC, GREP, READ, TASK, WRITE } from '../../tool-names.js';
6
+ import { buildActionPrompt } from '../prompts/action.js';
7
+ const NAME = 'clone';
8
+ const DESCRIPTION = `\
9
+ A full clone of the main agent with tools (read, bash, edit, write, grep, excel_exec, task).
10
+ Can spawn general and document_reader subagents but cannot spawn other clones.
11
+ Use only for independent batches of tasks that are perfectly parallelizable.`;
12
+ const SYSTEM_PROMPT = buildActionPrompt();
13
+ export const cloneAgent = {
14
+ name: NAME,
15
+ description: DESCRIPTION,
16
+ systemPrompt: SYSTEM_PROMPT,
17
+ tools: [READ, BASH, EDIT, WRITE, GREP, EXCEL_EXEC, TASK],
18
+ model: 'shortcut/claude-opus-4-6',
19
+ thinkingLevel: 'low',
20
+ timeoutSeconds: 900
21
+ };
22
+ //# sourceMappingURL=clone.js.map
@@ -5,14 +5,13 @@
5
5
  * Tools: bash (programmatic extraction) + llm_analysis (multimodal LLM).
6
6
  */
7
7
  import { BASH, LLM_ANALYSIS } from '../../tool-names.js';
8
- export const documentReaderAgent = {
9
- name: 'document_reader',
10
- description: `\
8
+ const NAME = 'document_reader';
9
+ const DESCRIPTION = `\
11
10
  Extract structured data from PDFs, images, and documents with high accuracy.
12
11
  Tools: bash (Python packages for programmatic extraction) and llm_analysis (multimodal LLM for visual/complex content).
13
12
  Use for: PDF table extraction, financial statement parsing, image/chart analysis, document scanning, and data verification.
14
- Prefers programmatic extraction for large structured data (>500 data points), multimodal LLMs for visual content and verification.`,
15
- systemPrompt: `\
13
+ Prefers programmatic extraction for large structured data (>500 data points), multimodal LLMs for visual content and verification.`;
14
+ const SYSTEM_PROMPT = `\
16
15
  ======================
17
16
  DOCUMENT READER AGENT
18
17
  ======================
@@ -21,11 +20,11 @@ You must extract values exactly as they appear in the source: preserve original
21
20
 
22
21
  WORKFLOW:
23
22
  1. Scan the document structure first using llm_analysis to understand the layout, sections, and where key data lives.
24
- 2. For structured data (>500 data points): use programmatic extraction via bash (Python packages). This is fast, cheap, and deterministic.
23
+ 2. For structured data (>500 data points): ALWAYS use programmatic extraction via bash. This is fast, cheap, and deterministic.
25
24
  3. For everything else (images, charts, complex layouts, scanned docs): use llm_analysis with multimodal LLMs.
26
25
  4. Always sanity-check and verify extractions — use both methods and compare when accuracy is critical.
27
26
 
28
- PROGRAMMATIC EXTRACTION (bash + Python):
27
+ PROGRAMMATIC EXTRACTION (bash): IMPORTANT -- pip install any packages you currently do not have
29
28
  - camelot: Default for tables. Returns pre-split DataFrame columns. Use flavor='stream' for borderless tables.
30
29
  - pdftotext -layout: Cleanest column-aligned output (CLI tool).
31
30
  - PyMuPDF (fitz): Fastest Python-native option. Use sort=True for layout-aware extraction.
@@ -61,7 +60,11 @@ OUTPUT:
61
60
  - Large structured outputs (>500 entries): JSON format. Smaller/unstructured: markdown.
62
61
  - Do NOT create summary, README, or "simplified" files.
63
62
  - Do NOT consolidate data into a single file unless you can do it programmatically.
64
- - Once data is extracted, list file paths and STOP.`,
63
+ - Once data is extracted, list file paths and STOP.`;
64
+ export const documentReaderAgent = {
65
+ name: NAME,
66
+ description: DESCRIPTION,
67
+ systemPrompt: SYSTEM_PROMPT,
65
68
  tools: [BASH, LLM_ANALYSIS],
66
69
  model: 'shortcut/claude-opus-4-6',
67
70
  thinkingLevel: 'low',
@@ -1,24 +1,47 @@
1
1
  /**
2
2
  * General-purpose subagent — research, computation, and Excel tasks.
3
3
  */
4
- import { BASH, EXCEL_EXEC } from '../../tool-names.js';
5
- export const generalAgent = {
6
- name: 'general',
7
- description: `\
8
- General-purpose agent for research, computation, and Excel tasks.
9
- Tools: bash (includes curl/wget for web) and excel_exec (Excel COM API via Python).
10
- Use for: data analysis, web research, file manipulation, spreadsheet operations, multi-step computations.
11
- Each agent can handle approximately 50 tool calls per task. Launch multiple concurrently for large workloads.`,
12
- systemPrompt: `\
13
- You are a general-purpose agent. You handle research, computation, data processing, and Excel tasks.
14
- Use bash for computation, file processing, and fetching URLs (curl/wget).
15
- Use excel_exec to read/write Excel workbooks via the COM API.
4
+ import { BASH, EXCEL_EXEC, LLM_ANALYSIS } from '../../tool-names.js';
5
+ const NAME = 'general';
6
+ const DESCRIPTION = `\
7
+ General-purpose agent for research, computation, and web tasks. Uses include:
8
+ - parallelizable classification tasks. Each agent can handle approximately <50 natural language queries (LLM calls / tool uses) and <10 web search queries per task, and should be launched concurrently to optimize performance.
9
+ - when the task involves reading or classifying spreadsheet data, the agent has full excel_exec access to read the workbook directly — never transcribe cell values into the query.
10
+ Tools: bash (includes curl/wget for URLs), excel_exec (Excel COM API via Python), llm_analysis (offload classification batches).`;
11
+ const SYSTEM_PROMPT = `\
12
+ ======================
13
+ GENERAL AGENT
14
+ ======================
15
+
16
+ ## YOUR ROLE: GENERAL-PURPOSE AGENT
17
+
18
+ You handle research, computation, data processing, and web tasks.
19
+ You have access to bash, excel_exec, and llm_analysis.
20
+ Use bash for computation, file processing, and fetching specific URLs (curl/wget).
21
+ Use excel_exec to read/write Excel workbooks.
22
+ Use llm_analysis to parallelize classification/extraction work.
23
+
24
+ NOTE:
25
+ - You are often invoked for the purpose of performing difficult classification tasks that evade easy programmatic parsing
26
+ - Always read all relevant information, extractions, web searches, per entry to ensure that categorizations are done correctly
27
+ - To do this, alternate between reading relevant information and writing categorizations to file
28
+ - NEVER try to parse or categorize programmatically unless the task is simple enough to be done this way with perfect fidelity
29
+
30
+ HOW TO CATEGORIZE:
31
+ - You can only handle on the order of 5 difficult classifications per batch, 10-20 medium classifications, and 50 easy classifications.
32
+ - For llm_analysis tool calls, this may require dividing files into bite-sized pieces
33
+ - When you have a lot of categorizations to perform (>30), parallelize by offloading work via llm_analysis tool calls instead of doing it yourself
16
34
 
17
35
  OUTPUT:
18
- - Be concise and precise. Report findings directly.
19
- - If you create files, list the file paths at the end.
20
- - Do NOT create summary or README files unless asked.`,
21
- tools: [BASH, EXCEL_EXEC],
36
+ - Your final outputs should be files containing clear and very concise analyses.
37
+ - Do NOT create "simplified", "visual summary", "quick reference", or "README" files. Never try to "improve" or "simplify" or "summarize"
38
+ - You NEVER need to consolidate or organize data into a single file unless you can do it programmatically. Re-listing data takes time, output tokens, and is extremely error-prone.
39
+ - Once analysis is performed, LIST the file paths and STOP.`;
40
+ export const generalAgent = {
41
+ name: NAME,
42
+ description: DESCRIPTION,
43
+ systemPrompt: SYSTEM_PROMPT,
44
+ tools: [BASH, EXCEL_EXEC, LLM_ANALYSIS],
22
45
  model: 'shortcut/claude-opus-4-6',
23
46
  thinkingLevel: 'low',
24
47
  timeoutSeconds: 900
@@ -11,10 +11,11 @@
11
11
  */
12
12
  export { actionAgent } from './action.js';
13
13
  export { installationAgent } from './installation.js';
14
+ import { cloneAgent } from './clone.js';
14
15
  import { documentReaderAgent } from './document-reader.js';
15
16
  import { generalAgent } from './general.js';
16
17
  /** All subagent definitions (used internally by accessor functions below) */
17
- const SUBAGENTS = [documentReaderAgent, generalAgent];
18
+ const SUBAGENTS = [cloneAgent, documentReaderAgent, generalAgent];
18
19
  /** Get a subagent config by name */
19
20
  export function getSubagent(name) {
20
21
  return SUBAGENTS.find((a) => a.name === name);
@@ -34,8 +35,9 @@ Include file paths, sheet names, cell ranges, and specific instructions in the q
34
35
  When multiple tasks are needed, launch them concurrently. NEVER call them sequentially — each agent takes time to complete.
35
36
 
36
37
  Choose the subagent_type carefully based on the task:
38
+ - clone: agent clone with bash, edit, excel_exec, and task — use for complex tasks needing editing and subagent delegation
37
39
  - document_reader: PDFs, images, document extraction (has llm_analysis for multimodal + bash for programmatic extraction)
38
- - general: research, computation, Excel tasks (has excel_exec + bash)`;
40
+ - general: research, computation, Excel tasks (has excel_exec + bash + llm_analysis)`;
39
41
  const lines = [preamble];
40
42
  for (const agent of SUBAGENTS) {
41
43
  lines.push('');
@@ -1,18 +1,29 @@
1
1
  /**
2
- * Shortcut production constants.
2
+ * Shortcut infrastructure constants.
3
3
  *
4
- * These are the defaults when no .env file is present (i.e. installed product).
5
- * For local development, create a .env file to override — see .env.development.
6
- *
7
- * Every env var listed here can be overridden via process.env.
4
+ * Toggle ACTIVE_ENV between 'prod' and 'staging' to switch environments.
5
+ * Every env var can still be overridden via process.env.
8
6
  */
7
+ const ACTIVE_ENV = 'staging';
8
+ const ENV = {
9
+ prod: {
10
+ llmProxyUrl: 'https://agent.shortcut.ai',
11
+ authUrl: 'https://auth.shortcut.ai',
12
+ apiUrl: 'https://api.shortcut.ai'
13
+ },
14
+ staging: {
15
+ llmProxyUrl: 'https://staging-agent.shortcut.ai',
16
+ authUrl: 'https://auth-staging.shortcut.ai',
17
+ apiUrl: 'https://api-staging.shortcut.ai'
18
+ }
19
+ };
9
20
  // -- Shortcut infrastructure URLs ------------------------------------------
10
21
  /** LLM proxy — routes LLM calls through the Python backend (auth, credits, model allowlist). */
11
- export const SHORTCUT_LLM_PROXY_URL = process.env.SHORTCUT_LLM_PROXY_URL ?? 'https://agent.shortcut.ai';
22
+ export const SHORTCUT_LLM_PROXY_URL = process.env.SHORTCUT_LLM_PROXY_URL ?? ENV[ACTIVE_ENV].llmProxyUrl;
12
23
  /** Auth service — device code OAuth flow, token refresh. */
13
- export const SHORTCUT_AUTH_URL = process.env.SHORTCUT_AUTH_URL ?? 'https://auth.shortcut.ai';
24
+ export const SHORTCUT_AUTH_URL = process.env.SHORTCUT_AUTH_URL ?? ENV[ACTIVE_ENV].authUrl;
14
25
  /** API service — credit balance, billing. */
15
- export const SHORTCUT_API_URL = process.env.SHORTCUT_API_URL ?? 'https://api.shortcut.ai';
26
+ export const SHORTCUT_API_URL = process.env.SHORTCUT_API_URL ?? ENV[ACTIVE_ENV].apiUrl;
16
27
  // -- Dev mode --------------------------------------------------------------
17
28
  // DEV_MODE lives in config.ts (layer-0) so modes/ can import it without boundary violations.
18
29
  // -- Excel XLL -------------------------------------------------------------
@@ -22,6 +22,7 @@ Multiple agents may operate on the same Excel instance concurrently. Follow thes
22
22
  - For new workbooks, we must skip the splash screen. To do this, create a workbook in the temp dir via openpyxl or use an existing one. Then open it via start
23
23
  - Do NOT use /e or other command-line flags — Excel interprets them as file paths
24
24
  - If the user references a file that isn't open, explore the filesystem to locate it, then open it
25
+ - For files that you are only reading from (attached workbooks, reference files): open them headlessly. This avoids cluttering the user's Excel with files they don't need to see or edit
25
26
 
26
27
  ### Workbook References — NEVER use ActiveWorkbook
27
28
  - At the start of a task, list workbooks to find the right name: [wb.Name for wb in app.Workbooks]
@@ -25,8 +25,11 @@ const TOOL_NAME = TASK;
25
25
  const TOOL_LABEL = 'Task';
26
26
  const TOOL_DESCRIPTION = buildTaskToolDescription();
27
27
  const SUBAGENT_MODELS = ['shortcut/claude-opus-4-6', 'shortcut/gpt-5.4-2026-03-05'];
28
- function buildSchema() {
29
- const names = getSubagentNames();
28
+ function buildSchema(excludeSubagents) {
29
+ const allNames = getSubagentNames();
30
+ const names = excludeSubagents
31
+ ? allNames.filter((n) => !excludeSubagents.includes(n))
32
+ : allNames;
30
33
  return Type.Object({
31
34
  subagent_type: StringEnum(names, {
32
35
  description: 'The type of specialized agent to use for this task'
@@ -44,8 +47,8 @@ function buildSchema() {
44
47
  });
45
48
  }
46
49
  export function createTaskTool(options = {}) {
47
- const { cwd: cwdOverride, extraArgs = [] } = options;
48
- const schema = buildSchema();
50
+ const { cwd: cwdOverride, extraArgs = [], excludeSubagents } = options;
51
+ const schema = buildSchema(excludeSubagents);
49
52
  return {
50
53
  name: TOOL_NAME,
51
54
  label: TOOL_LABEL,
package/dist/main.js CHANGED
@@ -408,7 +408,11 @@ export async function main(args) {
408
408
  }
409
409
  extensionsResult.runtime.pendingProviderRegistrations = [];
410
410
  // Register Shortcut as a provider with custom stream through the Python backend
411
- registerShortcutProvider(modelRegistry, SHORTCUT_LLM_PROXY_URL);
411
+ // Print mode uses non-streaming invoke (like subagents) — no UI needs deltas
412
+ const isPrintMode = firstPass.print || firstPass.mode === 'text' || firstPass.mode === 'json';
413
+ registerShortcutProvider(modelRegistry, SHORTCUT_LLM_PROXY_URL, {
414
+ streaming: !isPrintMode
415
+ });
412
416
  // Keep Shortcut OAuth tokens fresh while the TUI is idle.
413
417
  // Without this, the access token (7-min TTL on staging) expires silently
414
418
  // and the next LLM request fails with 401.
@@ -330,8 +330,8 @@ export class InteractiveMode {
330
330
  const textLines = [
331
331
  logoText,
332
332
  rawKeyHint('/', 'for commands'),
333
- rawKeyHint('/clear', 'new chat'),
334
- rawKeyHint('/resume', 'resume previous session'),
333
+ rawKeyHint('/clear', 'for new chat'),
334
+ rawKeyHint('/resume', 'to resume prior sessions'),
335
335
  rawKeyHint(`${appKey(kb, 'pasteImage')} or drop files`, 'to attach files'),
336
336
  hint('interrupt', 'to interrupt agent'),
337
337
  rawKeyHint(`${appKey(kb, 'clear')} twice`, 'to exit out of shortcutXL')
@@ -2,9 +2,28 @@
2
2
  * Print mode (single-shot): Send prompts, output result, exit.
3
3
  *
4
4
  * Used for:
5
- * - `pi -p "prompt"` - text output
5
+ * - `pi -p "prompt"` - text output with tool call summaries and streamed text
6
6
  * - `pi --mode json "prompt"` - JSON event stream
7
7
  */
8
+ import chalk from 'chalk';
9
+ /**
10
+ * Format a tool call for display.
11
+ * If the tool has a description arg, show "name: description".
12
+ * Otherwise show each arg as indented colored key: value lines.
13
+ */
14
+ function formatToolCall(toolName, args) {
15
+ if (!args || typeof args !== 'object' || Object.keys(args).length === 0) {
16
+ return chalk.cyan(toolName);
17
+ }
18
+ if (args.description) {
19
+ return `${chalk.cyan(toolName)}: ${args.description}`;
20
+ }
21
+ const lines = Object.entries(args).map(([key, value]) => {
22
+ const str = typeof value === 'string' ? value : JSON.stringify(value);
23
+ return ` ${chalk.dim(key)}: ${str}`;
24
+ });
25
+ return `${chalk.cyan(toolName)}\n${lines.join('\n')}`;
26
+ }
8
27
  /**
9
28
  * Run in print (single-shot) mode.
10
29
  * Sends prompts to the agent and outputs the result.
@@ -53,11 +72,57 @@ export async function runPrintMode(session, options) {
53
72
  console.error(`Extension error (${err.extensionPath}): ${err.error}`);
54
73
  }
55
74
  });
75
+ // Track whether we're mid-text-block (for newline management)
76
+ let inTextBlock = false;
56
77
  // Always subscribe to enable session persistence via _handleAgentEvent
57
78
  session.subscribe((event) => {
58
- // In JSON mode, output all events
59
79
  if (mode === 'json') {
60
80
  console.log(JSON.stringify(event));
81
+ return;
82
+ }
83
+ // Text mode: stream tool calls and text as they happen
84
+ if (mode === 'text') {
85
+ switch (event.type) {
86
+ case 'tool_execution_start': {
87
+ if (inTextBlock) {
88
+ process.stdout.write('\n');
89
+ inTextBlock = false;
90
+ }
91
+ const line = formatToolCall(event.toolName, event.args);
92
+ console.error(chalk.dim(' ▶ ') + line);
93
+ break;
94
+ }
95
+ case 'message_end': {
96
+ // Print text from completed assistant message
97
+ if (event.message.role === 'assistant') {
98
+ const msg = event.message;
99
+ for (const content of msg.content) {
100
+ if (content.type === 'text' && content.text) {
101
+ process.stdout.write(content.text);
102
+ inTextBlock = true;
103
+ }
104
+ }
105
+ }
106
+ break;
107
+ }
108
+ case 'agent_end': {
109
+ if (inTextBlock) {
110
+ process.stdout.write('\n');
111
+ inTextBlock = false;
112
+ }
113
+ // Check for errors
114
+ const msgs = event.messages;
115
+ const last = msgs[msgs.length - 1];
116
+ if (last?.role === 'assistant') {
117
+ const msg = last;
118
+ if (msg.stopReason === 'error' || msg.stopReason === 'aborted') {
119
+ console.error(msg.errorMessage || `Request ${msg.stopReason}`);
120
+ process.exit(1);
121
+ }
122
+ }
123
+ break;
124
+ }
125
+ }
61
126
  }
62
127
  });
63
128
  // Send initial message with attachments
@@ -68,25 +133,6 @@ export async function runPrintMode(session, options) {
68
133
  for (const message of messages) {
69
134
  await session.prompt(message);
70
135
  }
71
- // In text mode, output final response
72
- if (mode === 'text') {
73
- const state = session.state;
74
- const lastMessage = state.messages[state.messages.length - 1];
75
- if (lastMessage?.role === 'assistant') {
76
- const assistantMsg = lastMessage;
77
- // Check for error/aborted
78
- if (assistantMsg.stopReason === 'error' || assistantMsg.stopReason === 'aborted') {
79
- console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
80
- process.exit(1);
81
- }
82
- // Output text content
83
- for (const content of assistantMsg.content) {
84
- if (content.type === 'text') {
85
- console.log(content.text);
86
- }
87
- }
88
- }
89
- }
90
136
  // Ensure stdout is fully flushed before returning
91
137
  // This prevents race conditions where the process exits before all output is written
92
138
  await new Promise((resolve, reject) => {
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { parseArgs } from './cli/args.js';
14
- import { getAgentDir } from './config.js';
14
+ import { DEV_MODE, getAgentDir } from './config.js';
15
15
  import { AuthStorage } from './core/auth-storage.js';
16
16
  import { ModelRegistry } from './core/model-registry.js';
17
17
  import { resolveCliModel } from './core/model-resolver.js';
@@ -27,8 +27,9 @@ import { fetchWorkbookSummary, formatSummaryForLlm, parseWorkbookNames } from '.
27
27
  import { registerShortcutProvider } from './custom/providers/register-shortcut-provider.js';
28
28
  import { createExcelExecTool } from './custom/tools/excel-exec.js';
29
29
  import { createLlmAnalysisTool } from './custom/tools/llm-analysis.js';
30
+ import { createTaskTool } from './custom/tools/task/index.js';
30
31
  import { runPrintMode } from './modes/print-mode.js';
31
- import { EXCEL_EXEC, LLM_ANALYSIS } from './tool-names.js';
32
+ import { EXCEL_EXEC, LLM_ANALYSIS, TASK } from './tool-names.js';
32
33
  export async function runSubagent(args) {
33
34
  const parsed = parseArgs(args);
34
35
  // Read-only auth: reads auth.json without file locking so concurrent
@@ -82,7 +83,9 @@ export async function runSubagent(args) {
82
83
  noExtensions: true,
83
84
  noSkills: true,
84
85
  noPromptTemplates: true,
85
- noThemes: true
86
+ noThemes: true,
87
+ // AGENTS.md / CLAUDE.md context files are dev-only (not useful for end users)
88
+ agentsFilesOverride: DEV_MODE ? undefined : () => ({ agentsFiles: [] })
86
89
  });
87
90
  await resourceLoader.reload();
88
91
  // Build tools
@@ -92,7 +95,7 @@ export async function runSubagent(args) {
92
95
  }
93
96
  return parsed.tools?.map((name) => allTools[name]);
94
97
  })();
95
- // Build custom tools (no task tool — subagents don't spawn sub-subagents)
98
+ // Build custom tools
96
99
  const customTools = [];
97
100
  if (!parsed.noCustomTools) {
98
101
  const customToolSet = parsed.customTools ? new Set(parsed.customTools) : null;
@@ -102,6 +105,10 @@ export async function runSubagent(args) {
102
105
  if (!customToolSet || customToolSet.has(LLM_ANALYSIS)) {
103
106
  customTools.push(createLlmAnalysisTool(SHORTCUT_LLM_PROXY_URL));
104
107
  }
108
+ if (customToolSet?.has(TASK)) {
109
+ // Clone agents can spawn leaf subagents but not other clones
110
+ customTools.push(createTaskTool({ excludeSubagents: ['clone'] }));
111
+ }
105
112
  }
106
113
  const { session } = await createAgentSession({
107
114
  model,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shortcutxl",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/",