devglide 0.1.1
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/LICENSE +21 -0
- package/README.md +338 -0
- package/bin/claude-md-template.js +94 -0
- package/bin/devglide.js +387 -0
- package/package.json +85 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/apps/coder/.turbo/turbo-lint.log +5 -0
- package/src/apps/coder/package.json +16 -0
- package/src/apps/coder/public/favicon.svg +7 -0
- package/src/apps/coder/public/page.css +275 -0
- package/src/apps/coder/public/page.js +528 -0
- package/src/apps/coder/server.js +3 -0
- package/src/apps/documentation/public/page.css +597 -0
- package/src/apps/documentation/public/page.js +609 -0
- package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
- package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/kanban/package.json +32 -0
- package/src/apps/kanban/public/favicon.svg +7 -0
- package/src/apps/kanban/public/page.css +1010 -0
- package/src/apps/kanban/public/page.js +1730 -0
- package/src/apps/kanban/public/vendor/marked.min.js +6 -0
- package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
- package/src/apps/kanban/src/db.ts +319 -0
- package/src/apps/kanban/src/index.ts +14 -0
- package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
- package/src/apps/kanban/src/mcp-helpers.ts +60 -0
- package/src/apps/kanban/src/mcp.ts +59 -0
- package/src/apps/kanban/src/routes/attachments.ts +161 -0
- package/src/apps/kanban/src/routes/features.ts +233 -0
- package/src/apps/kanban/src/routes/issues.ts +373 -0
- package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
- package/src/apps/kanban/src/tools/item-tools.ts +307 -0
- package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
- package/src/apps/kanban/tsconfig.check.json +9 -0
- package/src/apps/kanban/tsconfig.json +9 -0
- package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
- package/src/apps/keymap/package.json +16 -0
- package/src/apps/keymap/public/page.css +275 -0
- package/src/apps/keymap/public/page.js +294 -0
- package/src/apps/keymap/server.js +25 -0
- package/src/apps/log/.turbo/turbo-build.log +5 -0
- package/src/apps/log/.turbo/turbo-lint.log +45 -0
- package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/log/node_modules/.bin/tsc +21 -0
- package/src/apps/log/node_modules/.bin/tsserver +21 -0
- package/src/apps/log/node_modules/.bin/tsx +21 -0
- package/src/apps/log/package.json +36 -0
- package/src/apps/log/public/console-sniffer.js +221 -0
- package/src/apps/log/public/favicon.svg +7 -0
- package/src/apps/log/public/page.css +322 -0
- package/src/apps/log/public/page.js +463 -0
- package/src/apps/log/src/index.ts +9 -0
- package/src/apps/log/src/mcp.ts +122 -0
- package/src/apps/log/src/routes/log.ts +333 -0
- package/src/apps/log/src/routes/status.ts +25 -0
- package/src/apps/log/src/server-sniffer.ts +118 -0
- package/src/apps/log/src/services/file-patterns.ts +39 -0
- package/src/apps/log/src/services/file-tailer.ts +228 -0
- package/src/apps/log/src/services/line-parser.ts +94 -0
- package/src/apps/log/src/services/log-writer.ts +39 -0
- package/src/apps/log/tsconfig.json +8 -0
- package/src/apps/prompts/.turbo/turbo-build.log +5 -0
- package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
- package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/prompts/mcp.ts +175 -0
- package/src/apps/prompts/node_modules/.bin/tsc +21 -0
- package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
- package/src/apps/prompts/node_modules/.bin/tsx +21 -0
- package/src/apps/prompts/package.json +25 -0
- package/src/apps/prompts/public/page.css +315 -0
- package/src/apps/prompts/public/page.js +541 -0
- package/src/apps/prompts/services/prompt-store.ts +212 -0
- package/src/apps/prompts/src/index.ts +9 -0
- package/src/apps/prompts/tsconfig.json +8 -0
- package/src/apps/prompts/types.ts +27 -0
- package/src/apps/shell/.turbo/turbo-build.log +5 -0
- package/src/apps/shell/.turbo/turbo-lint.log +34 -0
- package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/shell/package.json +35 -0
- package/src/apps/shell/public/favicon.svg +7 -0
- package/src/apps/shell/public/page.css +407 -0
- package/src/apps/shell/public/page.js +1577 -0
- package/src/apps/shell/src/index.ts +150 -0
- package/src/apps/shell/src/mcp.ts +398 -0
- package/src/apps/shell/src/shell-types.ts +41 -0
- package/src/apps/shell/tsconfig.json +8 -0
- package/src/apps/test/.turbo/turbo-build.log +5 -0
- package/src/apps/test/.turbo/turbo-lint.log +27 -0
- package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/test/node_modules/.bin/tsc +21 -0
- package/src/apps/test/node_modules/.bin/tsserver +21 -0
- package/src/apps/test/node_modules/.bin/tsx +21 -0
- package/src/apps/test/node_modules/.bin/uuid +21 -0
- package/src/apps/test/package.json +35 -0
- package/src/apps/test/public/favicon.svg +7 -0
- package/src/apps/test/public/page.css +499 -0
- package/src/apps/test/public/page.js +417 -0
- package/src/apps/test/public/scenario-runner.js +450 -0
- package/src/apps/test/src/index.ts +9 -0
- package/src/apps/test/src/mcp.ts +192 -0
- package/src/apps/test/src/routes/trigger.ts +285 -0
- package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
- package/src/apps/test/src/services/scenario-manager.ts +361 -0
- package/src/apps/test/src/services/scenario-store.ts +145 -0
- package/src/apps/test/tsconfig.json +8 -0
- package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
- package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
- package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/vocabulary/mcp.ts +173 -0
- package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
- package/src/apps/vocabulary/package.json +25 -0
- package/src/apps/vocabulary/public/page.css +247 -0
- package/src/apps/vocabulary/public/page.js +444 -0
- package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
- package/src/apps/vocabulary/src/index.ts +10 -0
- package/src/apps/vocabulary/tsconfig.json +8 -0
- package/src/apps/vocabulary/types.ts +22 -0
- package/src/apps/voice/.turbo/turbo-build.log +5 -0
- package/src/apps/voice/.turbo/turbo-lint.log +43 -0
- package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/voice/node_modules/.bin/openai +21 -0
- package/src/apps/voice/node_modules/.bin/tsc +21 -0
- package/src/apps/voice/node_modules/.bin/tsserver +21 -0
- package/src/apps/voice/node_modules/.bin/tsx +21 -0
- package/src/apps/voice/package.json +35 -0
- package/src/apps/voice/public/favicon.svg +7 -0
- package/src/apps/voice/public/page.css +388 -0
- package/src/apps/voice/public/page.js +718 -0
- package/src/apps/voice/src/index.ts +10 -0
- package/src/apps/voice/src/mcp.ts +70 -0
- package/src/apps/voice/src/providers/index.ts +85 -0
- package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
- package/src/apps/voice/src/providers/types.ts +27 -0
- package/src/apps/voice/src/routes/config.ts +118 -0
- package/src/apps/voice/src/routes/transcribe.ts +90 -0
- package/src/apps/voice/src/services/config-store.ts +129 -0
- package/src/apps/voice/src/services/stats.ts +108 -0
- package/src/apps/voice/src/transcribe.ts +11 -0
- package/src/apps/voice/src/utils/mime.ts +16 -0
- package/src/apps/voice/tsconfig.json +8 -0
- package/src/apps/workflow/.turbo/turbo-build.log +5 -0
- package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
- package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
- package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
- package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
- package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
- package/src/apps/workflow/engine/executors/index.ts +28 -0
- package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
- package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
- package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
- package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
- package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
- package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
- package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
- package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
- package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
- package/src/apps/workflow/engine/graph-runner.ts +438 -0
- package/src/apps/workflow/engine/node-executor.ts +104 -0
- package/src/apps/workflow/engine/node-registry.ts +15 -0
- package/src/apps/workflow/engine/variable-resolver.ts +109 -0
- package/src/apps/workflow/mcp.ts +223 -0
- package/src/apps/workflow/node_modules/.bin/tsc +21 -0
- package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
- package/src/apps/workflow/node_modules/.bin/tsx +21 -0
- package/src/apps/workflow/package.json +25 -0
- package/src/apps/workflow/public/editor/canvas.js +366 -0
- package/src/apps/workflow/public/editor/drag-manager.js +326 -0
- package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
- package/src/apps/workflow/public/editor/history-manager.js +147 -0
- package/src/apps/workflow/public/editor/layout-engine.js +159 -0
- package/src/apps/workflow/public/editor/node-renderer.js +199 -0
- package/src/apps/workflow/public/editor/selection-manager.js +193 -0
- package/src/apps/workflow/public/favicon.svg +7 -0
- package/src/apps/workflow/public/models/node-types.js +300 -0
- package/src/apps/workflow/public/models/workflow-model.js +257 -0
- package/src/apps/workflow/public/page.css +406 -0
- package/src/apps/workflow/public/page.js +658 -0
- package/src/apps/workflow/public/panels/inspector.js +360 -0
- package/src/apps/workflow/public/panels/palette.js +106 -0
- package/src/apps/workflow/public/panels/run-view.js +275 -0
- package/src/apps/workflow/public/panels/toolbar.js +232 -0
- package/src/apps/workflow/public/panels/workflow-list.js +237 -0
- package/src/apps/workflow/public/state/store.js +47 -0
- package/src/apps/workflow/services/custom-node-loader.ts +48 -0
- package/src/apps/workflow/services/legacy-converter.ts +72 -0
- package/src/apps/workflow/services/run-manager.ts +190 -0
- package/src/apps/workflow/services/workflow-store.ts +424 -0
- package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
- package/src/apps/workflow/services/workflow-validator.ts +98 -0
- package/src/apps/workflow/src/index.ts +10 -0
- package/src/apps/workflow/templates/ci-pipeline.json +18 -0
- package/src/apps/workflow/templates/code-review.json +22 -0
- package/src/apps/workflow/templates/kanban-testing.json +24 -0
- package/src/apps/workflow/tsconfig.json +8 -0
- package/src/apps/workflow/types.ts +268 -0
- package/src/packages/auth-middleware.ts +14 -0
- package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
- package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
- package/src/packages/design-tokens/build.js +413 -0
- package/src/packages/design-tokens/demo/index.html +1367 -0
- package/src/packages/design-tokens/demo/proposition-a.html +717 -0
- package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
- package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
- package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
- package/src/packages/design-tokens/dist/tokens.css +345 -0
- package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
- package/src/packages/design-tokens/dist/tokens.js +386 -0
- package/src/packages/design-tokens/package.json +25 -0
- package/src/packages/design-tokens/tokens.json +228 -0
- package/src/packages/devtools-middleware.ts +22 -0
- package/src/packages/eslint-config/index.js +63 -0
- package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
- package/src/packages/eslint-config/package.json +18 -0
- package/src/packages/json-file-store.ts +232 -0
- package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
- package/src/packages/mcp-utils/dist/index.d.ts +33 -0
- package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
- package/src/packages/mcp-utils/dist/index.js +126 -0
- package/src/packages/mcp-utils/dist/index.js.map +1 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
- package/src/packages/mcp-utils/package.json +32 -0
- package/src/packages/mcp-utils/src/index.ts +171 -0
- package/src/packages/mcp-utils/tsconfig.json +9 -0
- package/src/packages/paths.ts +18 -0
- package/src/packages/project-context/index.js +55 -0
- package/src/packages/project-context/package.json +13 -0
- package/src/packages/project-store.ts +127 -0
- package/src/packages/server-sniffer.ts +132 -0
- package/src/packages/shared-assets/favicon.svg +7 -0
- package/src/packages/shared-assets/keymap-registry.js +512 -0
- package/src/packages/shared-assets/logo.svg +6 -0
- package/src/packages/shared-assets/package.json +11 -0
- package/src/packages/shared-assets/ui-utils.js +48 -0
- package/src/packages/shared-assets/voice-widget.d.ts +37 -0
- package/src/packages/shared-assets/voice-widget.js +695 -0
- package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
- package/src/packages/shared-types/dist/index.d.ts +39 -0
- package/src/packages/shared-types/dist/index.d.ts.map +1 -0
- package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
- package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
- package/src/packages/shared-types/package.json +25 -0
- package/src/packages/shared-types/src/index.ts +41 -0
- package/src/packages/shared-types/tsconfig.json +11 -0
- package/src/packages/tsconfig/base.json +15 -0
- package/src/packages/tsconfig/next.json +14 -0
- package/src/packages/tsconfig/node.json +11 -0
- package/src/packages/tsconfig/package.json +10 -0
- package/turbo.json +25 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevGlide Keymap Registry
|
|
3
|
+
*
|
|
4
|
+
* Global, platform-aware keymap registry loaded via <script> tag.
|
|
5
|
+
* Manages default bindings, user overrides (persisted to localStorage),
|
|
6
|
+
* and provides matching / formatting utilities for keyboard shortcuts.
|
|
7
|
+
*/
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var STORAGE_KEY = 'devglide:keymap';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Platform detection
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
var isMac = typeof navigator !== 'undefined' &&
|
|
18
|
+
/Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Internal state
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** @type {Map<string, {defaultBinding: object, description: string, group: string}>} */
|
|
25
|
+
var actions = new Map();
|
|
26
|
+
|
|
27
|
+
/** @type {Map<string, object>} user overrides keyed by actionId */
|
|
28
|
+
var overrides = new Map();
|
|
29
|
+
|
|
30
|
+
/** @type {Set<Function>} change listeners */
|
|
31
|
+
var listeners = new Set();
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Persistence helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function loadOverrides() {
|
|
38
|
+
try {
|
|
39
|
+
var raw = localStorage.getItem(STORAGE_KEY);
|
|
40
|
+
if (raw) {
|
|
41
|
+
var parsed = JSON.parse(raw);
|
|
42
|
+
if (parsed && typeof parsed === 'object') {
|
|
43
|
+
Object.keys(parsed).forEach(function (id) {
|
|
44
|
+
overrides.set(id, parsed[id]);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (_) { /* ignore corrupt data */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function saveOverrides() {
|
|
52
|
+
try {
|
|
53
|
+
var obj = {};
|
|
54
|
+
overrides.forEach(function (binding, id) {
|
|
55
|
+
obj[id] = binding;
|
|
56
|
+
});
|
|
57
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(obj));
|
|
58
|
+
} catch (_) { /* storage full / unavailable */ }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Notify helpers
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function notifyChange(actionId) {
|
|
66
|
+
listeners.forEach(function (cb) {
|
|
67
|
+
try { cb(actionId); } catch (_) { /* swallow listener errors */ }
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Modifier resolution helpers
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Evaluate whether a modifier flag matches the event.
|
|
77
|
+
* Handles the special 'ctrlOrMeta' value.
|
|
78
|
+
*/
|
|
79
|
+
function modifierMatches(bindingValue, eventCtrl, eventMeta) {
|
|
80
|
+
if (bindingValue === true) return true;
|
|
81
|
+
if (bindingValue === 'ctrlOrMeta') {
|
|
82
|
+
return isMac ? eventMeta : eventCtrl;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function eventHasCtrlOrMeta(e) {
|
|
88
|
+
return isMac ? e.metaKey : e.ctrlKey;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Matching helpers
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
var MODIFIER_KEYS = new Set([
|
|
96
|
+
'Control', 'Alt', 'Shift', 'Meta',
|
|
97
|
+
'ControlLeft', 'ControlRight',
|
|
98
|
+
'AltLeft', 'AltRight',
|
|
99
|
+
'ShiftLeft', 'ShiftRight',
|
|
100
|
+
'MetaLeft', 'MetaRight',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function isModifierKey(event) {
|
|
104
|
+
return MODIFIER_KEYS.has(event.key) || MODIFIER_KEYS.has(event.code);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if a binding specifies a modifier-only combo (no key/code).
|
|
109
|
+
*/
|
|
110
|
+
function isModifierOnly(binding) {
|
|
111
|
+
return !binding.key && !binding.code;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a binding requires no modifiers at all.
|
|
116
|
+
*/
|
|
117
|
+
function hasNoModifiers(binding) {
|
|
118
|
+
return !binding.ctrlKey && !binding.altKey && !binding.shiftKey &&
|
|
119
|
+
!binding.ctrlOrMeta && binding.ctrlOrMeta !== 'ctrlOrMeta' &&
|
|
120
|
+
binding.ctrlKey !== 'ctrlOrMeta';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Return true if `event` satisfies `binding`.
|
|
125
|
+
*/
|
|
126
|
+
function bindingMatchesEvent(binding, event) {
|
|
127
|
+
// --- Modifier checks ---
|
|
128
|
+
|
|
129
|
+
// ctrlKey on binding
|
|
130
|
+
var expectCtrl = binding.ctrlKey;
|
|
131
|
+
var expectCtrlOrMeta = (binding.ctrlOrMeta === true) ||
|
|
132
|
+
(binding.ctrlKey === 'ctrlOrMeta') ||
|
|
133
|
+
(binding.ctrlOrMeta === 'ctrlOrMeta');
|
|
134
|
+
|
|
135
|
+
if (expectCtrlOrMeta) {
|
|
136
|
+
// On Mac: require metaKey, on Win/Linux: require ctrlKey
|
|
137
|
+
if (isMac) {
|
|
138
|
+
if (!event.metaKey) return false;
|
|
139
|
+
} else {
|
|
140
|
+
if (!event.ctrlKey) return false;
|
|
141
|
+
}
|
|
142
|
+
} else if (expectCtrl === true) {
|
|
143
|
+
if (!event.ctrlKey) return false;
|
|
144
|
+
} else {
|
|
145
|
+
// No ctrl expected — but we must ensure the user isn't pressing ctrl
|
|
146
|
+
// (unless ctrlOrMeta accounts for it on the platform)
|
|
147
|
+
if (event.ctrlKey && !isMac) return false;
|
|
148
|
+
// On Mac, ctrlKey can be physical Control which we might want to ignore
|
|
149
|
+
// but for clean matching, if no ctrl is expected, reject if ctrl pressed
|
|
150
|
+
if (event.ctrlKey && isMac) {
|
|
151
|
+
// Allow ctrlKey on Mac if metaKey is what ctrlOrMeta maps to.
|
|
152
|
+
// Actually, if we're here, there's no ctrlOrMeta, so reject.
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// altKey
|
|
158
|
+
if (binding.altKey) {
|
|
159
|
+
if (!event.altKey) return false;
|
|
160
|
+
} else {
|
|
161
|
+
if (event.altKey) return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// shiftKey
|
|
165
|
+
if (binding.shiftKey) {
|
|
166
|
+
if (!event.shiftKey) return false;
|
|
167
|
+
} else {
|
|
168
|
+
if (event.shiftKey) return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// metaKey: if ctrlOrMeta is active and we're on Mac, we already checked
|
|
172
|
+
// metaKey above. Otherwise, meta should not be pressed (unless on Mac
|
|
173
|
+
// where ctrlOrMeta covers it).
|
|
174
|
+
if (!expectCtrlOrMeta) {
|
|
175
|
+
if (isMac && event.metaKey) return false;
|
|
176
|
+
if (!isMac && event.metaKey) return false;
|
|
177
|
+
} else {
|
|
178
|
+
// On the non-matching platform side, the other modifier should not be
|
|
179
|
+
// pressed. E.g. on Mac, ctrlOrMeta -> metaKey, so ctrlKey should be
|
|
180
|
+
// false (unless it happens to also be pressed, which we'll be lenient
|
|
181
|
+
// about — actually let's be strict for clean matching).
|
|
182
|
+
// We already validated the primary; no extra check needed since we
|
|
183
|
+
// checked ctrlKey above for non-ctrlOrMeta case.
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Key / Code checks ---
|
|
187
|
+
|
|
188
|
+
if (isModifierOnly(binding)) {
|
|
189
|
+
// Modifier-only binding: match when the event key is itself a modifier.
|
|
190
|
+
return isModifierKey(event);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (binding.code) {
|
|
194
|
+
if (event.code !== binding.code) return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (binding.key) {
|
|
198
|
+
if (event.key !== binding.key) return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Public API
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
var KeymapRegistry = {
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Register an action with its default key binding.
|
|
212
|
+
*/
|
|
213
|
+
register: function (actionId, defaultBinding, description, group) {
|
|
214
|
+
actions.set(actionId, {
|
|
215
|
+
defaultBinding: defaultBinding,
|
|
216
|
+
description: description || '',
|
|
217
|
+
group: group || 'General',
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Given a KeyboardEvent, return the matching actionId or null.
|
|
223
|
+
*/
|
|
224
|
+
resolve: function (event) {
|
|
225
|
+
var matched = null;
|
|
226
|
+
actions.forEach(function (entry, actionId) {
|
|
227
|
+
if (matched) return; // already found
|
|
228
|
+
var binding = overrides.has(actionId)
|
|
229
|
+
? overrides.get(actionId)
|
|
230
|
+
: entry.defaultBinding;
|
|
231
|
+
if (bindingMatchesEvent(binding, event)) {
|
|
232
|
+
matched = actionId;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return matched;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Return all registered actions grouped by their group name.
|
|
240
|
+
* Each entry contains actionId, description, binding, defaultBinding,
|
|
241
|
+
* and whether the binding has been overridden.
|
|
242
|
+
*/
|
|
243
|
+
getAll: function () {
|
|
244
|
+
var groups = {};
|
|
245
|
+
actions.forEach(function (entry, actionId) {
|
|
246
|
+
var group = entry.group;
|
|
247
|
+
if (!groups[group]) groups[group] = [];
|
|
248
|
+
var currentBinding = overrides.has(actionId)
|
|
249
|
+
? overrides.get(actionId)
|
|
250
|
+
: entry.defaultBinding;
|
|
251
|
+
groups[group].push({
|
|
252
|
+
actionId: actionId,
|
|
253
|
+
description: entry.description,
|
|
254
|
+
group: group,
|
|
255
|
+
binding: currentBinding,
|
|
256
|
+
defaultBinding: entry.defaultBinding,
|
|
257
|
+
overridden: overrides.has(actionId),
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
return groups;
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Return the current binding for an action.
|
|
265
|
+
*/
|
|
266
|
+
getBinding: function (actionId) {
|
|
267
|
+
if (overrides.has(actionId)) return overrides.get(actionId);
|
|
268
|
+
var entry = actions.get(actionId);
|
|
269
|
+
return entry ? entry.defaultBinding : null;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Set a user override for an action.
|
|
274
|
+
*/
|
|
275
|
+
rebind: function (actionId, newBinding) {
|
|
276
|
+
overrides.set(actionId, newBinding);
|
|
277
|
+
saveOverrides();
|
|
278
|
+
notifyChange(actionId);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Remove the user override for a single action.
|
|
283
|
+
*/
|
|
284
|
+
reset: function (actionId) {
|
|
285
|
+
if (overrides.has(actionId)) {
|
|
286
|
+
overrides.delete(actionId);
|
|
287
|
+
saveOverrides();
|
|
288
|
+
notifyChange(actionId);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Remove all user overrides.
|
|
294
|
+
*/
|
|
295
|
+
resetAll: function () {
|
|
296
|
+
var ids = Array.from(overrides.keys());
|
|
297
|
+
overrides.clear();
|
|
298
|
+
saveOverrides();
|
|
299
|
+
ids.forEach(function (id) { notifyChange(id); });
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format a binding for display.
|
|
304
|
+
* Returns a string like "Ctrl+Alt+J" or "Cmd+Alt+J" on Mac.
|
|
305
|
+
*/
|
|
306
|
+
formatBinding: function (binding) {
|
|
307
|
+
if (!binding) return '';
|
|
308
|
+
var parts = [];
|
|
309
|
+
|
|
310
|
+
// ctrlOrMeta / ctrlKey
|
|
311
|
+
var hasCtrlOrMeta = (binding.ctrlOrMeta === true) ||
|
|
312
|
+
(binding.ctrlKey === 'ctrlOrMeta') ||
|
|
313
|
+
(binding.ctrlOrMeta === 'ctrlOrMeta');
|
|
314
|
+
if (hasCtrlOrMeta) {
|
|
315
|
+
parts.push(isMac ? 'Cmd' : 'Ctrl');
|
|
316
|
+
} else if (binding.ctrlKey === true) {
|
|
317
|
+
parts.push('Ctrl');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (binding.altKey) parts.push('Alt');
|
|
321
|
+
if (binding.shiftKey) parts.push('Shift');
|
|
322
|
+
|
|
323
|
+
// Key label
|
|
324
|
+
var keyLabel = null;
|
|
325
|
+
if (binding.code) {
|
|
326
|
+
// Convert code to a readable label
|
|
327
|
+
if (binding.code.startsWith('Digit')) {
|
|
328
|
+
keyLabel = binding.code.replace('Digit', '');
|
|
329
|
+
} else if (binding.code.startsWith('Key')) {
|
|
330
|
+
keyLabel = binding.code.replace('Key', '');
|
|
331
|
+
} else {
|
|
332
|
+
keyLabel = binding.code;
|
|
333
|
+
}
|
|
334
|
+
} else if (binding.key) {
|
|
335
|
+
// Friendly names for special keys
|
|
336
|
+
var friendlyKeys = {
|
|
337
|
+
'ArrowLeft': 'Left',
|
|
338
|
+
'ArrowRight': 'Right',
|
|
339
|
+
'ArrowUp': 'Up',
|
|
340
|
+
'ArrowDown': 'Down',
|
|
341
|
+
'Escape': 'Esc',
|
|
342
|
+
' ': 'Space',
|
|
343
|
+
'Enter': 'Enter',
|
|
344
|
+
'Backspace': 'Backspace',
|
|
345
|
+
'Delete': 'Delete',
|
|
346
|
+
'Tab': 'Tab',
|
|
347
|
+
};
|
|
348
|
+
keyLabel = friendlyKeys[binding.key] || binding.key;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (keyLabel) parts.push(keyLabel);
|
|
352
|
+
return parts.join('+');
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Given a KeyboardEvent, build and return a binding object.
|
|
357
|
+
* Used by the settings UI key-capture recorder.
|
|
358
|
+
*/
|
|
359
|
+
captureBinding: function (event) {
|
|
360
|
+
var binding = {};
|
|
361
|
+
|
|
362
|
+
// Determine modifier state
|
|
363
|
+
if (isMac ? event.metaKey : event.ctrlKey) {
|
|
364
|
+
binding.ctrlOrMeta = true;
|
|
365
|
+
}
|
|
366
|
+
if (binding.ctrlOrMeta && isMac && event.ctrlKey) {
|
|
367
|
+
// User also pressed physical Ctrl on Mac alongside Cmd
|
|
368
|
+
binding.ctrlKey = true;
|
|
369
|
+
}
|
|
370
|
+
if (!isMac && event.metaKey && !binding.ctrlOrMeta) {
|
|
371
|
+
// Meta on non-Mac without ctrlOrMeta
|
|
372
|
+
binding.ctrlOrMeta = true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
binding.altKey = event.altKey || false;
|
|
376
|
+
binding.shiftKey = event.shiftKey || false;
|
|
377
|
+
|
|
378
|
+
// Key / code
|
|
379
|
+
if (!isModifierKey(event)) {
|
|
380
|
+
// Prefer code for letter and digit keys
|
|
381
|
+
if (event.code && (event.code.startsWith('Key') || event.code.startsWith('Digit'))) {
|
|
382
|
+
binding.code = event.code;
|
|
383
|
+
} else {
|
|
384
|
+
binding.key = event.key;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// If it's a modifier key, leave key/code absent (modifier-only combo)
|
|
388
|
+
|
|
389
|
+
return binding;
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Export user overrides as a JSON string.
|
|
394
|
+
*/
|
|
395
|
+
exportConfig: function () {
|
|
396
|
+
var obj = {};
|
|
397
|
+
overrides.forEach(function (binding, id) {
|
|
398
|
+
obj[id] = binding;
|
|
399
|
+
});
|
|
400
|
+
return JSON.stringify(obj, null, 2);
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Import user overrides from a JSON string.
|
|
405
|
+
* Replaces all current overrides.
|
|
406
|
+
*/
|
|
407
|
+
importConfig: function (json) {
|
|
408
|
+
var parsed;
|
|
409
|
+
try {
|
|
410
|
+
parsed = JSON.parse(json);
|
|
411
|
+
} catch (_) {
|
|
412
|
+
throw new Error('Invalid JSON for keymap config');
|
|
413
|
+
}
|
|
414
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
415
|
+
throw new Error('Keymap config must be a JSON object');
|
|
416
|
+
}
|
|
417
|
+
overrides.clear();
|
|
418
|
+
Object.keys(parsed).forEach(function (id) {
|
|
419
|
+
overrides.set(id, parsed[id]);
|
|
420
|
+
});
|
|
421
|
+
saveOverrides();
|
|
422
|
+
// Notify for all imported actions
|
|
423
|
+
Object.keys(parsed).forEach(function (id) { notifyChange(id); });
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Register a listener for binding changes.
|
|
428
|
+
* Callback receives the actionId that changed.
|
|
429
|
+
* Returns an unsubscribe function.
|
|
430
|
+
*/
|
|
431
|
+
onChange: function (callback) {
|
|
432
|
+
listeners.add(callback);
|
|
433
|
+
return function () { listeners.delete(callback); };
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Check if a KeyboardEvent matches a specific action.
|
|
438
|
+
*/
|
|
439
|
+
matchesAction: function (event, actionId) {
|
|
440
|
+
var binding = overrides.has(actionId)
|
|
441
|
+
? overrides.get(actionId)
|
|
442
|
+
: (actions.has(actionId) ? actions.get(actionId).defaultBinding : null);
|
|
443
|
+
if (!binding) return false;
|
|
444
|
+
return bindingMatchesEvent(binding, event);
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
/** Expose platform detection for consumers */
|
|
448
|
+
isMac: isMac,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// Initialisation — load overrides, register defaults
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
loadOverrides();
|
|
456
|
+
|
|
457
|
+
// Shell group — All arrows navigate grid spatially
|
|
458
|
+
KeymapRegistry.register('shell:terminal-down',
|
|
459
|
+
{ ctrlOrMeta: true, altKey: true, key: 'ArrowDown' },
|
|
460
|
+
'Down terminal', 'Shell');
|
|
461
|
+
|
|
462
|
+
KeymapRegistry.register('shell:terminal-up',
|
|
463
|
+
{ ctrlOrMeta: true, altKey: true, key: 'ArrowUp' },
|
|
464
|
+
'Up terminal', 'Shell');
|
|
465
|
+
|
|
466
|
+
KeymapRegistry.register('shell:terminal-right',
|
|
467
|
+
{ ctrlOrMeta: true, altKey: true, key: 'ArrowRight' },
|
|
468
|
+
'Right terminal', 'Shell');
|
|
469
|
+
|
|
470
|
+
KeymapRegistry.register('shell:terminal-left',
|
|
471
|
+
{ ctrlOrMeta: true, altKey: true, key: 'ArrowLeft' },
|
|
472
|
+
'Left terminal', 'Shell');
|
|
473
|
+
|
|
474
|
+
for (var i = 1; i <= 9; i++) {
|
|
475
|
+
KeymapRegistry.register('shell:terminal-' + i,
|
|
476
|
+
{ ctrlOrMeta: true, altKey: true, code: 'Digit' + i },
|
|
477
|
+
'Terminal ' + i, 'Shell');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
KeymapRegistry.register('shell:new-terminal',
|
|
481
|
+
{ ctrlOrMeta: true, altKey: true, code: 'KeyJ' },
|
|
482
|
+
'New terminal', 'Shell');
|
|
483
|
+
|
|
484
|
+
KeymapRegistry.register('shell:new-browser',
|
|
485
|
+
{ ctrlOrMeta: true, altKey: true, code: 'KeyB' },
|
|
486
|
+
'New browser', 'Shell');
|
|
487
|
+
|
|
488
|
+
KeymapRegistry.register('shell:close-pane',
|
|
489
|
+
{ ctrlOrMeta: true, altKey: true, code: 'KeyK' },
|
|
490
|
+
'Close pane', 'Shell');
|
|
491
|
+
|
|
492
|
+
// Voice group
|
|
493
|
+
KeymapRegistry.register('voice:hold-to-speak',
|
|
494
|
+
{ ctrlOrMeta: true, altKey: true, shiftKey: true },
|
|
495
|
+
'Hold to speak', 'Voice');
|
|
496
|
+
|
|
497
|
+
// Kanban group
|
|
498
|
+
KeymapRegistry.register('kanban:focus-search',
|
|
499
|
+
{ key: '/' },
|
|
500
|
+
'Focus search', 'Kanban');
|
|
501
|
+
|
|
502
|
+
KeymapRegistry.register('kanban:clear-search',
|
|
503
|
+
{ key: 'Escape' },
|
|
504
|
+
'Clear search', 'Kanban');
|
|
505
|
+
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
// Expose globally
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
|
|
510
|
+
window.KeymapRegistry = KeymapRegistry;
|
|
511
|
+
|
|
512
|
+
})();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 32">
|
|
2
|
+
<rect width="32" height="32" rx="6" fill="#7c3aed"/>
|
|
3
|
+
<path d="M8 10h6l4 6-4 6H8l4-6-4-6z" fill="#fff"/>
|
|
4
|
+
<path d="M16 10h6l4 6-4 6h-6l4-6-4-6z" fill="#fff" opacity="0.6"/>
|
|
5
|
+
<text x="40" y="22" font-family="SF Mono, Cascadia Code, Fira Code, monospace" font-size="16" font-weight="600" fill="#e0e0e0">devglide</text>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devglide/shared-assets",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Shared frontend assets (voice widget, voice listener, keymap registry) for DevGlide apps",
|
|
6
|
+
"main": "voice-widget.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"*.js",
|
|
9
|
+
"*.d.ts"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ── Shared UI Utilities ──────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Escape HTML special characters to prevent XSS.
|
|
5
|
+
*/
|
|
6
|
+
export function escapeHtml(str) {
|
|
7
|
+
return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Escape string for use in HTML attributes.
|
|
12
|
+
*/
|
|
13
|
+
export function escapeAttr(str) {
|
|
14
|
+
return (str || '').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert literal \n and \t escape sequences to real characters.
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeEscapes(text) {
|
|
21
|
+
if (!text) return '';
|
|
22
|
+
return text.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format a date string as relative time (e.g., "5m ago", "2h ago").
|
|
27
|
+
*/
|
|
28
|
+
export function timeAgo(dateStr) {
|
|
29
|
+
if (!dateStr) return 'never';
|
|
30
|
+
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
|
|
31
|
+
if (seconds < 5) return 'just now';
|
|
32
|
+
if (seconds < 60) return seconds + 's ago';
|
|
33
|
+
const minutes = Math.floor(seconds / 60);
|
|
34
|
+
if (minutes < 60) return minutes + 'm ago';
|
|
35
|
+
const hours = Math.floor(minutes / 60);
|
|
36
|
+
if (hours < 24) return hours + 'h ago';
|
|
37
|
+
const days = Math.floor(hours / 24);
|
|
38
|
+
return days + 'd ago';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format a duration in milliseconds to human-readable string.
|
|
43
|
+
*/
|
|
44
|
+
export function formatDuration(ms) {
|
|
45
|
+
if (ms == null) return '';
|
|
46
|
+
if (ms < 1000) return ms + 'ms';
|
|
47
|
+
return (ms / 1000).toFixed(1) + 's';
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface VoiceWidgetOptions {
|
|
2
|
+
/** Base URL of the voice app, e.g. 'http://localhost:7004' */
|
|
3
|
+
voiceUrl: string;
|
|
4
|
+
/** Called with the transcribed text when transcription succeeds */
|
|
5
|
+
onResult: (text: string) => void;
|
|
6
|
+
/** Called with an Error when something goes wrong (default: console.error) */
|
|
7
|
+
onError?: (err: Error) => void;
|
|
8
|
+
/** BCP-47 language tag, e.g. 'en'. Defaults to the voice app's configured language. */
|
|
9
|
+
language?: string;
|
|
10
|
+
/** Single-character keyboard shortcut to hold for recording, e.g. 'v' */
|
|
11
|
+
hotkey?: string;
|
|
12
|
+
/** Idle state label override (default: 'Speak') */
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface VoiceWidgetInstance {
|
|
17
|
+
/** Render the mic button into the given container element */
|
|
18
|
+
mount(containerEl: HTMLElement): void;
|
|
19
|
+
/** Remove the widget and clean up all event listeners */
|
|
20
|
+
destroy(): void;
|
|
21
|
+
/** Programmatically start recording */
|
|
22
|
+
startRecording(): void;
|
|
23
|
+
/** Programmatically stop recording and trigger transcription */
|
|
24
|
+
stopRecording(): void;
|
|
25
|
+
/** Current widget state */
|
|
26
|
+
readonly state: 'idle' | 'recording' | 'transcribing' | 'error';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface VoiceWidgetFactory {
|
|
30
|
+
create(opts: VoiceWidgetOptions): VoiceWidgetInstance;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare const VoiceWidget: VoiceWidgetFactory;
|
|
34
|
+
export default VoiceWidget;
|
|
35
|
+
|
|
36
|
+
// CommonJS / UMD
|
|
37
|
+
export { VoiceWidget };
|