@redaksjon/protokoll 0.0.11 → 0.0.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.
- package/.cursor/rules/definition-of-done.md +1 -0
- package/.cursor/rules/no-emoticons.md +26 -12
- package/README.md +483 -69
- package/dist/agentic/executor.js +473 -41
- package/dist/agentic/executor.js.map +1 -1
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/tools/lookup-person.js +123 -4
- package/dist/agentic/tools/lookup-person.js.map +1 -1
- package/dist/agentic/tools/lookup-project.js +139 -22
- package/dist/agentic/tools/lookup-project.js.map +1 -1
- package/dist/agentic/tools/route-note.js +5 -1
- package/dist/agentic/tools/route-note.js.map +1 -1
- package/dist/arguments.js +6 -3
- package/dist/arguments.js.map +1 -1
- package/dist/cli/action.js +704 -0
- package/dist/cli/action.js.map +1 -0
- package/dist/cli/config.js +482 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.js +466 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/feedback.js +858 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/index.js +103 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.js +572 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/transcript.js +199 -0
- package/dist/cli/transcript.js.map +1 -0
- package/dist/constants.js +12 -5
- package/dist/constants.js.map +1 -1
- package/dist/context/discovery.js +1 -1
- package/dist/context/discovery.js.map +1 -1
- package/dist/context/index.js +25 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/storage.js +57 -4
- package/dist/context/storage.js.map +1 -1
- package/dist/interactive/handler.js +310 -9
- package/dist/interactive/handler.js.map +1 -1
- package/dist/main.js +11 -1
- package/dist/main.js.map +1 -1
- package/dist/output/index.js.map +1 -1
- package/dist/output/manager.js +47 -2
- package/dist/output/manager.js.map +1 -1
- package/dist/phases/complete.js +38 -3
- package/dist/phases/complete.js.map +1 -1
- package/dist/phases/locate.js +1 -1
- package/dist/phases/locate.js.map +1 -1
- package/dist/pipeline/orchestrator.js +104 -31
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/protokoll.js +68 -2
- package/dist/protokoll.js.map +1 -1
- package/dist/reasoning/client.js +83 -0
- package/dist/reasoning/client.js.map +1 -1
- package/dist/reasoning/index.js +1 -0
- package/dist/reasoning/index.js.map +1 -1
- package/dist/routing/router.js +2 -2
- package/dist/routing/router.js.map +1 -1
- package/dist/util/media.js +1 -1
- package/dist/util/media.js.map +1 -1
- package/dist/util/metadata.js.map +1 -1
- package/dist/util/sound.js +116 -0
- package/dist/util/sound.js.map +1 -0
- package/dist/util/storage.js +3 -3
- package/dist/util/storage.js.map +1 -1
- package/docs/duplicate-question-prevention.md +117 -0
- package/docs/examples.md +152 -0
- package/docs/interactive-context-example.md +92 -0
- package/docs/package-lock.json +6 -0
- package/docs/package.json +3 -1
- package/eslint.config.mjs +1 -1
- package/guide/action.md +375 -0
- package/guide/config.md +207 -0
- package/guide/configuration.md +82 -67
- package/guide/context-commands.md +574 -0
- package/guide/context-system.md +20 -7
- package/guide/development.md +106 -4
- package/guide/feedback.md +335 -0
- package/guide/index.md +100 -4
- package/guide/interactive.md +15 -14
- package/guide/quickstart.md +21 -7
- package/guide/reasoning.md +18 -4
- package/guide/routing.md +192 -97
- package/package.json +2 -3
- package/scripts/copy-assets.mjs +47 -0
- package/scripts/coverage-priority.mjs +323 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +6 -13
- package/vitest.config.ts +5 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { getLogger } from '../logging.js';
|
|
3
|
+
|
|
4
|
+
// macOS system sounds that work well for notifications
|
|
5
|
+
const MACOS_NOTIFICATION_SOUNDS = [
|
|
6
|
+
'/System/Library/Sounds/Glass.aiff',
|
|
7
|
+
'/System/Library/Sounds/Ping.aiff',
|
|
8
|
+
'/System/Library/Sounds/Pop.aiff',
|
|
9
|
+
'/System/Library/Sounds/Tink.aiff'
|
|
10
|
+
];
|
|
11
|
+
// Default sound to use (Glass is similar to Cursor's notification)
|
|
12
|
+
const DEFAULT_MACOS_SOUND = MACOS_NOTIFICATION_SOUNDS[0];
|
|
13
|
+
/**
|
|
14
|
+
* Play a sound file using afplay (macOS)
|
|
15
|
+
*/ const playWithAfplay = (soundPath)=>{
|
|
16
|
+
return new Promise((resolve)=>{
|
|
17
|
+
const afplay = spawn('afplay', [
|
|
18
|
+
soundPath
|
|
19
|
+
], {
|
|
20
|
+
stdio: 'ignore',
|
|
21
|
+
detached: true
|
|
22
|
+
});
|
|
23
|
+
afplay.on('error', ()=>{
|
|
24
|
+
resolve(false);
|
|
25
|
+
});
|
|
26
|
+
afplay.on('close', (code)=>{
|
|
27
|
+
resolve(code === 0);
|
|
28
|
+
});
|
|
29
|
+
// Don't wait for the sound to finish - just fire and forget
|
|
30
|
+
afplay.unref();
|
|
31
|
+
// Consider it successful if spawn didn't throw
|
|
32
|
+
setTimeout(()=>resolve(true), 50);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Play Windows system notification sound using PowerShell
|
|
37
|
+
* Uses SystemSounds.Asterisk which is a pleasant notification tone
|
|
38
|
+
*/ const playWithPowerShell = ()=>{
|
|
39
|
+
return new Promise((resolve)=>{
|
|
40
|
+
// Use PowerShell to access .NET System.Media.SystemSounds
|
|
41
|
+
// Asterisk is a pleasant notification sound, similar to macOS Glass
|
|
42
|
+
const ps = spawn('powershell', [
|
|
43
|
+
'-NoProfile',
|
|
44
|
+
'-NonInteractive',
|
|
45
|
+
'-Command',
|
|
46
|
+
'[System.Media.SystemSounds]::Asterisk.Play()'
|
|
47
|
+
], {
|
|
48
|
+
stdio: 'ignore',
|
|
49
|
+
detached: true,
|
|
50
|
+
// On Windows, use shell to ensure PowerShell is found
|
|
51
|
+
shell: true
|
|
52
|
+
});
|
|
53
|
+
ps.on('error', ()=>{
|
|
54
|
+
resolve(false);
|
|
55
|
+
});
|
|
56
|
+
ps.on('close', (code)=>{
|
|
57
|
+
resolve(code === 0);
|
|
58
|
+
});
|
|
59
|
+
ps.unref();
|
|
60
|
+
// Consider it successful if spawn didn't throw
|
|
61
|
+
setTimeout(()=>resolve(true), 50);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Play terminal bell as fallback for Linux and other platforms
|
|
66
|
+
*/ const playTerminalBell = ()=>{
|
|
67
|
+
// Write ASCII bell character to stdout
|
|
68
|
+
process.stdout.write('\x07');
|
|
69
|
+
};
|
|
70
|
+
const create = (config)=>{
|
|
71
|
+
const logger = getLogger();
|
|
72
|
+
const playNotification = async ()=>{
|
|
73
|
+
if (config.silent) {
|
|
74
|
+
logger.debug('Sound notification skipped (silent mode)');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// macOS: use afplay with system sounds
|
|
79
|
+
if (process.platform === 'darwin') {
|
|
80
|
+
const success = await playWithAfplay(DEFAULT_MACOS_SOUND);
|
|
81
|
+
if (success) {
|
|
82
|
+
logger.debug('Played notification sound: %s', DEFAULT_MACOS_SOUND);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Windows: use PowerShell to play system sound
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
const success = await playWithPowerShell();
|
|
89
|
+
if (success) {
|
|
90
|
+
logger.debug('Played Windows notification sound via PowerShell');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Linux and others: fall back to terminal bell
|
|
95
|
+
playTerminalBell();
|
|
96
|
+
logger.debug('Played terminal bell notification');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Sound failures should never interrupt the workflow
|
|
99
|
+
logger.debug('Failed to play notification sound: %s', error);
|
|
100
|
+
// Try terminal bell as last resort
|
|
101
|
+
try {
|
|
102
|
+
playTerminalBell();
|
|
103
|
+
} catch {
|
|
104
|
+
// Silently ignore - sound is not critical
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const isEnabled = ()=>!config.silent;
|
|
109
|
+
return {
|
|
110
|
+
playNotification,
|
|
111
|
+
isEnabled
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export { create };
|
|
116
|
+
//# sourceMappingURL=sound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sound.js","sources":["../../src/util/sound.ts"],"sourcesContent":["/**\n * Sound Notification Utility\n * \n * Plays system sounds to notify the user when interactive input is needed.\n * Similar to Cursor's notification behavior.\n * \n * Platform support:\n * - macOS: Uses afplay with system sounds (Glass.aiff)\n * - Windows: Uses PowerShell to play system notification sound\n * - Linux/Other: Falls back to terminal bell\n */\n\nimport { spawn } from 'child_process';\nimport * as Logging from '../logging';\n\nexport interface SoundConfig {\n /** Whether sounds are disabled (silent mode) */\n silent: boolean;\n}\n\nexport interface SoundInstance {\n /** Play a notification sound to get user's attention */\n playNotification(): Promise<void>;\n /** Check if sounds are enabled */\n isEnabled(): boolean;\n}\n\n// macOS system sounds that work well for notifications\nconst MACOS_NOTIFICATION_SOUNDS = [\n '/System/Library/Sounds/Glass.aiff',\n '/System/Library/Sounds/Ping.aiff', \n '/System/Library/Sounds/Pop.aiff',\n '/System/Library/Sounds/Tink.aiff',\n];\n\n// Default sound to use (Glass is similar to Cursor's notification)\nconst DEFAULT_MACOS_SOUND = MACOS_NOTIFICATION_SOUNDS[0];\n\n/**\n * Play a sound file using afplay (macOS)\n */\nconst playWithAfplay = (soundPath: string): Promise<boolean> => {\n return new Promise((resolve) => {\n const afplay = spawn('afplay', [soundPath], {\n stdio: 'ignore',\n detached: true,\n });\n\n afplay.on('error', () => {\n resolve(false);\n });\n\n afplay.on('close', (code) => {\n resolve(code === 0);\n });\n\n // Don't wait for the sound to finish - just fire and forget\n afplay.unref();\n \n // Consider it successful if spawn didn't throw\n setTimeout(() => resolve(true), 50);\n });\n};\n\n/**\n * Play Windows system notification sound using PowerShell\n * Uses SystemSounds.Asterisk which is a pleasant notification tone\n */\nconst playWithPowerShell = (): Promise<boolean> => {\n return new Promise((resolve) => {\n // Use PowerShell to access .NET System.Media.SystemSounds\n // Asterisk is a pleasant notification sound, similar to macOS Glass\n const ps = spawn('powershell', [\n '-NoProfile',\n '-NonInteractive', \n '-Command',\n '[System.Media.SystemSounds]::Asterisk.Play()'\n ], {\n stdio: 'ignore',\n detached: true,\n // On Windows, use shell to ensure PowerShell is found\n shell: true,\n });\n\n ps.on('error', () => {\n resolve(false);\n });\n\n ps.on('close', (code) => {\n resolve(code === 0);\n });\n\n ps.unref();\n \n // Consider it successful if spawn didn't throw\n setTimeout(() => resolve(true), 50);\n });\n};\n\n/**\n * Play terminal bell as fallback for Linux and other platforms\n */\nconst playTerminalBell = (): void => {\n // Write ASCII bell character to stdout\n process.stdout.write('\\x07');\n};\n\nexport const create = (config: SoundConfig): SoundInstance => {\n const logger = Logging.getLogger();\n \n const playNotification = async (): Promise<void> => {\n if (config.silent) {\n logger.debug('Sound notification skipped (silent mode)');\n return;\n }\n\n try {\n // macOS: use afplay with system sounds\n if (process.platform === 'darwin') {\n const success = await playWithAfplay(DEFAULT_MACOS_SOUND);\n if (success) {\n logger.debug('Played notification sound: %s', DEFAULT_MACOS_SOUND);\n return;\n }\n }\n \n // Windows: use PowerShell to play system sound\n if (process.platform === 'win32') {\n const success = await playWithPowerShell();\n if (success) {\n logger.debug('Played Windows notification sound via PowerShell');\n return;\n }\n }\n \n // Linux and others: fall back to terminal bell\n playTerminalBell();\n logger.debug('Played terminal bell notification');\n } catch (error) {\n // Sound failures should never interrupt the workflow\n logger.debug('Failed to play notification sound: %s', error);\n // Try terminal bell as last resort\n try {\n playTerminalBell();\n } catch {\n // Silently ignore - sound is not critical\n }\n }\n };\n\n const isEnabled = (): boolean => !config.silent;\n\n return {\n playNotification,\n isEnabled,\n };\n};\n\n"],"names":["MACOS_NOTIFICATION_SOUNDS","DEFAULT_MACOS_SOUND","playWithAfplay","soundPath","Promise","resolve","afplay","spawn","stdio","detached","on","code","unref","setTimeout","playWithPowerShell","ps","shell","playTerminalBell","process","stdout","write","create","config","logger","Logging","playNotification","silent","debug","platform","success","error","isEnabled"],"mappings":"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"}
|
package/dist/util/storage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
2
|
import { glob } from 'glob';
|
|
3
|
-
import path__default from 'path';
|
|
4
|
-
import crypto from 'crypto';
|
|
3
|
+
import path__default from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
5
|
|
|
6
6
|
// eslint-disable-next-line no-restricted-imports
|
|
7
7
|
const create = (params)=>{
|
package/dist/util/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sources":["../../src/util/storage.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport * as fs from 'fs';\nimport { glob } from 'glob';\nimport path from 'path';\nimport crypto from 'crypto';\n/**\n * This module exists to isolate filesystem operations from the rest of the codebase.\n * This makes testing easier by avoiding direct fs mocking in jest configuration.\n * \n * Additionally, abstracting storage operations allows for future flexibility - \n * this export utility may need to work with storage systems other than the local filesystem\n * (e.g. S3, Google Cloud Storage, etc).\n */\n\nexport interface Utility {\n exists: (path: string) => Promise<boolean>;\n isDirectory: (path: string) => Promise<boolean>;\n isFile: (path: string) => Promise<boolean>;\n isReadable: (path: string) => Promise<boolean>;\n isWritable: (path: string) => Promise<boolean>;\n isFileReadable: (path: string) => Promise<boolean>;\n isDirectoryWritable: (path: string) => Promise<boolean>;\n isDirectoryReadable: (path: string) => Promise<boolean>;\n createDirectory: (path: string) => Promise<void>;\n readFile: (path: string, encoding: string) => Promise<string>;\n readStream: (path: string) => Promise<fs.ReadStream>;\n writeFile: (path: string, data: string | Buffer, encoding: string) => Promise<void>;\n forEachFileIn: (directory: string, callback: (path: string) => Promise<void>, options?: { pattern: string }) => Promise<void>;\n hashFile: (path: string, length: number) => Promise<string>;\n listFiles: (directory: string) => Promise<string[]>;\n deleteFile: (path: string) => Promise<void>;\n getFileSize: (path: string) => Promise<number>;\n}\n\nexport const create = (params: { log?: (message: string, ...args: any[]) => void }): Utility => {\n\n // eslint-disable-next-line no-console\n const log = params.log || console.log;\n\n const exists = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.stat(path);\n return true;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error: any) {\n return false;\n }\n }\n\n const isDirectory = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isDirectory()) {\n log(`${path} is not a directory`);\n return false;\n }\n return true;\n }\n\n const isFile = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isFile()) {\n log(`${path} is not a file`);\n return false;\n }\n return true;\n }\n\n const isReadable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.R_OK);\n } catch (error: any) {\n log(`${path} is not readable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isWritable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.W_OK);\n } catch (error: any) {\n log(`${path} is not writable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isFileReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isFile(path) && await isReadable(path);\n }\n\n const isDirectoryWritable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isWritable(path);\n }\n\n const isDirectoryReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isReadable(path);\n }\n\n const createDirectory = async (path: string): Promise<void> => {\n try {\n await fs.promises.mkdir(path, { recursive: true });\n } catch (mkdirError: any) {\n throw new Error(`Failed to create output directory ${path}: ${mkdirError.message} ${mkdirError.stack}`);\n }\n }\n\n const readFile = async (path: string, encoding: string): Promise<string> => {\n return await fs.promises.readFile(path, { encoding: encoding as BufferEncoding });\n }\n\n const writeFile = async (path: string, data: string | Buffer, encoding: string): Promise<void> => {\n await fs.promises.writeFile(path, data, { encoding: encoding as BufferEncoding });\n }\n\n const forEachFileIn = async (directory: string, callback: (file: string) => Promise<void>, options: { pattern: string | string[] } = { pattern: '*.*' }): Promise<void> => {\n try {\n const files = await glob(options.pattern, { cwd: directory, nodir: true });\n for (const file of files) {\n await callback(path.join(directory, file));\n }\n } catch (err: any) {\n throw new Error(`Failed to glob pattern ${options.pattern} in ${directory}: ${err.message}`);\n }\n }\n\n const readStream = async (path: string): Promise<fs.ReadStream> => {\n return fs.createReadStream(path);\n }\n\n const hashFile = async (path: string, length: number): Promise<string> => {\n const file = await readFile(path, 'utf8');\n return crypto.createHash('sha256').update(file).digest('hex').slice(0, length);\n }\n\n const listFiles = async (directory: string): Promise<string[]> => {\n return await fs.promises.readdir(directory);\n }\n\n const deleteFile = async (path: string): Promise<void> => {\n await fs.promises.unlink(path);\n }\n\n const getFileSize = async (path: string): Promise<number> => {\n const stats = await fs.promises.stat(path);\n return stats.size;\n }\n\n return {\n exists,\n isDirectory,\n isFile,\n isReadable,\n isWritable,\n isFileReadable,\n isDirectoryWritable,\n isDirectoryReadable,\n createDirectory,\n readFile,\n readStream,\n writeFile,\n forEachFileIn,\n hashFile,\n listFiles,\n deleteFile,\n getFileSize,\n };\n}"],"names":["create","params","log","console","exists","path","fs","promises","stat","error","isDirectory","stats","isFile","isReadable","access","constants","R_OK","message","stack","isWritable","W_OK","isFileReadable","isDirectoryWritable","isDirectoryReadable","createDirectory","mkdir","recursive","mkdirError","Error","readFile","encoding","writeFile","data","forEachFileIn","directory","callback","options","pattern","files","glob","cwd","nodir","file","join","err","readStream","createReadStream","hashFile","length","crypto","createHash","update","digest","slice","listFiles","readdir","deleteFile","unlink","getFileSize","size"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.js","sources":["../../src/util/storage.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport * as fs from 'node:fs';\nimport { glob } from 'glob';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\n/**\n * This module exists to isolate filesystem operations from the rest of the codebase.\n * This makes testing easier by avoiding direct fs mocking in jest configuration.\n * \n * Additionally, abstracting storage operations allows for future flexibility - \n * this export utility may need to work with storage systems other than the local filesystem\n * (e.g. S3, Google Cloud Storage, etc).\n */\n\nexport interface Utility {\n exists: (path: string) => Promise<boolean>;\n isDirectory: (path: string) => Promise<boolean>;\n isFile: (path: string) => Promise<boolean>;\n isReadable: (path: string) => Promise<boolean>;\n isWritable: (path: string) => Promise<boolean>;\n isFileReadable: (path: string) => Promise<boolean>;\n isDirectoryWritable: (path: string) => Promise<boolean>;\n isDirectoryReadable: (path: string) => Promise<boolean>;\n createDirectory: (path: string) => Promise<void>;\n readFile: (path: string, encoding: string) => Promise<string>;\n readStream: (path: string) => Promise<fs.ReadStream>;\n writeFile: (path: string, data: string | Buffer, encoding: string) => Promise<void>;\n forEachFileIn: (directory: string, callback: (path: string) => Promise<void>, options?: { pattern: string }) => Promise<void>;\n hashFile: (path: string, length: number) => Promise<string>;\n listFiles: (directory: string) => Promise<string[]>;\n deleteFile: (path: string) => Promise<void>;\n getFileSize: (path: string) => Promise<number>;\n}\n\nexport const create = (params: { log?: (message: string, ...args: any[]) => void }): Utility => {\n\n // eslint-disable-next-line no-console\n const log = params.log || console.log;\n\n const exists = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.stat(path);\n return true;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error: any) {\n return false;\n }\n }\n\n const isDirectory = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isDirectory()) {\n log(`${path} is not a directory`);\n return false;\n }\n return true;\n }\n\n const isFile = async (path: string): Promise<boolean> => {\n const stats = await fs.promises.stat(path);\n if (!stats.isFile()) {\n log(`${path} is not a file`);\n return false;\n }\n return true;\n }\n\n const isReadable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.R_OK);\n } catch (error: any) {\n log(`${path} is not readable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isWritable = async (path: string): Promise<boolean> => {\n try {\n await fs.promises.access(path, fs.constants.W_OK);\n } catch (error: any) {\n log(`${path} is not writable: %s %s`, error.message, error.stack);\n return false;\n }\n return true;\n }\n\n const isFileReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isFile(path) && await isReadable(path);\n }\n\n const isDirectoryWritable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isWritable(path);\n }\n\n const isDirectoryReadable = async (path: string): Promise<boolean> => {\n return await exists(path) && await isDirectory(path) && await isReadable(path);\n }\n\n const createDirectory = async (path: string): Promise<void> => {\n try {\n await fs.promises.mkdir(path, { recursive: true });\n } catch (mkdirError: any) {\n throw new Error(`Failed to create output directory ${path}: ${mkdirError.message} ${mkdirError.stack}`);\n }\n }\n\n const readFile = async (path: string, encoding: string): Promise<string> => {\n return await fs.promises.readFile(path, { encoding: encoding as BufferEncoding });\n }\n\n const writeFile = async (path: string, data: string | Buffer, encoding: string): Promise<void> => {\n await fs.promises.writeFile(path, data, { encoding: encoding as BufferEncoding });\n }\n\n const forEachFileIn = async (directory: string, callback: (file: string) => Promise<void>, options: { pattern: string | string[] } = { pattern: '*.*' }): Promise<void> => {\n try {\n const files = await glob(options.pattern, { cwd: directory, nodir: true });\n for (const file of files) {\n await callback(path.join(directory, file));\n }\n } catch (err: any) {\n throw new Error(`Failed to glob pattern ${options.pattern} in ${directory}: ${err.message}`);\n }\n }\n\n const readStream = async (path: string): Promise<fs.ReadStream> => {\n return fs.createReadStream(path);\n }\n\n const hashFile = async (path: string, length: number): Promise<string> => {\n const file = await readFile(path, 'utf8');\n return crypto.createHash('sha256').update(file).digest('hex').slice(0, length);\n }\n\n const listFiles = async (directory: string): Promise<string[]> => {\n return await fs.promises.readdir(directory);\n }\n\n const deleteFile = async (path: string): Promise<void> => {\n await fs.promises.unlink(path);\n }\n\n const getFileSize = async (path: string): Promise<number> => {\n const stats = await fs.promises.stat(path);\n return stats.size;\n }\n\n return {\n exists,\n isDirectory,\n isFile,\n isReadable,\n isWritable,\n isFileReadable,\n isDirectoryWritable,\n isDirectoryReadable,\n createDirectory,\n readFile,\n readStream,\n writeFile,\n forEachFileIn,\n hashFile,\n listFiles,\n deleteFile,\n getFileSize,\n };\n}"],"names":["create","params","log","console","exists","path","fs","promises","stat","error","isDirectory","stats","isFile","isReadable","access","constants","R_OK","message","stack","isWritable","W_OK","isFileReadable","isDirectoryWritable","isDirectoryReadable","createDirectory","mkdir","recursive","mkdirError","Error","readFile","encoding","writeFile","data","forEachFileIn","directory","callback","options","pattern","files","glob","cwd","nodir","file","join","err","readStream","createReadStream","hashFile","length","crypto","createHash","update","digest","slice","listFiles","readdir","deleteFile","unlink","getFileSize","size"],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Duplicate Question Prevention
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
When processing long transcripts in interactive mode, protokoll was asking the same questions repeatedly about people and projects that had already been resolved during the same session.
|
|
5
|
+
|
|
6
|
+
## Root Cause
|
|
7
|
+
The lookup tools (`lookup_person`, `lookup_project`) were only checking the context files on disk, not the in-memory state of entities that were just resolved. When a user answered a question about "John Doe" early in the transcript, and the same name appeared again later, the tool would ask about it again because:
|
|
8
|
+
|
|
9
|
+
1. The newly created entity was saved to disk but not immediately available in subsequent searches
|
|
10
|
+
2. The tools didn't check if the entity was already resolved during this processing session
|
|
11
|
+
|
|
12
|
+
## Solution
|
|
13
|
+
|
|
14
|
+
### 1. Resolved Entities Tracking
|
|
15
|
+
Added `resolvedEntities` Map to the `ToolContext`:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
export interface ToolContext {
|
|
19
|
+
// ... existing fields
|
|
20
|
+
resolvedEntities?: Map<string, string>; // Entities resolved during this session
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This Map tracks all entity resolutions made during the current transcript processing session.
|
|
25
|
+
|
|
26
|
+
### 2. Check Before Prompting
|
|
27
|
+
Updated both `lookup_person` and `lookup_project` tools to check the resolved entities first:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// First, check if this person was already resolved in this session
|
|
31
|
+
if (ctx.resolvedEntities?.has(args.name)) {
|
|
32
|
+
const resolvedName = ctx.resolvedEntities.get(args.name);
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: {
|
|
36
|
+
found: true,
|
|
37
|
+
suggestion: `Already resolved: use "${resolvedName}"`,
|
|
38
|
+
cached: true,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Immediate Context Reload
|
|
45
|
+
After saving a new entity to disk, immediately reload the context:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
await ctx.contextInstance.saveEntity(newProject);
|
|
49
|
+
await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This ensures that subsequent tool calls will find the newly created entity even before checking the resolved entities cache.
|
|
53
|
+
|
|
54
|
+
## Benefits
|
|
55
|
+
|
|
56
|
+
**No Duplicate Questions:** Users are only asked about each person/project/term once per transcript
|
|
57
|
+
**Better UX:** Faster processing for long transcripts with repeated mentions
|
|
58
|
+
**Maintains State:** The resolved entities Map is shared across all tool executions in the session
|
|
59
|
+
**Backwards Compatible:** Works even if `resolvedEntities` is undefined (graceful fallback)
|
|
60
|
+
|
|
61
|
+
## Example
|
|
62
|
+
|
|
63
|
+
### Before (Annoying!)
|
|
64
|
+
```
|
|
65
|
+
Processing transcript with 20 mentions of "Trey Toulson"...
|
|
66
|
+
|
|
67
|
+
────────────────────────────────────────────────────────────
|
|
68
|
+
[Unknown Person Detected]
|
|
69
|
+
Name heard: "Trey Toulson"
|
|
70
|
+
...
|
|
71
|
+
────────────────────────────────────────────────────────────
|
|
72
|
+
Is the name spelled correctly? → User enters details
|
|
73
|
+
|
|
74
|
+
[... processing continues ...]
|
|
75
|
+
|
|
76
|
+
────────────────────────────────────────────────────────────
|
|
77
|
+
[Unknown Person Detected]
|
|
78
|
+
Name heard: "Trey Toulson" ← SAME PERSON AGAIN!
|
|
79
|
+
...
|
|
80
|
+
────────────────────────────────────────────────────────────
|
|
81
|
+
Is the name spelled correctly? → User asked AGAIN!
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### After (Fixed!)
|
|
85
|
+
```
|
|
86
|
+
Processing transcript with 20 mentions of "Trey Toulson"...
|
|
87
|
+
|
|
88
|
+
────────────────────────────────────────────────────────────
|
|
89
|
+
[Unknown Person Detected]
|
|
90
|
+
Name heard: "Trey Toulson"
|
|
91
|
+
...
|
|
92
|
+
────────────────────────────────────────────────────────────
|
|
93
|
+
Is the name spelled correctly? → User enters details
|
|
94
|
+
|
|
95
|
+
[... processing continues ...]
|
|
96
|
+
|
|
97
|
+
[Tool automatically uses cached resolution for "Trey Toulson"]
|
|
98
|
+
[No prompt - continues processing smoothly]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Implementation Details
|
|
102
|
+
|
|
103
|
+
### Files Modified
|
|
104
|
+
- `src/agentic/types.ts` - Added `resolvedEntities` to `ToolContext`
|
|
105
|
+
- `src/agentic/executor.ts` - Wire up resolved entities and reload context after saves
|
|
106
|
+
- `src/agentic/tools/lookup-person.ts` - Check cache before prompting
|
|
107
|
+
- `src/agentic/tools/lookup-project.ts` - Check cache before prompting
|
|
108
|
+
|
|
109
|
+
### Test Coverage
|
|
110
|
+
Added 8 new tests in `tests/agentic/resolved-entities.test.ts`:
|
|
111
|
+
- Test that first lookup prompts
|
|
112
|
+
- Test that second lookup returns cached result
|
|
113
|
+
- Test that multiple lookups of same entity don't prompt again
|
|
114
|
+
- Test cross-tool entity sharing
|
|
115
|
+
- Test graceful handling when resolvedEntities is undefined
|
|
116
|
+
|
|
117
|
+
All 580 tests passing.
|
package/docs/examples.md
CHANGED
|
@@ -222,3 +222,155 @@ cd ~/work/project-a
|
|
|
222
222
|
protokoll --input-directory ./recordings
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
+
## Scenario 11: Edit Transcript Title
|
|
226
|
+
|
|
227
|
+
Rename a transcript with a more meaningful title.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Change title (updates document heading and filename)
|
|
231
|
+
protokoll action --title "Q1 Budget Review Meeting" ~/notes/2026/01/15-1412-meeting.md
|
|
232
|
+
|
|
233
|
+
# Preview changes first
|
|
234
|
+
protokoll action --title "Q1 Budget Review" ~/notes/file.md --dry-run --verbose
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Result:
|
|
238
|
+
- Document heading changes to `# Q1 Budget Review Meeting`
|
|
239
|
+
- File renames to `15-1412-q1-budget-review-meeting.md`
|
|
240
|
+
|
|
241
|
+
## Scenario 12: Move Transcript to Different Project
|
|
242
|
+
|
|
243
|
+
Realize a transcript belongs to a different project.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Move to different project (updates metadata and routes to project destination)
|
|
247
|
+
protokoll action --project client-alpha ~/notes/2026/01/15-1412-meeting.md
|
|
248
|
+
|
|
249
|
+
# Change both title and project
|
|
250
|
+
protokoll action --title "Alpha Kickoff" --project client-alpha ~/notes/file.md
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Result:
|
|
254
|
+
- Metadata updated with new project name and ID
|
|
255
|
+
- File moved to project's configured destination
|
|
256
|
+
- Original file removed
|
|
257
|
+
|
|
258
|
+
## Scenario 13: Combine Multiple Transcripts
|
|
259
|
+
|
|
260
|
+
Merge several related transcripts from a long meeting.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Combine with a custom title
|
|
264
|
+
protokoll action --title "Full Team Standup" --combine "~/notes/2026/01/15-1412-part1.md
|
|
265
|
+
~/notes/2026/01/15-1421-part2.md
|
|
266
|
+
~/notes/2026/01/15-1435-part3.md"
|
|
267
|
+
|
|
268
|
+
# Combine and assign to project
|
|
269
|
+
protokoll action --title "Sprint 42 Planning" --project sprint-42 --combine "~/notes/misc1.md
|
|
270
|
+
~/notes/misc2.md"
|
|
271
|
+
|
|
272
|
+
# Preview what would happen
|
|
273
|
+
protokoll action --combine "~/notes/files..." --dry-run --verbose
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Result:
|
|
277
|
+
- Single combined transcript with custom title
|
|
278
|
+
- Sorted chronologically by timestamp
|
|
279
|
+
- Durations summed, tags deduplicated
|
|
280
|
+
- Source files automatically deleted
|
|
281
|
+
|
|
282
|
+
## Scenario 14: Reorganize Scattered Notes
|
|
283
|
+
|
|
284
|
+
Consolidate notes that were initially routed to the default location.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Find notes that mention a specific topic
|
|
288
|
+
ls ~/notes/2026/01/*standards*.md
|
|
289
|
+
|
|
290
|
+
# Combine them into a project
|
|
291
|
+
protokoll action --title "Fellow Standards Discussion" --project fellow-standards --combine "~/notes/2026/01/15-1412-ne-4th-st-0.md
|
|
292
|
+
~/notes/2026/01/15-1421-dimension-talk.md
|
|
293
|
+
~/notes/2026/01/15-1435-standards-continued.md"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Result:
|
|
297
|
+
- All related notes combined into one comprehensive document
|
|
298
|
+
- Routed to the `fellow-standards` project destination
|
|
299
|
+
- Original scattered files cleaned up
|
|
300
|
+
|
|
301
|
+
## Scenario 15: Fix a Misheard Term
|
|
302
|
+
|
|
303
|
+
A technical term was transcribed incorrectly.
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# The transcript has "WCMP" but should be "WCNP"
|
|
307
|
+
protokoll feedback ~/notes/2026/01/15-1412-meeting.md \
|
|
308
|
+
-f "Everywhere it says WCMP, that should be WCNP - Walmart's Native Cloud Platform"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Result:
|
|
312
|
+
- "WCMP" replaced with "WCNP" throughout the transcript
|
|
313
|
+
- "WCNP" added to your vocabulary with the full expansion
|
|
314
|
+
- Phonetic variants stored so it won't be misheard again
|
|
315
|
+
|
|
316
|
+
## Scenario 16: Fix a Misheard Name
|
|
317
|
+
|
|
318
|
+
A person's name was transcribed phonetically.
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# The transcript has "San Jay Grouper" but should be "Sanjay Gupta"
|
|
322
|
+
protokoll feedback ~/notes/2026/01/15-1412-meeting.md \
|
|
323
|
+
-f "San Jay Grouper is actually Sanjay Gupta"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Result:
|
|
327
|
+
- Name corrected throughout the transcript
|
|
328
|
+
- Variations like "San Jay" or "Sanjay Grouper" also fixed
|
|
329
|
+
- Person added to context for future recognition
|
|
330
|
+
|
|
331
|
+
## Scenario 17: Reassign to Different Project via Feedback
|
|
332
|
+
|
|
333
|
+
A transcript was routed to the wrong project.
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# Interactive feedback
|
|
337
|
+
protokoll feedback ~/notes/2026/01/15-1412-meeting.md
|
|
338
|
+
|
|
339
|
+
# When prompted: "This should be in the Quantum Readiness project"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Result:
|
|
343
|
+
- Project metadata updated in the transcript
|
|
344
|
+
- File moved to the project's configured destination
|
|
345
|
+
- Filename updated according to project rules
|
|
346
|
+
|
|
347
|
+
## Scenario 18: Preview Feedback Changes
|
|
348
|
+
|
|
349
|
+
See what would happen without making changes.
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Dry run with verbose output
|
|
353
|
+
protokoll feedback ~/notes/2026/01/15-1412-meeting.md \
|
|
354
|
+
-f "YB should be Wibey" \
|
|
355
|
+
--dry-run --verbose
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Output:
|
|
359
|
+
```
|
|
360
|
+
[Dry Run] Would apply the following changes:
|
|
361
|
+
- Replaced "YB" with "Wibey" (3 occurrences)
|
|
362
|
+
- Added term "Wibey" to context
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Scenario 19: Get Help with Feedback
|
|
366
|
+
|
|
367
|
+
Not sure what feedback you can give.
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Show feedback examples
|
|
371
|
+
protokoll feedback --help-me
|
|
372
|
+
|
|
373
|
+
# Or during interactive session
|
|
374
|
+
protokoll feedback ~/notes/meeting.md
|
|
375
|
+
# Enter: "What kinds of feedback can I give?"
|
|
376
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Interactive Prompt Context Improvements
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
When protokoll encounters an unknown person or project, it now provides rich context about the file being processed, making it easier to make informed decisions.
|
|
5
|
+
|
|
6
|
+
## Before (Old Output)
|
|
7
|
+
```
|
|
8
|
+
────────────────────────────────────────────────────────────
|
|
9
|
+
[Unknown Person Detected]
|
|
10
|
+
Name heard: "Trey Toulson"
|
|
11
|
+
Context: Name heard: "Trey Toulson"
|
|
12
|
+
────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
Is the name spelled correctly? (Enter to accept, or type correction):
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Problem:** No file context, duplicate information, unclear what recording this is from.
|
|
18
|
+
|
|
19
|
+
## After (New Output)
|
|
20
|
+
```
|
|
21
|
+
────────────────────────────────────────────────────────────
|
|
22
|
+
[Unknown Person Detected]
|
|
23
|
+
Name heard: "Trey Toulson"
|
|
24
|
+
|
|
25
|
+
File: meeting-notes-2026-01-15.m4a
|
|
26
|
+
Date: Wed, Jan 15, 2026, 07:10 AM
|
|
27
|
+
|
|
28
|
+
Unknown person mentioned: "Trey Toulson"
|
|
29
|
+
|
|
30
|
+
Context from transcript:
|
|
31
|
+
"I had a really productive meeting with Trey Toulson yesterday.
|
|
32
|
+
He's the new VP of Engineering at Acme Corp and is interested
|
|
33
|
+
in collaborating on the Phoenix Initiative."
|
|
34
|
+
────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
Is the name spelled correctly? (Enter to accept, or type correction):
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Benefits:**
|
|
40
|
+
- **File context:** See which recording this person was mentioned in
|
|
41
|
+
- **Date/time:** Know when the recording was made
|
|
42
|
+
- **Transcript excerpt:** See the sentences around where the name appears
|
|
43
|
+
- **Clear formatting:** Better visual hierarchy and no duplication
|
|
44
|
+
|
|
45
|
+
Now you can immediately see that Trey is the VP of Engineering at Acme Corp!
|
|
46
|
+
|
|
47
|
+
## Project/Term Detection
|
|
48
|
+
|
|
49
|
+
Similar improvements for unknown projects:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
────────────────────────────────────────────────────────────
|
|
53
|
+
[Unknown Project/Term]
|
|
54
|
+
Term: "Phoenix Initiative"
|
|
55
|
+
|
|
56
|
+
File: status-update-2026-01-15.m4a
|
|
57
|
+
Date: Wed, Jan 15, 2026, 02:30 PM
|
|
58
|
+
|
|
59
|
+
Unknown project/term: "Phoenix Initiative"
|
|
60
|
+
|
|
61
|
+
Context from transcript:
|
|
62
|
+
"We're making great progress on the Phoenix Initiative. The team
|
|
63
|
+
has completed the initial architecture design and we're ready to
|
|
64
|
+
start implementation next week."
|
|
65
|
+
────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
Is this a Project or a Term? (P/T, or Enter to skip):
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You can immediately see this is an active project with progress updates!
|
|
71
|
+
|
|
72
|
+
## Implementation Details
|
|
73
|
+
|
|
74
|
+
The improvements were made to:
|
|
75
|
+
- `src/agentic/tools/lookup-person.ts` - Added file metadata and transcript context extraction
|
|
76
|
+
- `src/agentic/tools/lookup-project.ts` - Added file metadata and transcript context extraction
|
|
77
|
+
- `src/interactive/handler.ts` - Enhanced display formatting in wizard prompts
|
|
78
|
+
|
|
79
|
+
The `ToolContext` interface already provided:
|
|
80
|
+
- `sourceFile: string` - Full path to the audio file
|
|
81
|
+
- `audioDate: Date` - Recording creation date
|
|
82
|
+
- `transcriptText: string` - The full transcript being processed
|
|
83
|
+
|
|
84
|
+
### Transcript Context Extraction
|
|
85
|
+
|
|
86
|
+
The tools now include intelligent context extraction that:
|
|
87
|
+
1. **Finds the mention:** Searches for the name/term in the transcript (case-insensitive)
|
|
88
|
+
2. **Extracts surrounding text:** Captures approximately one sentence before and after
|
|
89
|
+
3. **Limits length:** Keeps context under ~300 characters to avoid overwhelming the prompt
|
|
90
|
+
4. **Handles edge cases:** Gracefully handles missing mentions or very long sentences
|
|
91
|
+
|
|
92
|
+
This gives you immediate understanding of who someone is or what a project involves, right from the prompt!
|
package/docs/package-lock.json
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
|
50
50
|
"dev": true,
|
|
51
51
|
"license": "MIT",
|
|
52
|
+
"peer": true,
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"@babel/code-frame": "^7.27.1",
|
|
54
55
|
"@babel/generator": "^7.28.5",
|
|
@@ -1214,6 +1215,7 @@
|
|
|
1214
1215
|
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
|
1215
1216
|
"dev": true,
|
|
1216
1217
|
"license": "MIT",
|
|
1218
|
+
"peer": true,
|
|
1217
1219
|
"dependencies": {
|
|
1218
1220
|
"@types/prop-types": "*",
|
|
1219
1221
|
"csstype": "^3.2.2"
|
|
@@ -1280,6 +1282,7 @@
|
|
|
1280
1282
|
}
|
|
1281
1283
|
],
|
|
1282
1284
|
"license": "MIT",
|
|
1285
|
+
"peer": true,
|
|
1283
1286
|
"dependencies": {
|
|
1284
1287
|
"baseline-browser-mapping": "^2.9.0",
|
|
1285
1288
|
"caniuse-lite": "^1.0.30001759",
|
|
@@ -1549,6 +1552,7 @@
|
|
|
1549
1552
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
|
1550
1553
|
"dev": true,
|
|
1551
1554
|
"license": "MIT",
|
|
1555
|
+
"peer": true,
|
|
1552
1556
|
"engines": {
|
|
1553
1557
|
"node": ">=12"
|
|
1554
1558
|
},
|
|
@@ -1590,6 +1594,7 @@
|
|
|
1590
1594
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
1591
1595
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
|
1592
1596
|
"license": "MIT",
|
|
1597
|
+
"peer": true,
|
|
1593
1598
|
"dependencies": {
|
|
1594
1599
|
"loose-envify": "^1.1.0"
|
|
1595
1600
|
},
|
|
@@ -1748,6 +1753,7 @@
|
|
|
1748
1753
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
|
1749
1754
|
"dev": true,
|
|
1750
1755
|
"license": "MIT",
|
|
1756
|
+
"peer": true,
|
|
1751
1757
|
"dependencies": {
|
|
1752
1758
|
"esbuild": "^0.27.0",
|
|
1753
1759
|
"fdir": "^6.5.0",
|
package/docs/package.json
CHANGED
package/eslint.config.mjs
CHANGED