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 +10 -43
- package/dist/core/session-retry.js +1 -1
- package/dist/custom/agents/clone.js +22 -0
- package/dist/custom/agents/document-reader.js +11 -8
- package/dist/custom/agents/general.js +39 -16
- package/dist/custom/agents/index.js +4 -2
- package/dist/custom/constants.js +19 -8
- package/dist/custom/prompts/api.js +1 -0
- package/dist/custom/tools/task/task.js +7 -4
- package/dist/main.js +5 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/print-mode.js +67 -21
- package/dist/subagent-entry.js +11 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,59 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ShortcutXL
|
|
2
2
|
|
|
3
|
-
|
|
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:**
|
|
10
|
+
> **Important:** Install globally with `-g`. Do not use `npm install shortcutxl` without it.
|
|
11
11
|
|
|
12
12
|
## Capabilities
|
|
13
13
|
|
|
14
|
-
|
|
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** —
|
|
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
|
|
22
|
-
- **User-defined functions (UDFs)** —
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
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('');
|
package/dist/custom/constants.js
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shortcut
|
|
2
|
+
* Shortcut infrastructure constants.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
|
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')
|
package/dist/modes/print-mode.js
CHANGED
|
@@ -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) => {
|
package/dist/subagent-entry.js
CHANGED
|
@@ -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
|
|
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,
|