shortcutxl 0.3.48 → 0.3.50

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 (85) hide show
  1. package/CHANGELOG.md +23 -11
  2. package/agent-docs/docs/tui.md +24 -0
  3. package/agent-docs/examples/rpc-extension-ui.ts +1 -1
  4. package/dist/app/agent-session.d.ts +2 -3
  5. package/dist/app/agent-session.js +3 -7
  6. package/dist/app/index.d.ts +1 -1
  7. package/dist/app/index.js +1 -1
  8. package/dist/app/modes/action/agent.js +6 -1
  9. package/dist/app/prompts/com-api-reference.json +146 -146
  10. package/dist/app/resources/package-manager.js +2 -41
  11. package/dist/app/settings-manager.d.ts +1 -1
  12. package/dist/app/settings-manager.js +1 -1
  13. package/dist/app/skill-proposals/catalog.d.ts +5 -1
  14. package/dist/app/skill-proposals/catalog.js +7 -1
  15. package/dist/app/skill-proposals/review.d.ts +1 -1
  16. package/dist/app/skill-proposals/review.js +2 -1
  17. package/dist/app/subagents/clone/agent.js +2 -2
  18. package/dist/app/subagents/defaults.d.ts +1 -0
  19. package/dist/app/subagents/defaults.js +1 -0
  20. package/dist/app/subagents/document-reader/agent.js +2 -2
  21. package/dist/app/subagents/general/agent.js +8 -6
  22. package/dist/app/subagents/index.d.ts +1 -1
  23. package/dist/app/subagents/index.js +2 -2
  24. package/dist/app/subagents/simulation/agent.js +2 -2
  25. package/dist/app/subagents/verification/agent.js +2 -2
  26. package/dist/app/subagents/workbook-reader/agent.js +2 -2
  27. package/dist/app/sync/skills-download.d.ts +3 -12
  28. package/dist/app/sync/skills-download.js +32 -57
  29. package/dist/app/sync/skills-upload.d.ts +4 -4
  30. package/dist/app/sync/skills-upload.js +15 -40
  31. package/dist/app/tools/task/task.js +5 -4
  32. package/dist/cli/config-selector.js +1 -1
  33. package/dist/cli/session-picker.js +1 -1
  34. package/dist/cli.js +1059 -1058
  35. package/dist/config.d.ts +0 -2
  36. package/dist/config.js +0 -4
  37. package/dist/constants.d.ts +4 -0
  38. package/dist/constants.js +17 -0
  39. package/dist/embedded-agent/host-tools/task/agent-definition.d.ts +1 -0
  40. package/dist/embedded-agent/host-tools/task/agent-definition.js +1 -1
  41. package/dist/embedded-agent/host-tools/task/agents/document-reader.d.ts +1 -1
  42. package/dist/embedded-agent/host-tools/task/agents/document-reader.js +2 -2
  43. package/dist/embedded-agent/host-tools/task/agents/general.d.ts +1 -1
  44. package/dist/embedded-agent/host-tools/task/agents/general.js +2 -2
  45. package/dist/embedded-agent/host-tools/task/agents/verification.d.ts +1 -1
  46. package/dist/embedded-agent/host-tools/task/agents/verification.js +2 -2
  47. package/dist/embedded-agent/host-tools/task/agents/workbook-reader.d.ts +1 -1
  48. package/dist/embedded-agent/host-tools/task/agents/workbook-reader.js +2 -2
  49. package/dist/main.js +1 -3
  50. package/dist/rpc/rpc-mode.js +1 -1
  51. package/dist/shell/approvals/file-access-approval.js +2 -2
  52. package/dist/shell/components/interactive-shell-layout.d.ts +16 -0
  53. package/dist/shell/components/interactive-shell-layout.js +90 -0
  54. package/dist/shell/dev/interactive-render-trace.d.ts +3 -0
  55. package/dist/shell/dev/interactive-render-trace.js +39 -0
  56. package/dist/shell/interactive/interactive-actions.js +0 -1
  57. package/dist/shell/interactive/interactive-mode-options.d.ts +1 -5
  58. package/dist/shell/interactive/interactive-mode.d.ts +7 -0
  59. package/dist/shell/interactive/interactive-mode.js +88 -17
  60. package/dist/shell/interactive/proposals-workflow.js +33 -13
  61. package/dist/shell/interactive/skills-workflow.d.ts +1 -5
  62. package/dist/shell/interactive/skills-workflow.js +25 -24
  63. package/dist/shell/interactive/super-fight-ui.d.ts +3 -2
  64. package/dist/shell/interactive/super-fight-ui.js +7 -6
  65. package/dist/shell/keybindings.d.ts +1 -1
  66. package/dist/shell/keybindings.js +0 -2
  67. package/dist/startup/interactive-commands.js +0 -2
  68. package/dist/tui/index.d.ts +1 -0
  69. package/dist/tui/index.js +1 -1
  70. package/dist/tui/keys.d.ts +14 -0
  71. package/dist/tui/keys.js +78 -0
  72. package/dist/tui/render-trace.d.ts +32 -0
  73. package/dist/tui/render-trace.js +293 -0
  74. package/dist/tui/terminal.d.ts +11 -0
  75. package/dist/tui/terminal.js +43 -15
  76. package/dist/tui/tui.d.ts +34 -0
  77. package/dist/tui/tui.js +359 -99
  78. package/package.json +291 -290
  79. package/skills/advanced-mog-api/api-reference.json +9291 -9291
  80. package/user-docs/dist/index.html +1 -1
  81. package/user-docs/dist/shortcutxl-docs.pdf +0 -0
  82. package/dist/app/sync/skills-sync-state.d.ts +0 -14
  83. package/dist/app/sync/skills-sync-state.js +0 -46
  84. package/dist/startup/skills-sync.d.ts +0 -10
  85. package/dist/startup/skills-sync.js +0 -41
package/CHANGELOG.md CHANGED
@@ -1,14 +1,26 @@
1
- # Changelog
2
-
3
- ## [0.3.48]
4
-
5
- - **Background subagents** - Subagents can now work in the background, be steered during conversations, be revived for additional tasks once they are done, and have their statuses surfaced in the UI.
6
- - **Skill proposals** - ShortcutXL now automatically browses through past sessions and suggests new skills and skill modifications.
7
-
8
- ## [0.3.47]
9
-
10
- - **GPT-5.5** — Upgraded the OpenAI reasoning model from GPT-5.4 to GPT-5.5 (400K context window, stronger reasoning).
11
- - **Out-of-credits message** When your credit balance runs out, ShortcutXL now shows a clear message instead of silently failing.
1
+ # Changelog
2
+
3
+ ## [0.3.50]
4
+
5
+ - **MCP-enabled task agents** - General task agents can now use connected MCP servers, giving delegated work access to the same external tools and resources as the main CLI.
6
+ - **Safer skill uploads** - Uploading skills no longer treats missing local skills as cloud deletions, so remote-only skills stay untouched unless explicitly replaced.
7
+ - **Misc bug fixes** - Shift+A for agent panel, wording, clarity.
8
+
9
+ ## [0.3.49]
10
+
11
+ - **Transcript scrolling & rendering** - Long conversations now scroll more cleanly with mouse wheel, PageUp/PageDown, and Ctrl+Home/Ctrl+End support while the editor and footer stay pinned.
12
+ - **Longer-running task agents** - Background task agents now get a 30-minute default timeout, reducing early timeouts on bigger jobs.
13
+ - **Clearer path-based skills** - Skills loaded from custom folders are grouped by their real root paths so the agent can find and use them more reliably.
14
+
15
+ ## [0.3.48]
16
+
17
+ - **Background subagents** - Subagents can now work in the background, be steered during conversations, be revived for additional tasks once they are done, and have their statuses surfaced in the UI.
18
+ - **Skill proposals** - ShortcutXL now automatically browses through past sessions and suggests new skills and skill modifications.
19
+
20
+ ## [0.3.47]
21
+
22
+ - **GPT-5.5** — Upgraded the OpenAI reasoning model from GPT-5.4 to GPT-5.5 (400K context window, stronger reasoning).
23
+ - **Out-of-credits message** — When your credit balance runs out, ShortcutXL now shows a clear message instead of silently failing.
12
24
 
13
25
  ## [0.3.46]
14
26
 
@@ -28,6 +28,8 @@ interface Component {
28
28
 
29
29
  The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
30
30
 
31
+ Shortcut's interactive shell configures `ProcessTerminal` to use the terminal alternate screen buffer. The main transcript is an app-owned scroll viewport, not native terminal scrollback. In that shell, PageUp/PageDown, Ctrl+Home/Ctrl+End, and mouse wheel input scroll the transcript while the editor/footer remain pinned. Fullscreen custom views should clear/redraw inside the existing alternate buffer instead of entering or leaving alternate-buffer mode themselves.
32
+
31
33
  ## Focusable Interface (IME Support)
32
34
 
33
35
  Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
@@ -445,12 +447,34 @@ interface MyTheme {
445
447
 
446
448
  ## Debug logging
447
449
 
450
+ `ProcessTerminal` only enters the alternate screen buffer (`CSI ? 1049 h`) when `useAlternateBuffer: true` is passed. Shortcut's interactive shell enables that mode; full-screen subviews should clear and redraw inside the existing TUI buffer instead of entering or leaving alternate buffer themselves.
451
+
452
+ Set `SHORTCUT_TUI_RENDER_TRACE=1` to capture a structured JSONL trace of TUI rendering decisions, input events, overlay/focus state, cursor movement, changed line hashes/previews, and ANSI write summaries. By default this writes to `~/.shortcut/agent/tui-render-traces/<timestamp>-<pid>.jsonl`. Set it to a file path to choose the destination. Add `SHORTCUT_TUI_RENDER_TRACE_FULL=1` to include full rendered lines and full write buffers; leave it off for smaller logs.
453
+
454
+ ```bash
455
+ SHORTCUT_TUI_RENDER_TRACE=1 shortcut
456
+ SHORTCUT_TUI_RENDER_TRACE=C:\temp\shortcut-tui.jsonl SHORTCUT_TUI_RENDER_TRACE_FULL=1 shortcut
457
+ ```
458
+
448
459
  Set `SHORTCUT_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
449
460
 
450
461
  ```bash
451
462
  SHORTCUT_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts
452
463
  ```
453
464
 
465
+ Summarize a captured trace with:
466
+
467
+ ```bash
468
+ npm run analyze:tui-trace -- C:\temp\shortcut-tui.jsonl
469
+ npm run analyze:tui-trace -- C:\temp\shortcut-tui.jsonl --json
470
+ ```
471
+
472
+ For deterministic repro coverage, run the PTY render matrix:
473
+
474
+ ```bash
475
+ npm run test:pty -- tests/interactive/tui-render-matrix/tui-render-matrix.pty.test.ts
476
+ ```
477
+
454
478
  ## Performance
455
479
 
456
480
  Cache rendered output when possible:
@@ -274,7 +274,7 @@ async function main() {
274
274
 
275
275
  // -- TUI setup --
276
276
 
277
- const terminal = new ProcessTerminal();
277
+ const terminal = new ProcessTerminal({ useAlternateBuffer: true });
278
278
  const tui = new TUI(terminal);
279
279
 
280
280
  const outputLog = new OutputLog();
@@ -137,7 +137,7 @@ export interface PromptOptions {
137
137
  expandPromptTemplates?: boolean;
138
138
  /** Image attachments */
139
139
  images?: ImageContent[];
140
- /** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). Required if streaming. */
140
+ /** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). Defaults to "steer". */
141
141
  streamingBehavior?: 'steer' | 'followUp';
142
142
  /** Source of input for extension input event handlers. Defaults to "interactive". */
143
143
  source?: InputSource;
@@ -322,9 +322,8 @@ export declare class AgentSession {
322
322
  /**
323
323
  * Send a prompt to the agent.
324
324
  * - Expands file-based prompt templates by default
325
- * - During streaming, queues via steer() or followUp() based on streamingBehavior option
325
+ * - During streaming, queues via steer() by default, or followUp() when requested
326
326
  * - Validates model and API key before sending (when not streaming)
327
- * @throws Error if streaming and no streamingBehavior specified
328
327
  * @throws Error if no model selected or no API key available (when not streaming)
329
328
  */
330
329
  prompt(text: string, options?: PromptOptions): Promise<void>;
@@ -734,9 +734,8 @@ export class AgentSession {
734
734
  /**
735
735
  * Send a prompt to the agent.
736
736
  * - Expands file-based prompt templates by default
737
- * - During streaming, queues via steer() or followUp() based on streamingBehavior option
737
+ * - During streaming, queues via steer() by default, or followUp() when requested
738
738
  * - Validates model and API key before sending (when not streaming)
739
- * @throws Error if streaming and no streamingBehavior specified
740
739
  * @throws Error if no model selected or no API key available (when not streaming)
741
740
  */
742
741
  async prompt(text, options) {
@@ -756,12 +755,9 @@ export class AgentSession {
756
755
  currentText = parsedBudget.task;
757
756
  }
758
757
  const expandedText = currentText;
759
- // If streaming, queue via steer() or followUp() based on option
758
+ // If streaming, queue via steer() by default; follow-up is opt-in.
760
759
  if (this.isStreaming) {
761
- if (!options?.streamingBehavior) {
762
- throw new Error("AgentController is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
763
- }
764
- if (options.streamingBehavior === 'followUp') {
760
+ if (options?.streamingBehavior === 'followUp') {
765
761
  await this._queueFollowUp(expandedText, currentImages);
766
762
  }
767
763
  else {
@@ -46,7 +46,7 @@ export { default as skillProposalsExtension } from './skill-proposals/extension.
46
46
  export { getSkillProposalRoots, type ProposalRoots } from './skill-proposals/proposal-fs.js';
47
47
  export { readSkillProposalReviewSnapshot, type SkillProposalReviewSnapshot } from './skill-proposals/review.js';
48
48
  export type { StartupInfoEntry } from './startup-info.js';
49
- export { downloadSkillsWithApproval, syncSkillsConservatively } from './sync/skills-download.js';
49
+ export { downloadSkillsWithApproval } from './sync/skills-download.js';
50
50
  export type { SkillDownloadApprovalHandler, SkillDownloadApprovalRequest, SkillsDownloadResult } from './sync/skills-download.js';
51
51
  export { uploadSkills } from './sync/skills-upload.js';
52
52
  export type { SkillUploadApprovalRequest, SkillsUploadResult } from './sync/skills-upload.js';
package/dist/app/index.js CHANGED
@@ -36,7 +36,7 @@ export { countSkillProposals, listSkillProposals } from './skill-proposals/catal
36
36
  export { default as skillProposalsExtension } from './skill-proposals/extension.js';
37
37
  export { getSkillProposalRoots } from './skill-proposals/proposal-fs.js';
38
38
  export { readSkillProposalReviewSnapshot } from './skill-proposals/review.js';
39
- export { downloadSkillsWithApproval, syncSkillsConservatively } from './sync/skills-download.js';
39
+ export { downloadSkillsWithApproval } from './sync/skills-download.js';
40
40
  export { uploadSkills } from './sync/skills-upload.js';
41
41
  export * from './tools/index.js';
42
42
  export { buildWorkbookPromptContext, fetchWorkbookConnectionState, formatWorkbookScopePromptContext } from './workbook-context/workbook-summary.js';
@@ -5,12 +5,17 @@
5
5
  * for making changes to the spreadsheet. Can switch to plan or installation mode.
6
6
  */
7
7
  import { MODE } from '../../../mode-names.js';
8
- import { ACTION_SWITCH_MODE, BASH, EXECUTE_CODE, EXECUTE_TOOL, GET_TOOL_INFO, MCP_TOOL_NAMES, SEND_MESSAGE, TASK, TODO_LIST, WRITE } from '../../../tool-names.js';
8
+ import { ACTION_SWITCH_MODE, BASH, EDIT, EXECUTE_CODE, EXECUTE_TOOL, FIND, GET_TOOL_INFO, GREP, LS, MCP_TOOL_NAMES, READ, SEND_MESSAGE, TASK, TODO_LIST, WRITE } from '../../../tool-names.js';
9
9
  import { buildActionPrompt } from './prompt.js';
10
10
  const DESCRIPTION = 'Execute changes to the spreadsheet.';
11
11
  const TOOLS = [
12
+ READ,
12
13
  BASH,
14
+ EDIT,
13
15
  WRITE,
16
+ GREP,
17
+ FIND,
18
+ LS,
14
19
  EXECUTE_CODE,
15
20
  TASK,
16
21
  SEND_MESSAGE,
@@ -1,146 +1,146 @@
1
- {
2
- "interfaces": {
3
- "Workbook": {
4
- "docstring": "Workbook wrapper.\nUsage:\n wb = Workbook(app.Workbooks(\"MyFile.xlsx\"))\n\nRaw COM access:\n wb._wb \u2014 the underlying COM Workbook object for direct platform API calls.\n Example: wb._wb.Worksheets(\"Sheet1\").Visible = False",
5
- "functions": {
6
- "getSheetNames": {
7
- "signature": "def getSheetNames() -> list[str]",
8
- "docstring": "Get the names of all sheets in the workbook.",
9
- "tags": ["action", "ask"]
10
- },
11
- "getWorkbookSummary": {
12
- "signature": "def getWorkbookSummary() -> str",
13
- "docstring": "Get sheet names and used ranges for this workbook.\nReturns lines like:\n Sheets and their used ranges:\n Sheet1: A1:D10 (active)\n Inputs: (empty)",
14
- "tags": ["action", "ask"]
15
- },
16
- "getSheet": {
17
- "signature": "def getSheet(name: str) -> 'Worksheet'",
18
- "docstring": "Get a sheet by name, returned as a wrapped Worksheet.",
19
- "tags": ["action", "ask"]
20
- },
21
- "addSheet": {
22
- "signature": "def addSheet(name: str, index: int | None = None) -> 'Worksheet'",
23
- "docstring": "Add a new sheet at 0-based index. index=0 inserts before the first sheet.\nOmit index for Excel's default position.\nExample: wb.addSheet(\"NewSheet\", 0) # insert as first sheet",
24
- "tags": ["action"]
25
- },
26
- "moveSheet": {
27
- "signature": "def moveSheet(name: str, index: int) -> None",
28
- "docstring": "Move a sheet to 0-based index. index=0 moves to first, index=Count-1 moves to last.\nExample: wb.moveSheet(\"Data\", 0) # move to first position",
29
- "tags": ["action"]
30
- },
31
- "calculate": {
32
- "signature": "def calculate() -> None",
33
- "docstring": "Recalculate the entire workbook.\nDependent formulas don't update until after code block completes \u2014 read in a follow-up block.",
34
- "tags": ["action"]
35
- },
36
- "errorCheck": {
37
- "signature": "def errorCheck(ranges: list[str] | None = None) -> str",
38
- "docstring": "Scan for errors (#REF!, #DIV/0!, #NAME?, #VALUE!, #N/A, #NULL!, #NUM!). Ranges MUST include a sheet name (\"Sheet1\" or \"Sheet1!A1:D50\") \u2014 bare ranges like \"A1:D50\" will fail.\n Args:\n ranges: Optional sheet names (\"Sheet1\") or ranges (\"Sheet1!A1:D50\"). Omit to check all.\n Returns:\n \"No issues found.\" or \"Errors (N):\nSheet1!A1: #REF!\n...\"",
39
- "tags": ["action", "ask"]
40
- },
41
- "copyPasteRange": {
42
- "signature": "def copyPasteRange(from_range: str, to_address: str, paste_type: str = 'all', ) -> None",
43
- "docstring": "Copy range within same workbook via range.Copy + PasteSpecial.\nAddresses must include sheet name. Do NOT use Sheet.Copy (broken via pywin32).\nExample: wb.copyPasteRange(\"Sheet1!A1:D10\", \"Sheet2!A1\", paste_type=\"values\")",
44
- "tags": ["action"]
45
- },
46
- "getNamedRangeInfo": {
47
- "signature": "def getNamedRangeInfo(max_count: int = 50) -> str",
48
- "docstring": "Get named ranges. Returns formatted string with name, reference, scope, and comment.\nExample output: \"Named Ranges (2):\\n Name: TaxRate | Ref: =Settings!$B$2 | Scope: workbook | Comment: ...\"\nReturns \"No named ranges found.\" if none exist.",
49
- "tags": ["action", "ask"]
50
- }
51
- }
52
- },
53
- "Worksheet": {
54
- "docstring": "Worksheet wrapper.\nUsage:\n sheet = wb.getSheet(\"Sheet1\")\n\nRaw COM access:\n sheet._ws \u2014 the underlying COM Worksheet object for direct platform API calls.\n Use for borders, text orientation, cell protection, and other properties not covered by the wrapper.\n Example: sheet._ws.Range(\"A1\").Borders(9).LineStyle = 1 # xlEdgeBottom, xlContinuous",
55
- "functions": {
56
- "getSheetSummary": {
57
- "signature": "def getSheetSummary() -> str",
58
- "docstring": "Overview: name, used range, tables (with style/banded/header/total props),\npivots (with range), conditional formats (type/range/priority/stopIfTrue),\nautofilter (with filtering status), charts (with type).",
59
- "tags": ["action", "ask"]
60
- },
61
- "getUsedRange": {
62
- "signature": "def getUsedRange() -> str",
63
- "docstring": "Get the used range address like \"A1:F20\", or \"\" if empty.",
64
- "tags": ["action", "ask"]
65
- },
66
- "getCell": {
67
- "signature": "def getCell(address: str, include_style: bool = True) -> str",
68
- "docstring": "Formatted cell string: \"value(=FORMULA) (style_json)\". Formula always included.\nZero with custom format appends [0]: \"$ - [0]\".\nExample:\n print(sheet.getCell(\"A1\"))\n print(sheet.getCell(\"A1\", include_style=False))",
69
- "tags": ["action", "ask"]
70
- },
71
- "getCellRange": {
72
- "signature": "def getCellRange(range_addr: str, include_style: bool = True) -> str",
73
- "docstring": "Markdown-like range: \"A1:Name | B1:Value\\nA2:Alice | B2:100\". Uses display text. Max 3000 cells; read larger areas in smaller chunks.\nAppends \"--- Style patterns ---\" section when include_style=True.",
74
- "tags": ["action", "ask"]
75
- },
76
- "getRawCellData": {
77
- "signature": "def getRawCellData(address: str, formula: bool = False) -> Any",
78
- "docstring": "Get raw data value from a cell, preserving data type. Set formula=True to get the formula string.",
79
- "tags": ["action", "ask"]
80
- },
81
- "getRawRangeData": {
82
- "signature": "def getRawRangeData(range_addr: str, formula: bool = False) -> list[list[Any]]",
83
- "docstring": "Get raw data for a range as 2D list. Always returns 2D list even for single row/column.",
84
- "tags": ["action", "ask"]
85
- },
86
- "regexSearch": {
87
- "signature": "def regexSearch(patterns: list[str], match_case: bool = False) -> list[dict]",
88
- "docstring": "Regex search across used range using display text. Returns [{\"address\": \"A1\", \"value\": \"display text\"}, ...].\nExample: sheet.regexSearch([\"revenue\", \"cost\"])",
89
- "tags": ["action", "ask"]
90
- },
91
- "setCell": {
92
- "signature": "def setCell(address: str, value: Any, number_format: str | None = None, note: str | None = None, ) -> None",
93
- "docstring": "Write value or formula. \"=\" prefix = formula. None or \"\" clears the cell.\nExamples:\n sheet.setCell(\"A1\", 8000)\n sheet.setCell(\"C1\", 0.15, number_format=\"0%\", note=\"Tax rate\")\n sheet.setCell(\"B1\", \"=SUM(A1:A10)\")",
94
- "tags": ["action"]
95
- },
96
- "setCellRange": {
97
- "signature": "def setCellRange(range_addr: str, values: list[list[Any]], number_format: str | None = None, ) -> None",
98
- "docstring": "Bulk write \u2014 ALWAYS use for >100 cells. \"=\" prefix = formula.\nExamples:\n sheet.setCellRange(\"A1:B2\", [[1, 2], [3, 4]])\n sheet.setCellRange(\"A1:B2\", [[\"=SUM(C1)\", \"=SUM(D1)\"], [5, 6]], number_format=\"$#,##0\")",
99
- "tags": ["action"]
100
- },
101
- "autoFill": {
102
- "signature": "def autoFill(source_range: str, target_range: str, fill_mode: str = 'auto', ) -> None",
103
- "docstring": "Drag-fill. Source must be contained within target. Default \"auto\" increments hardcoded numeric literals; use fill_mode=\"constant\" to copy literal values, then read back destination cells and check for empty/NaN values.\nUse fill_mode=\"constant\" when copying a single value without incrementing.\nWARNING: Single numeric value with \"auto\" creates incrementing sequence (0,1,2,3...).\nExample: sheet.autoFill(\"A1:A2\", \"A1:A10\")",
104
- "tags": ["action"]
105
- },
106
- "addPicture": {
107
- "signature": "def addPicture(name: str, base64_data: str, anchor_cell: str) -> None",
108
- "docstring": "Add a picture from base64-encoded PNG or JPEG data. No data URI prefix.\nExample: sheet.addPicture(\"chart1\", base64_string, \"F2\")",
109
- "tags": ["action"]
110
- },
111
- "setIBTextColors": {
112
- "signature": "def setIBTextColors(range_addr: str, ignored_constants: list[int] | None = None, colors: dict | None = None, ) -> None",
113
- "docstring": "IB text colors for numeric inputs only. Text strings and booleans are left untouched.\n- Blue/16711680: hard-coded numeric constants (int/float)\n- Black/0: formulas (same-sheet references)\n- Green/32768: formulas with cross-sheet references (contains '!')\nColors are BGR integers (native COM format).\nExample: sheet.setIBTextColors(\"A1:D10\", ignored_constants=[0])",
114
- "tags": ["action"]
115
- }
116
- }
117
- }
118
- },
119
- "helpers": {
120
- "hex_to_bgr": {
121
- "signature": "def hex_to_bgr(hex_str: str) -> int",
122
- "docstring": "Convert \"#RRGGBB\" hex string to BGR integer for Excel COM.\nExample: ws.Range(\"A1\").Font.Color = hex_to_bgr(\"#FF0000\") # red",
123
- "tags": ["action", "ask"]
124
- },
125
- "index_to_address": {
126
- "signature": "def index_to_address(row: int, col: int) -> str",
127
- "docstring": "Convert 0-based (row, col) to Excel address: index_to_address(0, 0) \u2192 \"A1\".",
128
- "tags": ["action", "ask"]
129
- },
130
- "address_to_index": {
131
- "signature": "def address_to_index(address: str) -> tuple[int, int]",
132
- "docstring": "Parse \"A1\" into (row, col) 0-based tuple: address_to_index(\"B3\") \u2192 (2, 1).",
133
- "tags": ["action", "ask"]
134
- },
135
- "col_letter": {
136
- "signature": "def col_letter(index: int) -> str",
137
- "docstring": "Convert 0-based column index to letter: col_letter(26) \u2192 \"AA\".",
138
- "tags": ["action", "ask"]
139
- },
140
- "col_index": {
141
- "signature": "def col_index(letter: str) -> int",
142
- "docstring": "Convert column letter to 0-based index: col_index(\"AA\") \u2192 26.",
143
- "tags": ["action", "ask"]
144
- }
145
- }
146
- }
1
+ {
2
+ "interfaces": {
3
+ "Workbook": {
4
+ "docstring": "Workbook wrapper.\nUsage:\n wb = Workbook(app.Workbooks(\"MyFile.xlsx\"))\n\nRaw COM access:\n wb._wb \u2014 the underlying COM Workbook object for direct platform API calls.\n Example: wb._wb.Worksheets(\"Sheet1\").Visible = False",
5
+ "functions": {
6
+ "getSheetNames": {
7
+ "signature": "def getSheetNames() -> list[str]",
8
+ "docstring": "Get the names of all sheets in the workbook.",
9
+ "tags": ["action", "ask"]
10
+ },
11
+ "getWorkbookSummary": {
12
+ "signature": "def getWorkbookSummary() -> str",
13
+ "docstring": "Get sheet names and used ranges for this workbook.\nReturns lines like:\n Sheets and their used ranges:\n Sheet1: A1:D10 (active)\n Inputs: (empty)",
14
+ "tags": ["action", "ask"]
15
+ },
16
+ "getSheet": {
17
+ "signature": "def getSheet(name: str) -> 'Worksheet'",
18
+ "docstring": "Get a sheet by name, returned as a wrapped Worksheet.",
19
+ "tags": ["action", "ask"]
20
+ },
21
+ "addSheet": {
22
+ "signature": "def addSheet(name: str, index: int | None = None) -> 'Worksheet'",
23
+ "docstring": "Add a new sheet at 0-based index. index=0 inserts before the first sheet.\nOmit index for Excel's default position.\nExample: wb.addSheet(\"NewSheet\", 0) # insert as first sheet",
24
+ "tags": ["action"]
25
+ },
26
+ "moveSheet": {
27
+ "signature": "def moveSheet(name: str, index: int) -> None",
28
+ "docstring": "Move a sheet to 0-based index. index=0 moves to first, index=Count-1 moves to last.\nExample: wb.moveSheet(\"Data\", 0) # move to first position",
29
+ "tags": ["action"]
30
+ },
31
+ "calculate": {
32
+ "signature": "def calculate() -> None",
33
+ "docstring": "Recalculate the entire workbook.\nDependent formulas don't update until after code block completes \u2014 read in a follow-up block.",
34
+ "tags": ["action"]
35
+ },
36
+ "errorCheck": {
37
+ "signature": "def errorCheck(ranges: list[str] | None = None) -> str",
38
+ "docstring": "Scan for errors (#REF!, #DIV/0!, #NAME?, #VALUE!, #N/A, #NULL!, #NUM!). Ranges MUST include a sheet name (\"Sheet1\" or \"Sheet1!A1:D50\") \u2014 bare ranges like \"A1:D50\" will fail.\n Args:\n ranges: Optional sheet names (\"Sheet1\") or ranges (\"Sheet1!A1:D50\"). Omit to check all.\n Returns:\n \"No issues found.\" or \"Errors (N):\nSheet1!A1: #REF!\n...\"",
39
+ "tags": ["action", "ask"]
40
+ },
41
+ "copyPasteRange": {
42
+ "signature": "def copyPasteRange(from_range: str, to_address: str, paste_type: str = 'all', ) -> None",
43
+ "docstring": "Copy range within same workbook via range.Copy + PasteSpecial.\nAddresses must include sheet name. Do NOT use Sheet.Copy (broken via pywin32).\nExample: wb.copyPasteRange(\"Sheet1!A1:D10\", \"Sheet2!A1\", paste_type=\"values\")",
44
+ "tags": ["action"]
45
+ },
46
+ "getNamedRangeInfo": {
47
+ "signature": "def getNamedRangeInfo(max_count: int = 50) -> str",
48
+ "docstring": "Get named ranges. Returns formatted string with name, reference, scope, and comment.\nExample output: \"Named Ranges (2):\\n Name: TaxRate | Ref: =Settings!$B$2 | Scope: workbook | Comment: ...\"\nReturns \"No named ranges found.\" if none exist.",
49
+ "tags": ["action", "ask"]
50
+ }
51
+ }
52
+ },
53
+ "Worksheet": {
54
+ "docstring": "Worksheet wrapper.\nUsage:\n sheet = wb.getSheet(\"Sheet1\")\n\nRaw COM access:\n sheet._ws \u2014 the underlying COM Worksheet object for direct platform API calls.\n Use for borders, text orientation, cell protection, and other properties not covered by the wrapper.\n Example: sheet._ws.Range(\"A1\").Borders(9).LineStyle = 1 # xlEdgeBottom, xlContinuous",
55
+ "functions": {
56
+ "getSheetSummary": {
57
+ "signature": "def getSheetSummary() -> str",
58
+ "docstring": "Overview: name, used range, tables (with style/banded/header/total props),\npivots (with range), conditional formats (type/range/priority/stopIfTrue),\nautofilter (with filtering status), charts (with type).",
59
+ "tags": ["action", "ask"]
60
+ },
61
+ "getUsedRange": {
62
+ "signature": "def getUsedRange() -> str",
63
+ "docstring": "Get the used range address like \"A1:F20\", or \"\" if empty.",
64
+ "tags": ["action", "ask"]
65
+ },
66
+ "getCell": {
67
+ "signature": "def getCell(address: str, include_style: bool = True) -> str",
68
+ "docstring": "Formatted cell string: \"value(=FORMULA) (style_json)\". Formula always included.\nZero with custom format appends [0]: \"$ - [0]\".\nExample:\n print(sheet.getCell(\"A1\"))\n print(sheet.getCell(\"A1\", include_style=False))",
69
+ "tags": ["action", "ask"]
70
+ },
71
+ "getCellRange": {
72
+ "signature": "def getCellRange(range_addr: str, include_style: bool = True) -> str",
73
+ "docstring": "Markdown-like range: \"A1:Name | B1:Value\\nA2:Alice | B2:100\". Uses display text. Max 3000 cells; read larger areas in smaller chunks.\nAppends \"--- Style patterns ---\" section when include_style=True.",
74
+ "tags": ["action", "ask"]
75
+ },
76
+ "getRawCellData": {
77
+ "signature": "def getRawCellData(address: str, formula: bool = False) -> Any",
78
+ "docstring": "Get raw data value from a cell, preserving data type. Set formula=True to get the formula string.",
79
+ "tags": ["action", "ask"]
80
+ },
81
+ "getRawRangeData": {
82
+ "signature": "def getRawRangeData(range_addr: str, formula: bool = False) -> list[list[Any]]",
83
+ "docstring": "Get raw data for a range as 2D list. Always returns 2D list even for single row/column.",
84
+ "tags": ["action", "ask"]
85
+ },
86
+ "regexSearch": {
87
+ "signature": "def regexSearch(patterns: list[str], match_case: bool = False) -> list[dict]",
88
+ "docstring": "Regex search across used range using display text. Returns [{\"address\": \"A1\", \"value\": \"display text\"}, ...].\nExample: sheet.regexSearch([\"revenue\", \"cost\"])",
89
+ "tags": ["action", "ask"]
90
+ },
91
+ "setCell": {
92
+ "signature": "def setCell(address: str, value: Any, number_format: str | None = None, note: str | None = None, ) -> None",
93
+ "docstring": "Write value or formula. \"=\" prefix = formula. None or \"\" clears the cell.\nExamples:\n sheet.setCell(\"A1\", 8000)\n sheet.setCell(\"C1\", 0.15, number_format=\"0%\", note=\"Tax rate\")\n sheet.setCell(\"B1\", \"=SUM(A1:A10)\")",
94
+ "tags": ["action"]
95
+ },
96
+ "setCellRange": {
97
+ "signature": "def setCellRange(range_addr: str, values: list[list[Any]], number_format: str | None = None, ) -> None",
98
+ "docstring": "Bulk write \u2014 ALWAYS use for >100 cells. \"=\" prefix = formula.\nExamples:\n sheet.setCellRange(\"A1:B2\", [[1, 2], [3, 4]])\n sheet.setCellRange(\"A1:B2\", [[\"=SUM(C1)\", \"=SUM(D1)\"], [5, 6]], number_format=\"$#,##0\")",
99
+ "tags": ["action"]
100
+ },
101
+ "autoFill": {
102
+ "signature": "def autoFill(source_range: str, target_range: str, fill_mode: str = 'auto', ) -> None",
103
+ "docstring": "Drag-fill. Source must be contained within target. Default \"auto\" increments hardcoded numeric literals; use fill_mode=\"constant\" to copy literal values, then read back destination cells and check for empty/NaN values.\nUse fill_mode=\"constant\" when copying a single value without incrementing.\nWARNING: Single numeric value with \"auto\" creates incrementing sequence (0,1,2,3...).\nExample: sheet.autoFill(\"A1:A2\", \"A1:A10\")",
104
+ "tags": ["action"]
105
+ },
106
+ "addPicture": {
107
+ "signature": "def addPicture(name: str, base64_data: str, anchor_cell: str) -> None",
108
+ "docstring": "Add a picture from base64-encoded PNG or JPEG data. No data URI prefix.\nExample: sheet.addPicture(\"chart1\", base64_string, \"F2\")",
109
+ "tags": ["action"]
110
+ },
111
+ "setIBTextColors": {
112
+ "signature": "def setIBTextColors(range_addr: str, ignored_constants: list[int] | None = None, colors: dict | None = None, ) -> None",
113
+ "docstring": "IB text colors for numeric inputs only. Text strings and booleans are left untouched.\n- Blue/16711680: hard-coded numeric constants (int/float)\n- Black/0: formulas (same-sheet references)\n- Green/32768: formulas with cross-sheet references (contains '!')\nColors are BGR integers (native COM format).\nExample: sheet.setIBTextColors(\"A1:D10\", ignored_constants=[0])",
114
+ "tags": ["action"]
115
+ }
116
+ }
117
+ }
118
+ },
119
+ "helpers": {
120
+ "hex_to_bgr": {
121
+ "signature": "def hex_to_bgr(hex_str: str) -> int",
122
+ "docstring": "Convert \"#RRGGBB\" hex string to BGR integer for Excel COM.\nExample: ws.Range(\"A1\").Font.Color = hex_to_bgr(\"#FF0000\") # red",
123
+ "tags": ["action", "ask"]
124
+ },
125
+ "index_to_address": {
126
+ "signature": "def index_to_address(row: int, col: int) -> str",
127
+ "docstring": "Convert 0-based (row, col) to Excel address: index_to_address(0, 0) \u2192 \"A1\".",
128
+ "tags": ["action", "ask"]
129
+ },
130
+ "address_to_index": {
131
+ "signature": "def address_to_index(address: str) -> tuple[int, int]",
132
+ "docstring": "Parse \"A1\" into (row, col) 0-based tuple: address_to_index(\"B3\") \u2192 (2, 1).",
133
+ "tags": ["action", "ask"]
134
+ },
135
+ "col_letter": {
136
+ "signature": "def col_letter(index: int) -> str",
137
+ "docstring": "Convert 0-based column index to letter: col_letter(26) \u2192 \"AA\".",
138
+ "tags": ["action", "ask"]
139
+ },
140
+ "col_index": {
141
+ "signature": "def col_index(letter: str) -> int",
142
+ "docstring": "Convert column letter to 0-based index: col_index(\"AA\") \u2192 26.",
143
+ "tags": ["action", "ask"]
144
+ }
145
+ }
146
+ }
@@ -191,37 +191,6 @@ function collectSkillEntries(dir, includeRootFiles = true, ignoreMatcher, rootDi
191
191
  function collectAutoSkillEntries(dir, includeRootFiles = true) {
192
192
  return collectSkillEntries(dir, includeRootFiles);
193
193
  }
194
- function findGitRepoRoot(startDir) {
195
- let dir = resolve(startDir);
196
- while (true) {
197
- if (existsSync(join(dir, '.git'))) {
198
- return dir;
199
- }
200
- const parent = dirname(dir);
201
- if (parent === dir) {
202
- return null;
203
- }
204
- dir = parent;
205
- }
206
- }
207
- function collectAncestorAgentsSkillDirs(startDir) {
208
- const skillDirs = [];
209
- const resolvedStartDir = resolve(startDir);
210
- const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
211
- let dir = resolvedStartDir;
212
- while (true) {
213
- skillDirs.push(join(dir, '.agents', 'skills'));
214
- if (gitRepoRoot && dir === gitRepoRoot) {
215
- break;
216
- }
217
- const parent = dirname(dir);
218
- if (parent === dir) {
219
- break;
220
- }
221
- dir = parent;
222
- }
223
- return skillDirs;
224
- }
225
194
  function collectAutoPromptEntries(dir) {
226
195
  const entries = [];
227
196
  if (!existsSync(dir))
@@ -1370,8 +1339,6 @@ export class DefaultPackageManager {
1370
1339
  prompts: join(projectBaseDir, 'prompts'),
1371
1340
  themes: join(projectBaseDir, 'themes')
1372
1341
  };
1373
- const userAgentsSkillsDir = join(homedir(), '.agents', 'skills');
1374
- const projectAgentsSkillDirs = collectAncestorAgentsSkillDirs(this.cwd);
1375
1342
  const addResources = (resourceType, paths, metadata, overrides, baseDir) => {
1376
1343
  const target = this.getTargetMap(accumulator, resourceType);
1377
1344
  for (const path of paths) {
@@ -1380,17 +1347,11 @@ export class DefaultPackageManager {
1380
1347
  }
1381
1348
  };
1382
1349
  addResources('extensions', collectAutoExtensionEntries(projectDirs.extensions), projectMetadata, projectOverrides.extensions, projectBaseDir);
1383
- addResources('skills', [
1384
- ...collectAutoSkillEntries(projectDirs.skills),
1385
- ...projectAgentsSkillDirs.flatMap((dir) => collectAutoSkillEntries(dir))
1386
- ], projectMetadata, projectOverrides.skills, projectBaseDir);
1350
+ addResources('skills', collectAutoSkillEntries(projectDirs.skills), projectMetadata, projectOverrides.skills, projectBaseDir);
1387
1351
  addResources('prompts', collectAutoPromptEntries(projectDirs.prompts), projectMetadata, projectOverrides.prompts, projectBaseDir);
1388
1352
  addResources('themes', collectAutoThemeEntries(projectDirs.themes), projectMetadata, projectOverrides.themes, projectBaseDir);
1389
1353
  addResources('extensions', collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
1390
- addResources('skills', [
1391
- ...collectAutoSkillEntries(userDirs.skills),
1392
- ...collectAutoSkillEntries(userAgentsSkillsDir)
1393
- ], userMetadata, userOverrides.skills, globalBaseDir);
1354
+ addResources('skills', collectAutoSkillEntries(userDirs.skills), userMetadata, userOverrides.skills, globalBaseDir);
1394
1355
  // Bundled default skills shipped with the package
1395
1356
  const bundledMetadata = {
1396
1357
  source: 'auto',
@@ -336,7 +336,7 @@ export declare class SettingsManager {
336
336
  /** Whether session traces and ClickHouse logs are uploaded. Default: true. */
337
337
  getTelemetry(): boolean;
338
338
  setTelemetry(enabled: boolean): void;
339
- /** Whether dev mode is enabled (file tools, AGENTS.md, verbose logging). Default: false. */
339
+ /** Whether dev mode is enabled (AGENTS.md, dev resources, verbose logging). Default: false. */
340
340
  getDevMode(): boolean;
341
341
  setDevMode(enabled: boolean): void;
342
342
  /** Get all connection metadata (names, types, field names — no secrets). */
@@ -955,7 +955,7 @@ export class SettingsManager {
955
955
  this.markModified('telemetry');
956
956
  this.save();
957
957
  }
958
- /** Whether dev mode is enabled (file tools, AGENTS.md, verbose logging). Default: false. */
958
+ /** Whether dev mode is enabled (AGENTS.md, dev resources, verbose logging). Default: false. */
959
959
  getDevMode() {
960
960
  return this.settings.devMode ?? false;
961
961
  }
@@ -1,5 +1,9 @@
1
1
  import { type ProposalRoots } from './proposal-fs.js';
2
- export type ProposalKind = 'create' | 'update';
2
+ export declare const PROPOSAL_KIND: {
3
+ readonly CREATE: "create";
4
+ readonly UPDATE: "update";
5
+ };
6
+ export type ProposalKind = (typeof PROPOSAL_KIND)[keyof typeof PROPOSAL_KIND];
3
7
  export interface SkillProposalSummary {
4
8
  name: string;
5
9
  proposalDir: string;
@@ -8,6 +8,10 @@ import { lstatSync, readdirSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
9
  import { initializeSkillProposalRoots, liveSkillDir, proposalDir } from './proposal-fs.js';
10
10
  const SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
11
+ export const PROPOSAL_KIND = {
12
+ CREATE: 'create',
13
+ UPDATE: 'update'
14
+ };
11
15
  /** Passive discovery for status/prompt/reviewer; it must not mutate proposal folders. */
12
16
  export function listSkillProposals(roots) {
13
17
  initializeSkillProposalRoots(roots);
@@ -15,7 +19,9 @@ export function listSkillProposals(roots) {
15
19
  .filter((entry) => isValidProposalDirectoryEntry(roots, entry))
16
20
  .map((entry) => {
17
21
  const targetLiveSkillDir = liveSkillDir(roots, entry.name);
18
- const kind = pathExistsForLstat(targetLiveSkillDir) ? 'update' : 'create';
22
+ const kind = pathExistsForLstat(targetLiveSkillDir)
23
+ ? PROPOSAL_KIND.UPDATE
24
+ : PROPOSAL_KIND.CREATE;
19
25
  return {
20
26
  name: entry.name,
21
27
  proposalDir: proposalDir(roots, entry.name),
@@ -1,4 +1,4 @@
1
- import type { ProposalKind } from './catalog.js';
1
+ import { type ProposalKind } from './catalog.js';
2
2
  import { type ProposalRoots } from './proposal-fs.js';
3
3
  export interface SkillProposalReviewSnapshot {
4
4
  name: string;