@maccesar/aiskills 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +531 -0
  2. package/bin/aiskills.js +76 -0
  3. package/lib/cache.js +49 -0
  4. package/lib/cleanup.js +77 -0
  5. package/lib/commands/auto-update.js +131 -0
  6. package/lib/commands/doctor.js +139 -0
  7. package/lib/commands/list.js +77 -0
  8. package/lib/commands/skills.js +263 -0
  9. package/lib/commands/status.js +94 -0
  10. package/lib/commands/uninstall.js +182 -0
  11. package/lib/commands/update.js +149 -0
  12. package/lib/config.js +90 -0
  13. package/lib/downloader.js +110 -0
  14. package/lib/hooks.js +74 -0
  15. package/lib/installer.js +114 -0
  16. package/lib/platform.js +112 -0
  17. package/lib/prompts/checkboxCancel.js +264 -0
  18. package/lib/prompts/selectCancel.js +204 -0
  19. package/lib/symlink.js +154 -0
  20. package/lib/utils.js +49 -0
  21. package/package.json +61 -0
  22. package/skills/humaniza/SKILL.md +51 -0
  23. package/skills/humaniza/agents/openai.yaml +4 -0
  24. package/skills/humaniza/references/ai-patterns-es.md +51 -0
  25. package/skills/humaniza/references/checklist.md +9 -0
  26. package/skills/humaniza/references/examples.md +17 -0
  27. package/skills/humaniza/references/lexicon-es-mx.md +36 -0
  28. package/skills/humaniza/references/modes-es-mx.md +41 -0
  29. package/skills/humaniza/references/voice-es-mx.md +24 -0
  30. package/skills/refactoring-ui/SKILL.md +59 -0
  31. package/skills/refactoring-ui/references/01-design-process.md +72 -0
  32. package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
  33. package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
  34. package/skills/refactoring-ui/references/04-typography.md +70 -0
  35. package/skills/refactoring-ui/references/05-color.md +96 -0
  36. package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
  37. package/skills/refactoring-ui/references/07-images.md +75 -0
  38. package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
  39. package/skills/stitch-showcase/SKILL.md +411 -0
  40. package/skills/stitch-showcase/references/01-navbar.md +52 -0
  41. package/skills/stitch-showcase/references/02-hero.md +56 -0
  42. package/skills/stitch-showcase/references/03-design-system.md +102 -0
  43. package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
  44. package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
  45. package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
  46. package/skills/stitch-showcase/references/07-theme-system.md +77 -0
  47. package/skills/stitch-showcase/references/08-type-detection.md +81 -0
  48. package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
  49. package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
  50. package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
  51. package/skills/stitch-showcase/references/catalog-template.html +841 -0
  52. package/skills/stitch-showcase/references/index.html +299 -0
  53. package/skills/stitch-showcase/references/viewer.html +412 -0
  54. package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
  55. package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
  56. package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
  57. package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
  58. package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
  59. package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
  60. package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
  61. package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
  62. package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
  63. package/skills/stitch-showcase/scripts/component_utils.py +398 -0
  64. package/skills/stitch-showcase/scripts/detect_components.py +284 -0
  65. package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
  66. package/skills/stitch-showcase/scripts/extract_text.py +268 -0
  67. package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
  68. package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
  69. package/skills/vscode-extension-dev/SKILL.md +114 -0
  70. package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
  71. package/skills/vscode-extension-dev/references/architecture.md +287 -0
  72. package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
  73. package/skills/vscode-extension-dev/references/publishing.md +251 -0
@@ -0,0 +1,625 @@
1
+ # VS Code API Patterns
2
+
3
+ Working TypeScript implementations for common VS Code extension patterns.
4
+
5
+ ## TreeDataProvider
6
+
7
+ Provides data for a TreeView in the sidebar or panel.
8
+
9
+ ```typescript
10
+ import * as vscode from 'vscode';
11
+
12
+ interface TreeItem {
13
+ id: string;
14
+ label: string;
15
+ children?: TreeItem[];
16
+ }
17
+
18
+ class MyTreeProvider implements vscode.TreeDataProvider<TreeItem> {
19
+ private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
20
+ readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
21
+
22
+ private items: TreeItem[] = [];
23
+
24
+ refresh(): void {
25
+ this._onDidChangeTreeData.fire(undefined);
26
+ }
27
+
28
+ getTreeItem(element: TreeItem): vscode.TreeItem {
29
+ const treeItem = new vscode.TreeItem(
30
+ element.label,
31
+ element.children?.length
32
+ ? vscode.TreeItemCollapsibleState.Collapsed
33
+ : vscode.TreeItemCollapsibleState.None
34
+ );
35
+ treeItem.id = element.id;
36
+ treeItem.contextValue = element.children ? 'parent' : 'leaf';
37
+ treeItem.iconPath = new vscode.ThemeIcon('symbol-file');
38
+ // Make leaf items clickable
39
+ if (!element.children) {
40
+ treeItem.command = {
41
+ command: 'myExt.openItem',
42
+ title: 'Open Item',
43
+ arguments: [element],
44
+ };
45
+ }
46
+ return treeItem;
47
+ }
48
+
49
+ getChildren(element?: TreeItem): TreeItem[] {
50
+ if (!element) {
51
+ return this.items;
52
+ }
53
+ return element.children ?? [];
54
+ }
55
+
56
+ setItems(items: TreeItem[]): void {
57
+ this.items = items;
58
+ this.refresh();
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Registering the TreeView
64
+
65
+ ```typescript
66
+ export function activate(context: vscode.ExtensionContext) {
67
+ const treeProvider = new MyTreeProvider();
68
+
69
+ const treeView = vscode.window.createTreeView('myTreeView', {
70
+ treeDataProvider: treeProvider,
71
+ showCollapseAll: true,
72
+ });
73
+
74
+ context.subscriptions.push(
75
+ treeView,
76
+ vscode.commands.registerCommand('myExt.refresh', () => treeProvider.refresh()),
77
+ vscode.commands.registerCommand('myExt.openItem', (item: TreeItem) => {
78
+ vscode.window.showInformationMessage(`Opened: ${item.label}`);
79
+ }),
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## Webview Panel
85
+
86
+ Full HTML rendering with CSP and bidirectional messaging.
87
+
88
+ ```typescript
89
+ import * as vscode from 'vscode';
90
+
91
+ class MyWebviewPanel {
92
+ public static readonly viewType = 'myExt.webview';
93
+ private readonly _panel: vscode.WebviewPanel;
94
+ private readonly _extensionUri: vscode.Uri;
95
+ private _disposables: vscode.Disposable[] = [];
96
+
97
+ public static create(extensionUri: vscode.Uri): MyWebviewPanel {
98
+ const panel = vscode.window.createWebviewPanel(
99
+ MyWebviewPanel.viewType,
100
+ 'My Panel',
101
+ vscode.ViewColumn.One,
102
+ {
103
+ enableScripts: true,
104
+ retainContextWhenHidden: false, // saves memory; set true if state is expensive
105
+ localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')],
106
+ },
107
+ );
108
+
109
+ return new MyWebviewPanel(panel, extensionUri);
110
+ }
111
+
112
+ private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
113
+ this._panel = panel;
114
+ this._extensionUri = extensionUri;
115
+
116
+ this._panel.webview.html = this._getHtml(this._panel.webview);
117
+
118
+ // Handle messages FROM the webview
119
+ this._panel.webview.onDidReceiveMessage(
120
+ (message: { command: string; data?: unknown }) => {
121
+ switch (message.command) {
122
+ case 'save':
123
+ this._handleSave(message.data);
124
+ return;
125
+ case 'requestData':
126
+ this._sendData();
127
+ return;
128
+ }
129
+ },
130
+ null,
131
+ this._disposables,
132
+ );
133
+
134
+ this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
135
+ }
136
+
137
+ /** Send data TO the webview */
138
+ public sendMessage(command: string, data: unknown): void {
139
+ this._panel.webview.postMessage({ command, data });
140
+ }
141
+
142
+ private _handleSave(data: unknown): void {
143
+ vscode.window.showInformationMessage('Data saved!');
144
+ }
145
+
146
+ private _sendData(): void {
147
+ this.sendMessage('loadData', { items: ['a', 'b', 'c'] });
148
+ }
149
+
150
+ private _getHtml(webview: vscode.Webview): string {
151
+ const styleUri = webview.asWebviewUri(
152
+ vscode.Uri.joinPath(this._extensionUri, 'media', 'style.css'),
153
+ );
154
+ const scriptUri = webview.asWebviewUri(
155
+ vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'),
156
+ );
157
+ const nonce = getNonce();
158
+
159
+ return /*html*/ `<!DOCTYPE html>
160
+ <html lang="en">
161
+ <head>
162
+ <meta charset="UTF-8">
163
+ <meta http-equiv="Content-Security-Policy"
164
+ content="default-src 'none';
165
+ style-src ${webview.cspSource};
166
+ script-src 'nonce-${nonce}';
167
+ img-src ${webview.cspSource} https:;
168
+ font-src ${webview.cspSource};">
169
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
170
+ <link href="${styleUri}" rel="stylesheet">
171
+ <title>My Panel</title>
172
+ </head>
173
+ <body>
174
+ <div id="app"></div>
175
+ <script nonce="${nonce}" src="${scriptUri}"></script>
176
+ </body>
177
+ </html>`;
178
+ }
179
+
180
+ public dispose(): void {
181
+ this._panel.dispose();
182
+ while (this._disposables.length) {
183
+ const d = this._disposables.pop();
184
+ d?.dispose();
185
+ }
186
+ }
187
+ }
188
+
189
+ function getNonce(): string {
190
+ let text = '';
191
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
192
+ for (let i = 0; i < 32; i++) {
193
+ text += chars.charAt(Math.floor(Math.random() * chars.length));
194
+ }
195
+ return text;
196
+ }
197
+ ```
198
+
199
+ ### Webview Script (media/main.js)
200
+
201
+ ```javascript
202
+ // Inside the webview — communicates with the extension via postMessage
203
+ (function () {
204
+ // @ts-ignore
205
+ const vscode = acquireVsCodeApi();
206
+
207
+ // Send message TO the extension
208
+ function save(data) {
209
+ vscode.postMessage({ command: 'save', data });
210
+ }
211
+
212
+ // Receive messages FROM the extension
213
+ window.addEventListener('message', (event) => {
214
+ const message = event.data;
215
+ switch (message.command) {
216
+ case 'loadData':
217
+ renderData(message.data);
218
+ break;
219
+ }
220
+ });
221
+
222
+ function renderData(data) {
223
+ const app = document.getElementById('app');
224
+ if (app) {
225
+ app.textContent = JSON.stringify(data, null, 2);
226
+ }
227
+ }
228
+
229
+ // Request initial data
230
+ vscode.postMessage({ command: 'requestData' });
231
+ })();
232
+ ```
233
+
234
+ ## QuickPick
235
+
236
+ Modal list with filtering, multi-select, and async items.
237
+
238
+ ### Simple QuickPick
239
+
240
+ ```typescript
241
+ const items: vscode.QuickPickItem[] = [
242
+ { label: 'Item 1', description: 'First item', detail: 'Additional details' },
243
+ { label: 'Item 2', description: 'Second item', picked: true },
244
+ { label: 'Item 3', description: 'Third item' },
245
+ ];
246
+
247
+ const selected = await vscode.window.showQuickPick(items, {
248
+ placeHolder: 'Select an item',
249
+ canPickMany: false,
250
+ matchOnDescription: true,
251
+ matchOnDetail: true,
252
+ });
253
+
254
+ if (selected) {
255
+ vscode.window.showInformationMessage(`Selected: ${selected.label}`);
256
+ }
257
+ ```
258
+
259
+ ### QuickPick with Async Loading
260
+
261
+ ```typescript
262
+ async function showAsyncQuickPick(): Promise<void> {
263
+ const qp = vscode.window.createQuickPick<vscode.QuickPickItem>();
264
+ qp.placeholder = 'Search items...';
265
+ qp.matchOnDescription = true;
266
+
267
+ // Debounced search
268
+ let searchTimer: ReturnType<typeof setTimeout> | undefined;
269
+ const controller = new AbortController();
270
+
271
+ qp.onDidChangeValue((value) => {
272
+ if (searchTimer) {
273
+ clearTimeout(searchTimer);
274
+ }
275
+ searchTimer = setTimeout(async () => {
276
+ qp.busy = true;
277
+ try {
278
+ const results = await fetchItems(value, controller.signal);
279
+ qp.items = results.map((r) => ({
280
+ label: r.name,
281
+ description: r.description,
282
+ }));
283
+ } catch (err) {
284
+ if (err instanceof Error && err.name !== 'AbortError') {
285
+ vscode.window.showErrorMessage(`Search failed: ${err.message}`);
286
+ }
287
+ } finally {
288
+ qp.busy = false;
289
+ }
290
+ }, 300);
291
+ });
292
+
293
+ qp.onDidAccept(() => {
294
+ const selected = qp.selectedItems[0];
295
+ if (selected) {
296
+ vscode.window.showInformationMessage(`Selected: ${selected.label}`);
297
+ }
298
+ qp.dispose();
299
+ });
300
+
301
+ qp.onDidHide(() => {
302
+ controller.abort();
303
+ qp.dispose();
304
+ });
305
+
306
+ qp.show();
307
+ }
308
+ ```
309
+
310
+ ## StatusBarItem
311
+
312
+ Persistent info in the status bar with click action.
313
+
314
+ ```typescript
315
+ export function activate(context: vscode.ExtensionContext) {
316
+ const statusBar = vscode.window.createStatusBarItem(
317
+ vscode.StatusBarAlignment.Right,
318
+ 100, // priority — higher = more to the left
319
+ );
320
+
321
+ statusBar.text = '$(sync) My Extension';
322
+ statusBar.tooltip = 'Click to refresh';
323
+ statusBar.command = 'myExt.refresh';
324
+ statusBar.backgroundColor = undefined; // use new vscode.ThemeColor('statusBarItem.warningBackground') for warnings
325
+ statusBar.show();
326
+
327
+ context.subscriptions.push(statusBar);
328
+
329
+ // Update dynamically
330
+ function updateStatus(count: number): void {
331
+ statusBar.text = `$(check) ${count} items`;
332
+ statusBar.tooltip = `${count} items synced`;
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### Codicon Icons in StatusBar
338
+
339
+ Use `$(icon-name)` syntax. Common icons:
340
+ - `$(sync~spin)` — spinning sync (loading)
341
+ - `$(check)` — checkmark
342
+ - `$(warning)` — warning triangle
343
+ - `$(error)` — error circle
344
+ - `$(info)` — info circle
345
+ - `$(cloud-upload)` — upload
346
+ - Full list: https://code.visualstudio.com/api/references/icons-in-labels
347
+
348
+ ## SecretStorage
349
+
350
+ Secure credential storage using the OS keychain.
351
+
352
+ ```typescript
353
+ class CredentialManager {
354
+ private static readonly TOKEN_KEY = 'myExt.apiToken';
355
+
356
+ constructor(private readonly secrets: vscode.SecretStorage) {}
357
+
358
+ async getToken(): Promise<string | undefined> {
359
+ return this.secrets.get(CredentialManager.TOKEN_KEY);
360
+ }
361
+
362
+ async setToken(token: string): Promise<void> {
363
+ await this.secrets.store(CredentialManager.TOKEN_KEY, token);
364
+ }
365
+
366
+ async deleteToken(): Promise<void> {
367
+ await this.secrets.delete(CredentialManager.TOKEN_KEY);
368
+ }
369
+
370
+ onDidChange(callback: (e: vscode.SecretStorageChangeEvent) => void): vscode.Disposable {
371
+ return this.secrets.onDidChange(callback);
372
+ }
373
+ }
374
+
375
+ // In activate():
376
+ export function activate(context: vscode.ExtensionContext) {
377
+ const credentials = new CredentialManager(context.secrets);
378
+
379
+ context.subscriptions.push(
380
+ vscode.commands.registerCommand('myExt.login', async () => {
381
+ const token = await vscode.window.showInputBox({
382
+ prompt: 'Enter your API token',
383
+ password: true,
384
+ ignoreFocusOut: true,
385
+ });
386
+ if (token) {
387
+ await credentials.setToken(token);
388
+ vscode.window.showInformationMessage('Token saved securely.');
389
+ }
390
+ }),
391
+
392
+ vscode.commands.registerCommand('myExt.logout', async () => {
393
+ await credentials.deleteToken();
394
+ vscode.window.showInformationMessage('Token removed.');
395
+ }),
396
+
397
+ credentials.onDidChange((e) => {
398
+ if (e.key === 'myExt.apiToken') {
399
+ // React to credential change
400
+ }
401
+ }),
402
+ );
403
+ }
404
+ ```
405
+
406
+ ## withProgress
407
+
408
+ Show progress for long-running operations.
409
+
410
+ ### Notification Progress
411
+
412
+ ```typescript
413
+ async function longRunningTask(): Promise<void> {
414
+ await vscode.window.withProgress(
415
+ {
416
+ location: vscode.ProgressLocation.Notification,
417
+ title: 'Processing items',
418
+ cancellable: true,
419
+ },
420
+ async (progress, token) => {
421
+ const items = await getItems();
422
+ const total = items.length;
423
+
424
+ for (let i = 0; i < total; i++) {
425
+ // Check for cancellation
426
+ if (token.isCancellationRequested) {
427
+ vscode.window.showWarningMessage('Operation cancelled.');
428
+ return;
429
+ }
430
+
431
+ progress.report({
432
+ increment: 100 / total,
433
+ message: `(${i + 1}/${total}) ${items[i].name}`,
434
+ });
435
+
436
+ await processItem(items[i]);
437
+ }
438
+
439
+ vscode.window.showInformationMessage(`Processed ${total} items.`);
440
+ },
441
+ );
442
+ }
443
+ ```
444
+
445
+ ### Status Bar Progress
446
+
447
+ ```typescript
448
+ await vscode.window.withProgress(
449
+ {
450
+ location: vscode.ProgressLocation.Window,
451
+ title: 'Indexing files...',
452
+ },
453
+ async (progress) => {
454
+ progress.report({ message: 'scanning...' });
455
+ await scanFiles();
456
+ progress.report({ message: 'building index...' });
457
+ await buildIndex();
458
+ },
459
+ );
460
+ ```
461
+
462
+ ## FileSystemWatcher
463
+
464
+ React to file changes in the workspace.
465
+
466
+ ```typescript
467
+ export function activate(context: vscode.ExtensionContext) {
468
+ const watcher = vscode.workspace.createFileSystemWatcher(
469
+ '**/*.json', // glob pattern
470
+ false, // ignoreCreateEvents
471
+ false, // ignoreChangeEvents
472
+ false, // ignoreDeleteEvents
473
+ );
474
+
475
+ context.subscriptions.push(
476
+ watcher,
477
+ watcher.onDidCreate((uri) => {
478
+ console.log(`Created: ${uri.fsPath}`);
479
+ }),
480
+ watcher.onDidChange((uri) => {
481
+ console.log(`Changed: ${uri.fsPath}`);
482
+ }),
483
+ watcher.onDidDelete((uri) => {
484
+ console.log(`Deleted: ${uri.fsPath}`);
485
+ }),
486
+ );
487
+ }
488
+ ```
489
+
490
+ ## Disposable Cleanup Pattern
491
+
492
+ The standard pattern for managing extension lifecycle.
493
+
494
+ ```typescript
495
+ import * as vscode from 'vscode';
496
+
497
+ let outputChannel: vscode.OutputChannel | undefined;
498
+
499
+ export function activate(context: vscode.ExtensionContext) {
500
+ // Output channel for logging
501
+ outputChannel = vscode.window.createOutputChannel('My Extension');
502
+ context.subscriptions.push(outputChannel);
503
+
504
+ // All registrations go into context.subscriptions
505
+ context.subscriptions.push(
506
+ vscode.commands.registerCommand('myExt.run', run),
507
+ vscode.workspace.onDidSaveTextDocument(onDocSaved),
508
+ vscode.window.onDidChangeActiveTextEditor(onEditorChanged),
509
+ );
510
+
511
+ outputChannel.appendLine('Extension activated');
512
+ }
513
+
514
+ export function deactivate(): void {
515
+ // Only needed for async cleanup like:
516
+ // - Closing network connections
517
+ // - Stopping child processes
518
+ // - Flushing buffers
519
+ // Disposables in context.subscriptions are auto-disposed.
520
+ }
521
+
522
+ function run(): void {
523
+ outputChannel?.appendLine('Command executed');
524
+ }
525
+
526
+ function onDocSaved(doc: vscode.TextDocument): void {
527
+ outputChannel?.appendLine(`Saved: ${doc.fileName}`);
528
+ }
529
+
530
+ function onEditorChanged(editor: vscode.TextEditor | undefined): void {
531
+ outputChannel?.appendLine(`Active editor: ${editor?.document.fileName ?? 'none'}`);
532
+ }
533
+ ```
534
+
535
+ ## Diagnostic Collection
536
+
537
+ Report problems (errors, warnings) in the Problems panel.
538
+
539
+ ```typescript
540
+ const diagnostics = vscode.languages.createDiagnosticCollection('myExt');
541
+ context.subscriptions.push(diagnostics);
542
+
543
+ function validateDocument(doc: vscode.TextDocument): void {
544
+ const issues: vscode.Diagnostic[] = [];
545
+
546
+ for (let i = 0; i < doc.lineCount; i++) {
547
+ const line = doc.lineAt(i);
548
+ if (line.text.includes('TODO')) {
549
+ issues.push(
550
+ new vscode.Diagnostic(
551
+ line.range,
552
+ 'TODO comment found',
553
+ vscode.DiagnosticSeverity.Warning,
554
+ ),
555
+ );
556
+ }
557
+ }
558
+
559
+ diagnostics.set(doc.uri, issues);
560
+ }
561
+ ```
562
+
563
+ ## Output Channel and Logging
564
+
565
+ ```typescript
566
+ // Simple output channel
567
+ const output = vscode.window.createOutputChannel('My Extension');
568
+ output.appendLine('Info message');
569
+ output.show(true); // true = preserve focus
570
+
571
+ // Log output channel (structured, with log levels — VS Code 1.74+)
572
+ const log = vscode.window.createOutputChannel('My Extension', { log: true });
573
+ log.info('Started');
574
+ log.warn('Something looks off');
575
+ log.error('Something failed', new Error('details'));
576
+ log.debug('Debug data', { key: 'value' });
577
+ ```
578
+
579
+ ## Context Keys (When Clauses)
580
+
581
+ Set custom context keys to control menu/command visibility.
582
+
583
+ ```typescript
584
+ // Set a context key
585
+ vscode.commands.executeCommand('setContext', 'myExt.isConnected', true);
586
+
587
+ // Use in package.json when clauses:
588
+ // "when": "myExt.isConnected"
589
+ // "when": "myExt.isConnected && editorLangId == typescript"
590
+
591
+ // Clear it
592
+ vscode.commands.executeCommand('setContext', 'myExt.isConnected', false);
593
+ ```
594
+
595
+ ## TextDocumentContentProvider
596
+
597
+ Provide virtual read-only documents.
598
+
599
+ ```typescript
600
+ class MyContentProvider implements vscode.TextDocumentContentProvider {
601
+ private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
602
+ readonly onDidChange = this._onDidChange.event;
603
+
604
+ provideTextDocumentContent(uri: vscode.Uri): string {
605
+ const query = new URLSearchParams(uri.query);
606
+ const id = query.get('id') ?? 'unknown';
607
+ return `Content for: ${id}\nGenerated at: ${new Date().toISOString()}`;
608
+ }
609
+
610
+ refresh(uri: vscode.Uri): void {
611
+ this._onDidChange.fire(uri);
612
+ }
613
+ }
614
+
615
+ // Register
616
+ const provider = new MyContentProvider();
617
+ context.subscriptions.push(
618
+ vscode.workspace.registerTextDocumentContentProvider('myScheme', provider),
619
+ );
620
+
621
+ // Open a virtual document
622
+ const uri = vscode.Uri.parse('myScheme:item?id=123');
623
+ const doc = await vscode.workspace.openTextDocument(uri);
624
+ await vscode.window.showTextDocument(doc);
625
+ ```