shortcutxl 0.2.12 → 0.2.13

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 (110) hide show
  1. package/README.md +26 -26
  2. package/agent-docs/README.md +397 -397
  3. package/agent-docs/docs/compaction.md +390 -390
  4. package/agent-docs/docs/custom-provider.md +580 -580
  5. package/agent-docs/docs/extensions.md +1971 -1971
  6. package/agent-docs/docs/packages.md +209 -209
  7. package/agent-docs/docs/rpc.md +1317 -1317
  8. package/agent-docs/docs/sdk.md +962 -962
  9. package/agent-docs/docs/session.md +412 -412
  10. package/agent-docs/docs/termux.md +127 -127
  11. package/agent-docs/docs/tui.md +887 -887
  12. package/agent-docs/examples/README.md +25 -25
  13. package/agent-docs/examples/extensions/README.md +205 -205
  14. package/agent-docs/examples/extensions/antigravity-image-gen.ts +447 -447
  15. package/agent-docs/examples/extensions/auto-commit-on-exit.ts +49 -49
  16. package/agent-docs/examples/extensions/bash-spawn-hook.ts +30 -30
  17. package/agent-docs/examples/extensions/bookmark.ts +50 -50
  18. package/agent-docs/examples/extensions/built-in-tool-renderer.ts +256 -256
  19. package/agent-docs/examples/extensions/claude-rules.ts +86 -86
  20. package/agent-docs/examples/extensions/commands.ts +75 -75
  21. package/agent-docs/examples/extensions/confirm-destructive.ts +59 -59
  22. package/agent-docs/examples/extensions/custom-compaction.ts +126 -126
  23. package/agent-docs/examples/extensions/custom-footer.ts +63 -63
  24. package/agent-docs/examples/extensions/custom-header.ts +73 -73
  25. package/agent-docs/examples/extensions/custom-provider-anthropic/index.ts +660 -660
  26. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +362 -362
  27. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +88 -88
  28. package/agent-docs/examples/extensions/custom-provider-qwen-cli/index.ts +349 -349
  29. package/agent-docs/examples/extensions/dirty-repo-guard.ts +56 -56
  30. package/agent-docs/examples/extensions/doom-overlay/doom-component.ts +133 -133
  31. package/agent-docs/examples/extensions/doom-overlay/doom-keys.ts +108 -108
  32. package/agent-docs/examples/extensions/doom-overlay/index.ts +74 -74
  33. package/agent-docs/examples/extensions/dynamic-resources/index.ts +15 -15
  34. package/agent-docs/examples/extensions/dynamic-tools.ts +77 -77
  35. package/agent-docs/examples/extensions/event-bus.ts +43 -43
  36. package/agent-docs/examples/extensions/file-trigger.ts +41 -41
  37. package/agent-docs/examples/extensions/git-checkpoint.ts +53 -53
  38. package/agent-docs/examples/extensions/handoff.ts +155 -155
  39. package/agent-docs/examples/extensions/hello.ts +25 -25
  40. package/agent-docs/examples/extensions/inline-bash.ts +94 -94
  41. package/agent-docs/examples/extensions/input-transform.ts +43 -43
  42. package/agent-docs/examples/extensions/interactive-shell.ts +209 -209
  43. package/agent-docs/examples/extensions/mac-system-theme.ts +47 -47
  44. package/agent-docs/examples/extensions/message-renderer.ts +59 -59
  45. package/agent-docs/examples/extensions/minimal-mode.ts +430 -430
  46. package/agent-docs/examples/extensions/modal-editor.ts +90 -90
  47. package/agent-docs/examples/extensions/model-status.ts +31 -31
  48. package/agent-docs/examples/extensions/notify.ts +55 -55
  49. package/agent-docs/examples/extensions/overlay-qa-tests.ts +936 -936
  50. package/agent-docs/examples/extensions/overlay-test.ts +159 -159
  51. package/agent-docs/examples/extensions/permission-gate.ts +37 -37
  52. package/agent-docs/examples/extensions/pirate.ts +47 -47
  53. package/agent-docs/examples/extensions/plan-mode/index.ts +363 -363
  54. package/agent-docs/examples/extensions/preset.ts +418 -418
  55. package/agent-docs/examples/extensions/protected-paths.ts +30 -30
  56. package/agent-docs/examples/extensions/qna.ts +122 -122
  57. package/agent-docs/examples/extensions/question.ts +278 -278
  58. package/agent-docs/examples/extensions/questionnaire.ts +440 -440
  59. package/agent-docs/examples/extensions/rainbow-editor.ts +90 -90
  60. package/agent-docs/examples/extensions/reload-runtime.ts +37 -37
  61. package/agent-docs/examples/extensions/rpc-demo.ts +124 -124
  62. package/agent-docs/examples/extensions/sandbox/index.ts +324 -324
  63. package/agent-docs/examples/extensions/send-user-message.ts +97 -97
  64. package/agent-docs/examples/extensions/session-name.ts +27 -27
  65. package/agent-docs/examples/extensions/shutdown-command.ts +69 -69
  66. package/agent-docs/examples/extensions/snake.ts +343 -343
  67. package/agent-docs/examples/extensions/space-invaders.ts +566 -566
  68. package/agent-docs/examples/extensions/ssh.ts +233 -233
  69. package/agent-docs/examples/extensions/status-line.ts +40 -40
  70. package/agent-docs/examples/extensions/subagent/agents.ts +130 -130
  71. package/agent-docs/examples/extensions/subagent/index.ts +1068 -1068
  72. package/agent-docs/examples/extensions/summarize.ts +206 -206
  73. package/agent-docs/examples/extensions/system-prompt-header.ts +17 -17
  74. package/agent-docs/examples/extensions/timed-confirm.ts +72 -72
  75. package/agent-docs/examples/extensions/titlebar-spinner.ts +58 -58
  76. package/agent-docs/examples/extensions/todo.ts +314 -314
  77. package/agent-docs/examples/extensions/tool-override.ts +146 -146
  78. package/agent-docs/examples/extensions/tools.ts +145 -145
  79. package/agent-docs/examples/extensions/trigger-compact.ts +40 -40
  80. package/agent-docs/examples/extensions/truncated-tool.ts +194 -194
  81. package/agent-docs/examples/extensions/widget-placement.ts +17 -17
  82. package/agent-docs/examples/extensions/with-deps/index.ts +37 -37
  83. package/agent-docs/examples/rpc-extension-ui.ts +654 -654
  84. package/agent-docs/examples/sdk/01-minimal.ts +22 -22
  85. package/agent-docs/examples/sdk/02-custom-model.ts +48 -48
  86. package/agent-docs/examples/sdk/03-custom-prompt.ts +55 -55
  87. package/agent-docs/examples/sdk/04-skills.ts +53 -53
  88. package/agent-docs/examples/sdk/05-tools.ts +56 -56
  89. package/agent-docs/examples/sdk/06-extensions.ts +88 -88
  90. package/agent-docs/examples/sdk/07-context-files.ts +40 -40
  91. package/agent-docs/examples/sdk/08-prompt-templates.ts +47 -47
  92. package/agent-docs/examples/sdk/09-api-keys-and-oauth.ts +48 -48
  93. package/agent-docs/examples/sdk/10-settings.ts +54 -54
  94. package/agent-docs/examples/sdk/11-sessions.ts +48 -48
  95. package/agent-docs/examples/sdk/12-full-control.ts +82 -82
  96. package/agent-docs/examples/sdk/README.md +144 -144
  97. package/agent-docs/xll-spec.md +110 -110
  98. package/dist/core/auth-storage.js +21 -2
  99. package/package.json +1 -1
  100. package/xll/ShortcutXL.xll +0 -0
  101. package/xll/modules/debug_render.py +272 -272
  102. package/xll/modules/gameboy.py +241 -241
  103. package/xll/modules/pong.py +188 -188
  104. package/xll/modules/shortcut_xl/_diff_highlight.py +176 -0
  105. package/xll/modules/shortcut_xl/_log.py +12 -12
  106. package/xll/modules/shortcut_xl/_registry.py +44 -44
  107. package/xll/modules/stocks.py +100 -100
  108. /package/skills/{com-advanced-api → COM-advanced-api}/SKILL.md +0 -0
  109. /package/skills/{com-advanced-api → COM-advanced-api}/excel-type-library.py +0 -0
  110. /package/skills/{com-advanced-api → COM-advanced-api}/office-type-library.py +0 -0
@@ -1,194 +1,194 @@
1
- /**
2
- * Truncated Tool Example - Demonstrates proper output truncation for custom tools
3
- *
4
- * Custom tools MUST truncate their output to avoid overwhelming the LLM context.
5
- * The built-in limit is 50KB (~10k tokens) and 2000 lines, whichever is hit first.
6
- *
7
- * This example shows how to:
8
- * 1. Use the built-in truncation utilities
9
- * 2. Write full output to a temp file when truncated
10
- * 3. Inform the LLM where to find the complete output
11
- * 4. Custom rendering of tool calls and results
12
- *
13
- * The `rg` tool here wraps ripgrep with proper truncation. Compare this to the
14
- * built-in `grep` tool in src/core/tools/grep.ts for a more complete implementation.
15
- */
16
-
17
- import { Type } from '@sinclair/typebox';
18
- import { execSync } from 'child_process';
19
- import { mkdtempSync, writeFileSync } from 'fs';
20
- import { tmpdir } from 'os';
21
- import { join } from 'path';
22
- import type { ExtensionAPI } from 'shortcutxl';
23
- import {
24
- DEFAULT_MAX_BYTES,
25
- DEFAULT_MAX_LINES,
26
- formatSize,
27
- Text,
28
- truncateHead,
29
- type TruncationResult
30
- } from 'shortcutxl';
31
-
32
- const RgParams = Type.Object({
33
- pattern: Type.String({ description: 'Search pattern (regex)' }),
34
- path: Type.Optional(
35
- Type.String({ description: 'Directory to search (default: current directory)' })
36
- ),
37
- glob: Type.Optional(Type.String({ description: "File glob pattern, e.g. '*.ts'" }))
38
- });
39
-
40
- interface RgDetails {
41
- pattern: string;
42
- path?: string;
43
- glob?: string;
44
- matchCount: number;
45
- truncation?: TruncationResult;
46
- fullOutputPath?: string;
47
- }
48
-
49
- export default function (shortcut: ExtensionAPI) {
50
- shortcut.registerTool({
51
- name: 'rg',
52
- label: 'ripgrep',
53
- // Document the truncation limits in the tool description so the LLM knows
54
- description: `Search file contents using ripgrep. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first). If truncated, full output is saved to a temp file.`,
55
- parameters: RgParams,
56
-
57
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
58
- const { pattern, path: searchPath, glob } = params;
59
-
60
- // Build the ripgrep command
61
- const args = ['rg', '--line-number', '--color=never'];
62
- if (glob) args.push('--glob', glob);
63
- args.push(pattern);
64
- args.push(searchPath || '.');
65
-
66
- let output: string;
67
- try {
68
- output = execSync(args.join(' '), {
69
- cwd: ctx.cwd,
70
- encoding: 'utf-8',
71
- maxBuffer: 100 * 1024 * 1024 // 100MB buffer to capture full output
72
- });
73
- } catch (err: any) {
74
- // ripgrep exits with 1 when no matches found
75
- if (err.status === 1) {
76
- return {
77
- content: [{ type: 'text', text: 'No matches found' }],
78
- details: { pattern, path: searchPath, glob, matchCount: 0 } as RgDetails
79
- };
80
- }
81
- throw new Error(`ripgrep failed: ${err.message}`);
82
- }
83
-
84
- if (!output.trim()) {
85
- return {
86
- content: [{ type: 'text', text: 'No matches found' }],
87
- details: { pattern, path: searchPath, glob, matchCount: 0 } as RgDetails
88
- };
89
- }
90
-
91
- // Apply truncation using built-in utilities
92
- // truncateHead keeps the first N lines/bytes (good for search results)
93
- // truncateTail keeps the last N lines/bytes (good for logs/command output)
94
- const truncation = truncateHead(output, {
95
- maxLines: DEFAULT_MAX_LINES,
96
- maxBytes: DEFAULT_MAX_BYTES
97
- });
98
-
99
- // Count matches (each non-empty line with a match)
100
- const matchCount = output.split('\n').filter((line) => line.trim()).length;
101
-
102
- const details: RgDetails = {
103
- pattern,
104
- path: searchPath,
105
- glob,
106
- matchCount
107
- };
108
-
109
- let resultText = truncation.content;
110
-
111
- if (truncation.truncated) {
112
- // Save full output to a temp file so LLM can access it if needed
113
- const tempDir = mkdtempSync(join(tmpdir(), 'shortcut-rg-'));
114
- const tempFile = join(tempDir, 'output.txt');
115
- writeFileSync(tempFile, output);
116
-
117
- details.truncation = truncation;
118
- details.fullOutputPath = tempFile;
119
-
120
- // Add truncation notice - this helps the LLM understand the output is incomplete
121
- const truncatedLines = truncation.totalLines - truncation.outputLines;
122
- const truncatedBytes = truncation.totalBytes - truncation.outputBytes;
123
-
124
- resultText += `\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`;
125
- resultText += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
126
- resultText += ` ${truncatedLines} lines (${formatSize(truncatedBytes)}) omitted.`;
127
- resultText += ` Full output saved to: ${tempFile}]`;
128
- }
129
-
130
- return {
131
- content: [{ type: 'text', text: resultText }],
132
- details
133
- };
134
- },
135
-
136
- // Custom rendering of the tool call (shown before/during execution)
137
- renderCall(args, theme) {
138
- let text = theme.fg('toolTitle', theme.bold('rg '));
139
- text += theme.fg('accent', `"${args.pattern}"`);
140
- if (args.path) {
141
- text += theme.fg('muted', ` in ${args.path}`);
142
- }
143
- if (args.glob) {
144
- text += theme.fg('dim', ` --glob ${args.glob}`);
145
- }
146
- return new Text(text, 0, 0);
147
- },
148
-
149
- // Custom rendering of the tool result
150
- renderResult(result, { expanded, isPartial }, theme) {
151
- const details = result.details as RgDetails | undefined;
152
-
153
- // Handle streaming/partial results
154
- if (isPartial) {
155
- return new Text(theme.fg('warning', 'Searching...'), 0, 0);
156
- }
157
-
158
- // No matches
159
- if (!details || details.matchCount === 0) {
160
- return new Text(theme.fg('dim', 'No matches found'), 0, 0);
161
- }
162
-
163
- // Build result display
164
- let text = theme.fg('success', `${details.matchCount} matches`);
165
-
166
- // Show truncation warning if applicable
167
- if (details.truncation?.truncated) {
168
- text += theme.fg('warning', ' (truncated)');
169
- }
170
-
171
- // In expanded view, show the actual matches
172
- if (expanded) {
173
- const content = result.content[0];
174
- if (content?.type === 'text') {
175
- // Show first 20 lines in expanded view, or all if fewer
176
- const lines = content.text.split('\n').slice(0, 20);
177
- for (const line of lines) {
178
- text += `\n${theme.fg('dim', line)}`;
179
- }
180
- if (content.text.split('\n').length > 20) {
181
- text += `\n${theme.fg('muted', '... (use read tool to see full output)')}`;
182
- }
183
- }
184
-
185
- // Show temp file path if truncated
186
- if (details.fullOutputPath) {
187
- text += `\n${theme.fg('dim', `Full output: ${details.fullOutputPath}`)}`;
188
- }
189
- }
190
-
191
- return new Text(text, 0, 0);
192
- }
193
- });
194
- }
1
+ /**
2
+ * Truncated Tool Example - Demonstrates proper output truncation for custom tools
3
+ *
4
+ * Custom tools MUST truncate their output to avoid overwhelming the LLM context.
5
+ * The built-in limit is 50KB (~10k tokens) and 2000 lines, whichever is hit first.
6
+ *
7
+ * This example shows how to:
8
+ * 1. Use the built-in truncation utilities
9
+ * 2. Write full output to a temp file when truncated
10
+ * 3. Inform the LLM where to find the complete output
11
+ * 4. Custom rendering of tool calls and results
12
+ *
13
+ * The `rg` tool here wraps ripgrep with proper truncation. Compare this to the
14
+ * built-in `grep` tool in src/core/tools/grep.ts for a more complete implementation.
15
+ */
16
+
17
+ import { Type } from '@sinclair/typebox';
18
+ import { execSync } from 'child_process';
19
+ import { mkdtempSync, writeFileSync } from 'fs';
20
+ import { tmpdir } from 'os';
21
+ import { join } from 'path';
22
+ import type { ExtensionAPI } from 'shortcutxl';
23
+ import {
24
+ DEFAULT_MAX_BYTES,
25
+ DEFAULT_MAX_LINES,
26
+ formatSize,
27
+ Text,
28
+ truncateHead,
29
+ type TruncationResult
30
+ } from 'shortcutxl';
31
+
32
+ const RgParams = Type.Object({
33
+ pattern: Type.String({ description: 'Search pattern (regex)' }),
34
+ path: Type.Optional(
35
+ Type.String({ description: 'Directory to search (default: current directory)' })
36
+ ),
37
+ glob: Type.Optional(Type.String({ description: "File glob pattern, e.g. '*.ts'" }))
38
+ });
39
+
40
+ interface RgDetails {
41
+ pattern: string;
42
+ path?: string;
43
+ glob?: string;
44
+ matchCount: number;
45
+ truncation?: TruncationResult;
46
+ fullOutputPath?: string;
47
+ }
48
+
49
+ export default function (shortcut: ExtensionAPI) {
50
+ shortcut.registerTool({
51
+ name: 'rg',
52
+ label: 'ripgrep',
53
+ // Document the truncation limits in the tool description so the LLM knows
54
+ description: `Search file contents using ripgrep. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} (whichever is hit first). If truncated, full output is saved to a temp file.`,
55
+ parameters: RgParams,
56
+
57
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
58
+ const { pattern, path: searchPath, glob } = params;
59
+
60
+ // Build the ripgrep command
61
+ const args = ['rg', '--line-number', '--color=never'];
62
+ if (glob) args.push('--glob', glob);
63
+ args.push(pattern);
64
+ args.push(searchPath || '.');
65
+
66
+ let output: string;
67
+ try {
68
+ output = execSync(args.join(' '), {
69
+ cwd: ctx.cwd,
70
+ encoding: 'utf-8',
71
+ maxBuffer: 100 * 1024 * 1024 // 100MB buffer to capture full output
72
+ });
73
+ } catch (err: any) {
74
+ // ripgrep exits with 1 when no matches found
75
+ if (err.status === 1) {
76
+ return {
77
+ content: [{ type: 'text', text: 'No matches found' }],
78
+ details: { pattern, path: searchPath, glob, matchCount: 0 } as RgDetails
79
+ };
80
+ }
81
+ throw new Error(`ripgrep failed: ${err.message}`);
82
+ }
83
+
84
+ if (!output.trim()) {
85
+ return {
86
+ content: [{ type: 'text', text: 'No matches found' }],
87
+ details: { pattern, path: searchPath, glob, matchCount: 0 } as RgDetails
88
+ };
89
+ }
90
+
91
+ // Apply truncation using built-in utilities
92
+ // truncateHead keeps the first N lines/bytes (good for search results)
93
+ // truncateTail keeps the last N lines/bytes (good for logs/command output)
94
+ const truncation = truncateHead(output, {
95
+ maxLines: DEFAULT_MAX_LINES,
96
+ maxBytes: DEFAULT_MAX_BYTES
97
+ });
98
+
99
+ // Count matches (each non-empty line with a match)
100
+ const matchCount = output.split('\n').filter((line) => line.trim()).length;
101
+
102
+ const details: RgDetails = {
103
+ pattern,
104
+ path: searchPath,
105
+ glob,
106
+ matchCount
107
+ };
108
+
109
+ let resultText = truncation.content;
110
+
111
+ if (truncation.truncated) {
112
+ // Save full output to a temp file so LLM can access it if needed
113
+ const tempDir = mkdtempSync(join(tmpdir(), 'shortcut-rg-'));
114
+ const tempFile = join(tempDir, 'output.txt');
115
+ writeFileSync(tempFile, output);
116
+
117
+ details.truncation = truncation;
118
+ details.fullOutputPath = tempFile;
119
+
120
+ // Add truncation notice - this helps the LLM understand the output is incomplete
121
+ const truncatedLines = truncation.totalLines - truncation.outputLines;
122
+ const truncatedBytes = truncation.totalBytes - truncation.outputBytes;
123
+
124
+ resultText += `\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`;
125
+ resultText += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
126
+ resultText += ` ${truncatedLines} lines (${formatSize(truncatedBytes)}) omitted.`;
127
+ resultText += ` Full output saved to: ${tempFile}]`;
128
+ }
129
+
130
+ return {
131
+ content: [{ type: 'text', text: resultText }],
132
+ details
133
+ };
134
+ },
135
+
136
+ // Custom rendering of the tool call (shown before/during execution)
137
+ renderCall(args, theme) {
138
+ let text = theme.fg('toolTitle', theme.bold('rg '));
139
+ text += theme.fg('accent', `"${args.pattern}"`);
140
+ if (args.path) {
141
+ text += theme.fg('muted', ` in ${args.path}`);
142
+ }
143
+ if (args.glob) {
144
+ text += theme.fg('dim', ` --glob ${args.glob}`);
145
+ }
146
+ return new Text(text, 0, 0);
147
+ },
148
+
149
+ // Custom rendering of the tool result
150
+ renderResult(result, { expanded, isPartial }, theme) {
151
+ const details = result.details as RgDetails | undefined;
152
+
153
+ // Handle streaming/partial results
154
+ if (isPartial) {
155
+ return new Text(theme.fg('warning', 'Searching...'), 0, 0);
156
+ }
157
+
158
+ // No matches
159
+ if (!details || details.matchCount === 0) {
160
+ return new Text(theme.fg('dim', 'No matches found'), 0, 0);
161
+ }
162
+
163
+ // Build result display
164
+ let text = theme.fg('success', `${details.matchCount} matches`);
165
+
166
+ // Show truncation warning if applicable
167
+ if (details.truncation?.truncated) {
168
+ text += theme.fg('warning', ' (truncated)');
169
+ }
170
+
171
+ // In expanded view, show the actual matches
172
+ if (expanded) {
173
+ const content = result.content[0];
174
+ if (content?.type === 'text') {
175
+ // Show first 20 lines in expanded view, or all if fewer
176
+ const lines = content.text.split('\n').slice(0, 20);
177
+ for (const line of lines) {
178
+ text += `\n${theme.fg('dim', line)}`;
179
+ }
180
+ if (content.text.split('\n').length > 20) {
181
+ text += `\n${theme.fg('muted', '... (use read tool to see full output)')}`;
182
+ }
183
+ }
184
+
185
+ // Show temp file path if truncated
186
+ if (details.fullOutputPath) {
187
+ text += `\n${theme.fg('dim', `Full output: ${details.fullOutputPath}`)}`;
188
+ }
189
+ }
190
+
191
+ return new Text(text, 0, 0);
192
+ }
193
+ });
194
+ }
@@ -1,17 +1,17 @@
1
- import type { ExtensionAPI, ExtensionContext } from 'shortcutxl';
2
-
3
- const applyWidgets = (ctx: ExtensionContext) => {
4
- if (!ctx.hasUI) return;
5
- ctx.ui.setWidget('widget-above', ['Above editor widget']);
6
- ctx.ui.setWidget('widget-below', ['Below editor widget'], { placement: 'belowEditor' });
7
- };
8
-
9
- export default function widgetPlacementExtension(shortcut: ExtensionAPI) {
10
- shortcut.on('session_start', (_event, ctx) => {
11
- applyWidgets(ctx);
12
- });
13
-
14
- shortcut.on('session_switch', (_event, ctx) => {
15
- applyWidgets(ctx);
16
- });
17
- }
1
+ import type { ExtensionAPI, ExtensionContext } from 'shortcutxl';
2
+
3
+ const applyWidgets = (ctx: ExtensionContext) => {
4
+ if (!ctx.hasUI) return;
5
+ ctx.ui.setWidget('widget-above', ['Above editor widget']);
6
+ ctx.ui.setWidget('widget-below', ['Below editor widget'], { placement: 'belowEditor' });
7
+ };
8
+
9
+ export default function widgetPlacementExtension(shortcut: ExtensionAPI) {
10
+ shortcut.on('session_start', (_event, ctx) => {
11
+ applyWidgets(ctx);
12
+ });
13
+
14
+ shortcut.on('session_switch', (_event, ctx) => {
15
+ applyWidgets(ctx);
16
+ });
17
+ }
@@ -1,37 +1,37 @@
1
- /**
2
- * Example extension with its own npm dependencies.
3
- * Tests that jiti resolves modules from the extension's own node_modules.
4
- *
5
- * Requires: npm install in this directory
6
- */
7
-
8
- import { Type } from '@sinclair/typebox';
9
- import ms from 'ms';
10
- import type { ExtensionAPI } from 'shortcutxl';
11
-
12
- export default function (shortcut: ExtensionAPI) {
13
- // Register a tool that uses ms
14
- shortcut.registerTool({
15
- name: 'parse_duration',
16
- label: 'Parse Duration',
17
- description:
18
- "Parse a human-readable duration string (e.g., '2 days', '1h', '5m') to milliseconds",
19
- parameters: Type.Object({
20
- duration: Type.String({ description: "Duration string like '2 days', '1h', '5m'" })
21
- }),
22
- execute: async (_toolCallId, params) => {
23
- const result = ms(params.duration as ms.StringValue);
24
- if (result === undefined) {
25
- return {
26
- content: [{ type: 'text', text: `Invalid duration: "${params.duration}"` }],
27
- isError: true,
28
- details: {}
29
- };
30
- }
31
- return {
32
- content: [{ type: 'text', text: `${params.duration} = ${result} milliseconds` }],
33
- details: {}
34
- };
35
- }
36
- });
37
- }
1
+ /**
2
+ * Example extension with its own npm dependencies.
3
+ * Tests that jiti resolves modules from the extension's own node_modules.
4
+ *
5
+ * Requires: npm install in this directory
6
+ */
7
+
8
+ import { Type } from '@sinclair/typebox';
9
+ import ms from 'ms';
10
+ import type { ExtensionAPI } from 'shortcutxl';
11
+
12
+ export default function (shortcut: ExtensionAPI) {
13
+ // Register a tool that uses ms
14
+ shortcut.registerTool({
15
+ name: 'parse_duration',
16
+ label: 'Parse Duration',
17
+ description:
18
+ "Parse a human-readable duration string (e.g., '2 days', '1h', '5m') to milliseconds",
19
+ parameters: Type.Object({
20
+ duration: Type.String({ description: "Duration string like '2 days', '1h', '5m'" })
21
+ }),
22
+ execute: async (_toolCallId, params) => {
23
+ const result = ms(params.duration as ms.StringValue);
24
+ if (result === undefined) {
25
+ return {
26
+ content: [{ type: 'text', text: `Invalid duration: "${params.duration}"` }],
27
+ isError: true,
28
+ details: {}
29
+ };
30
+ }
31
+ return {
32
+ content: [{ type: 'text', text: `${params.duration} = ${result} milliseconds` }],
33
+ details: {}
34
+ };
35
+ }
36
+ });
37
+ }