@oddessentials/agent-guild 0.1.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.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/bin/agent-guild-report.mjs +134 -0
- package/bin/agent-guild.mjs +240 -0
- package/config/providers.default.json +124 -0
- package/examples/claude-code-settings.json +48 -0
- package/examples/codex-hooks.json +45 -0
- package/examples/gemini-settings.json +61 -0
- package/examples/grok-hooks.json +55 -0
- package/examples/providers.json +27 -0
- package/node_modules/@xterm/addon-fit/LICENSE +19 -0
- package/node_modules/@xterm/addon-fit/README.md +24 -0
- package/node_modules/@xterm/addon-fit/lib/addon-fit.js +2 -0
- package/node_modules/@xterm/addon-fit/lib/addon-fit.js.map +1 -0
- package/node_modules/@xterm/addon-fit/lib/addon-fit.mjs +18 -0
- package/node_modules/@xterm/addon-fit/lib/addon-fit.mjs.map +7 -0
- package/node_modules/@xterm/addon-fit/package.json +26 -0
- package/node_modules/@xterm/addon-fit/src/FitAddon.ts +92 -0
- package/node_modules/@xterm/addon-fit/typings/addon-fit.d.ts +55 -0
- package/node_modules/@xterm/addon-serialize/README.md +42 -0
- package/node_modules/@xterm/addon-serialize/lib/addon-serialize.js +2 -0
- package/node_modules/@xterm/addon-serialize/lib/addon-serialize.js.map +1 -0
- package/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs +19 -0
- package/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs.map +7 -0
- package/node_modules/@xterm/addon-serialize/package.json +29 -0
- package/node_modules/@xterm/addon-serialize/src/SerializeAddon.ts +695 -0
- package/node_modules/@xterm/addon-serialize/typings/addon-serialize.d.ts +116 -0
- package/node_modules/@xterm/addon-web-links/LICENSE +19 -0
- package/node_modules/@xterm/addon-web-links/README.md +21 -0
- package/node_modules/@xterm/addon-web-links/lib/addon-web-links.js +2 -0
- package/node_modules/@xterm/addon-web-links/lib/addon-web-links.js.map +1 -0
- package/node_modules/@xterm/addon-web-links/lib/addon-web-links.mjs +18 -0
- package/node_modules/@xterm/addon-web-links/lib/addon-web-links.mjs.map +7 -0
- package/node_modules/@xterm/addon-web-links/package.json +26 -0
- package/node_modules/@xterm/addon-web-links/src/WebLinkProvider.ts +199 -0
- package/node_modules/@xterm/addon-web-links/src/WebLinksAddon.ts +58 -0
- package/node_modules/@xterm/addon-web-links/typings/addon-web-links.d.ts +57 -0
- package/node_modules/@xterm/headless/README.md +31 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js +2 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js.map +1 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs.map +7 -0
- package/node_modules/@xterm/headless/package.json +29 -0
- package/node_modules/@xterm/headless/typings/xterm-headless.d.ts +1385 -0
- package/node_modules/@xterm/xterm/LICENSE +21 -0
- package/node_modules/@xterm/xterm/README.md +243 -0
- package/node_modules/@xterm/xterm/css/xterm.css +285 -0
- package/node_modules/@xterm/xterm/lib/xterm.js +2 -0
- package/node_modules/@xterm/xterm/lib/xterm.js.map +1 -0
- package/node_modules/@xterm/xterm/lib/xterm.mjs +53 -0
- package/node_modules/@xterm/xterm/lib/xterm.mjs.map +7 -0
- package/node_modules/@xterm/xterm/package.json +111 -0
- package/node_modules/@xterm/xterm/src/browser/AccessibilityManager.ts +435 -0
- package/node_modules/@xterm/xterm/src/browser/Clipboard.ts +93 -0
- package/node_modules/@xterm/xterm/src/browser/ColorContrastCache.ts +34 -0
- package/node_modules/@xterm/xterm/src/browser/CoreBrowserTerminal.ts +1339 -0
- package/node_modules/@xterm/xterm/src/browser/Linkifier.ts +403 -0
- package/node_modules/@xterm/xterm/src/browser/LocalizableStrings.ts +23 -0
- package/node_modules/@xterm/xterm/src/browser/OscLinkProvider.ts +129 -0
- package/node_modules/@xterm/xterm/src/browser/RenderDebouncer.ts +84 -0
- package/node_modules/@xterm/xterm/src/browser/TimeBasedDebouncer.ts +86 -0
- package/node_modules/@xterm/xterm/src/browser/Types.ts +226 -0
- package/node_modules/@xterm/xterm/src/browser/Viewport.ts +192 -0
- package/node_modules/@xterm/xterm/src/browser/decorations/BufferDecorationRenderer.ts +139 -0
- package/node_modules/@xterm/xterm/src/browser/decorations/ColorZoneStore.ts +117 -0
- package/node_modules/@xterm/xterm/src/browser/decorations/OverviewRulerRenderer.ts +214 -0
- package/node_modules/@xterm/xterm/src/browser/input/CompositionHelper.ts +248 -0
- package/node_modules/@xterm/xterm/src/browser/input/Mouse.ts +54 -0
- package/node_modules/@xterm/xterm/src/browser/input/MoveToCell.ts +251 -0
- package/node_modules/@xterm/xterm/src/browser/public/Terminal.ts +275 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/dom/DomRenderer.ts +542 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts +546 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/dom/WidthCache.ts +167 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/shared/Constants.ts +6 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/shared/RendererUtils.ts +95 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/shared/SelectionRenderModel.ts +93 -0
- package/node_modules/@xterm/xterm/src/browser/renderer/shared/Types.ts +84 -0
- package/node_modules/@xterm/xterm/src/browser/selection/SelectionModel.ts +144 -0
- package/node_modules/@xterm/xterm/src/browser/selection/Types.ts +15 -0
- package/node_modules/@xterm/xterm/src/browser/services/CharSizeService.ts +127 -0
- package/node_modules/@xterm/xterm/src/browser/services/CharacterJoinerService.ts +339 -0
- package/node_modules/@xterm/xterm/src/browser/services/CoreBrowserService.ts +137 -0
- package/node_modules/@xterm/xterm/src/browser/services/LinkProviderService.ts +28 -0
- package/node_modules/@xterm/xterm/src/browser/services/MouseService.ts +46 -0
- package/node_modules/@xterm/xterm/src/browser/services/RenderService.ts +376 -0
- package/node_modules/@xterm/xterm/src/browser/services/SelectionService.ts +1039 -0
- package/node_modules/@xterm/xterm/src/browser/services/Services.ts +158 -0
- package/node_modules/@xterm/xterm/src/browser/services/ThemeService.ts +198 -0
- package/node_modules/@xterm/xterm/src/browser/shared/Constants.ts +8 -0
- package/node_modules/@xterm/xterm/src/common/CircularList.ts +241 -0
- package/node_modules/@xterm/xterm/src/common/Clone.ts +23 -0
- package/node_modules/@xterm/xterm/src/common/Color.ts +376 -0
- package/node_modules/@xterm/xterm/src/common/CoreTerminal.ts +283 -0
- package/node_modules/@xterm/xterm/src/common/InputHandler.ts +3495 -0
- package/node_modules/@xterm/xterm/src/common/MultiKeyMap.ts +42 -0
- package/node_modules/@xterm/xterm/src/common/Platform.ts +44 -0
- package/node_modules/@xterm/xterm/src/common/SortedList.ts +194 -0
- package/node_modules/@xterm/xterm/src/common/TaskQueue.ts +166 -0
- package/node_modules/@xterm/xterm/src/common/TypedArrayUtils.ts +17 -0
- package/node_modules/@xterm/xterm/src/common/Types.ts +552 -0
- package/node_modules/@xterm/xterm/src/common/WindowsMode.ts +27 -0
- package/node_modules/@xterm/xterm/src/common/buffer/AttributeData.ts +211 -0
- package/node_modules/@xterm/xterm/src/common/buffer/Buffer.ts +662 -0
- package/node_modules/@xterm/xterm/src/common/buffer/BufferLine.ts +551 -0
- package/node_modules/@xterm/xterm/src/common/buffer/BufferRange.ts +13 -0
- package/node_modules/@xterm/xterm/src/common/buffer/BufferReflow.ts +226 -0
- package/node_modules/@xterm/xterm/src/common/buffer/BufferSet.ts +134 -0
- package/node_modules/@xterm/xterm/src/common/buffer/CellData.ts +94 -0
- package/node_modules/@xterm/xterm/src/common/buffer/Constants.ts +157 -0
- package/node_modules/@xterm/xterm/src/common/buffer/Marker.ts +43 -0
- package/node_modules/@xterm/xterm/src/common/buffer/Types.ts +52 -0
- package/node_modules/@xterm/xterm/src/common/data/Charsets.ts +256 -0
- package/node_modules/@xterm/xterm/src/common/data/EscapeSequences.ts +153 -0
- package/node_modules/@xterm/xterm/src/common/input/Keyboard.ts +373 -0
- package/node_modules/@xterm/xterm/src/common/input/TextDecoder.ts +346 -0
- package/node_modules/@xterm/xterm/src/common/input/UnicodeV6.ts +145 -0
- package/node_modules/@xterm/xterm/src/common/input/WriteBuffer.ts +247 -0
- package/node_modules/@xterm/xterm/src/common/input/XParseColor.ts +80 -0
- package/node_modules/@xterm/xterm/src/common/parser/Constants.ts +58 -0
- package/node_modules/@xterm/xterm/src/common/parser/DcsParser.ts +192 -0
- package/node_modules/@xterm/xterm/src/common/parser/EscapeSequenceParser.ts +792 -0
- package/node_modules/@xterm/xterm/src/common/parser/OscParser.ts +238 -0
- package/node_modules/@xterm/xterm/src/common/parser/Params.ts +229 -0
- package/node_modules/@xterm/xterm/src/common/parser/Types.ts +275 -0
- package/node_modules/@xterm/xterm/src/common/public/AddonManager.ts +53 -0
- package/node_modules/@xterm/xterm/src/common/public/BufferApiView.ts +35 -0
- package/node_modules/@xterm/xterm/src/common/public/BufferLineApiView.ts +29 -0
- package/node_modules/@xterm/xterm/src/common/public/BufferNamespaceApi.ts +36 -0
- package/node_modules/@xterm/xterm/src/common/public/ParserApi.ts +37 -0
- package/node_modules/@xterm/xterm/src/common/public/UnicodeApi.ts +27 -0
- package/node_modules/@xterm/xterm/src/common/services/BufferService.ts +154 -0
- package/node_modules/@xterm/xterm/src/common/services/CharsetService.ts +34 -0
- package/node_modules/@xterm/xterm/src/common/services/CoreMouseService.ts +365 -0
- package/node_modules/@xterm/xterm/src/common/services/CoreService.ts +92 -0
- package/node_modules/@xterm/xterm/src/common/services/DecorationService.ts +140 -0
- package/node_modules/@xterm/xterm/src/common/services/InstantiationService.ts +85 -0
- package/node_modules/@xterm/xterm/src/common/services/LogService.ts +124 -0
- package/node_modules/@xterm/xterm/src/common/services/OptionsService.ts +212 -0
- package/node_modules/@xterm/xterm/src/common/services/OscLinkService.ts +115 -0
- package/node_modules/@xterm/xterm/src/common/services/ServiceRegistry.ts +49 -0
- package/node_modules/@xterm/xterm/src/common/services/Services.ts +396 -0
- package/node_modules/@xterm/xterm/src/common/services/UnicodeService.ts +111 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/browser.ts +141 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/canIUse.ts +49 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/dom.ts +2369 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/fastDomNode.ts +316 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/globalPointerMoveMonitor.ts +112 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/iframe.ts +135 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/keyboardEvent.ts +213 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/mouseEvent.ts +229 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/touch.ts +372 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +303 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +114 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +720 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +165 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +114 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +243 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +118 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +116 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/ui/widget.ts +57 -0
- package/node_modules/@xterm/xterm/src/vs/base/browser/window.ts +14 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/arrays.ts +887 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/arraysFind.ts +202 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/assert.ts +71 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/async.ts +1992 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/cancellation.ts +148 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/charCode.ts +450 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/collections.ts +140 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/decorators.ts +130 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/equals.ts +146 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/errors.ts +303 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/event.ts +1778 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/functional.ts +32 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/hash.ts +316 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/iterator.ts +159 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/keyCodes.ts +526 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/keybindings.ts +284 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/lazy.ts +47 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/lifecycle.ts +801 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/linkedList.ts +142 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/map.ts +202 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/numbers.ts +98 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observable.ts +76 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/api.ts +31 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/autorun.ts +281 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/base.ts +489 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/debugName.ts +145 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/derived.ts +428 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/lazyObservableValue.ts +146 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/logging.ts +328 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/promise.ts +209 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/observableInternal/utils.ts +610 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/platform.ts +281 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/scrollable.ts +522 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/sequence.ts +34 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/stopwatch.ts +43 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/strings.ts +557 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/symbols.ts +9 -0
- package/node_modules/@xterm/xterm/src/vs/base/common/uint.ts +59 -0
- package/node_modules/@xterm/xterm/src/vs/patches/nls.ts +90 -0
- package/node_modules/@xterm/xterm/src/vs/typings/base-common.d.ts +20 -0
- package/node_modules/@xterm/xterm/src/vs/typings/require.d.ts +42 -0
- package/node_modules/@xterm/xterm/src/vs/typings/vscode-globals-nls.d.ts +36 -0
- package/node_modules/@xterm/xterm/src/vs/typings/vscode-globals-product.d.ts +33 -0
- package/node_modules/@xterm/xterm/typings/xterm.d.ts +1957 -0
- package/node_modules/node-addon-api/LICENSE.md +9 -0
- package/node_modules/node-addon-api/README.md +319 -0
- package/node_modules/node-addon-api/common.gypi +20 -0
- package/node_modules/node-addon-api/except.gypi +25 -0
- package/node_modules/node-addon-api/index.js +12 -0
- package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/node_modules/node-addon-api/napi-inl.h +6607 -0
- package/node_modules/node-addon-api/napi.h +3201 -0
- package/node_modules/node-addon-api/node_addon_api.gyp +32 -0
- package/node_modules/node-addon-api/node_api.gyp +9 -0
- package/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/node_modules/node-addon-api/nothing.c +0 -0
- package/node_modules/node-addon-api/package-support.json +21 -0
- package/node_modules/node-addon-api/package.json +480 -0
- package/node_modules/node-addon-api/tools/README.md +73 -0
- package/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/node_modules/node-addon-api/tools/eslint-format.js +79 -0
- package/node_modules/node-pty/LICENSE +69 -0
- package/node_modules/node-pty/README.md +164 -0
- package/node_modules/node-pty/binding.gyp +150 -0
- package/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/node_modules/node-pty/lib/index.js +52 -0
- package/node_modules/node-pty/lib/interfaces.js +7 -0
- package/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/node_modules/node-pty/lib/terminal.js +190 -0
- package/node_modules/node-pty/lib/types.js +7 -0
- package/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/node_modules/node-pty/lib/utils.js +38 -0
- package/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/node_modules/node-pty/package.json +66 -0
- package/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
- package/node_modules/node-pty/scripts/post-install.js +76 -0
- package/node_modules/node-pty/scripts/prebuild.js +34 -0
- package/node_modules/node-pty/src/unix/pty.cc +876 -0
- package/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
- package/node_modules/node-pty/src/win/conpty.cc +616 -0
- package/node_modules/node-pty/src/win/conpty.h +41 -0
- package/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
- package/node_modules/node-pty/src/win/path_util.cc +95 -0
- package/node_modules/node-pty/src/win/path_util.h +26 -0
- package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/OpenConsole.exe +0 -0
- package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/conpty.dll +0 -0
- package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/OpenConsole.exe +0 -0
- package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/conpty.dll +0 -0
- package/node_modules/node-pty/typings/node-pty.d.ts +215 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +530 -0
- package/node_modules/ws/lib/receiver.js +743 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +562 -0
- package/node_modules/ws/lib/websocket.js +1421 -0
- package/node_modules/ws/package.json +74 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/package.json +52 -0
- package/src/manager/command-resolver.mjs +140 -0
- package/src/manager/config.mjs +94 -0
- package/src/manager/install-channels.mjs +268 -0
- package/src/manager/main.mjs +136 -0
- package/src/manager/providers.mjs +601 -0
- package/src/manager/report-shims.mjs +104 -0
- package/src/manager/server.mjs +421 -0
- package/src/manager/session-manager.mjs +306 -0
- package/src/manager/session.mjs +531 -0
- package/src/manager/shell-env.mjs +150 -0
- package/src/manager/usage.mjs +535 -0
- package/src/manager/versions.mjs +62 -0
- package/src/report/hooks.mjs +150 -0
- package/web/app.js +916 -0
- package/web/art/art.css +186 -0
- package/web/art/characters/anthropic/idle.png +0 -0
- package/web/art/characters/anthropic/locked.png +0 -0
- package/web/art/characters/anthropic/working.png +0 -0
- package/web/art/characters/google/idle.png +0 -0
- package/web/art/characters/google/locked.png +0 -0
- package/web/art/characters/google/working.png +0 -0
- package/web/art/characters/openai/idle.png +0 -0
- package/web/art/characters/openai/locked.png +0 -0
- package/web/art/characters/openai/working.png +0 -0
- package/web/art/characters/shell/idle.png +0 -0
- package/web/art/characters/shell/locked.png +0 -0
- package/web/art/characters/shell/working.png +0 -0
- package/web/art/characters/xai/idle.png +0 -0
- package/web/art/characters/xai/locked.png +0 -0
- package/web/art/characters/xai/working.png +0 -0
- package/web/art/familiars/aether.png +0 -0
- package/web/art/familiars/flame.png +0 -0
- package/web/art/familiars/leaf.png +0 -0
- package/web/art/familiars/night.png +0 -0
- package/web/art/icons/anthropic.png +0 -0
- package/web/art/icons/google.png +0 -0
- package/web/art/icons/openai.png +0 -0
- package/web/art/icons/shell.png +0 -0
- package/web/art/icons/xai.png +0 -0
- package/web/art/page.png +0 -0
- package/web/art/ui/frame.png +0 -0
- package/web/art/ui/gem-mana.png +0 -0
- package/web/art/ui/gem-vitality.png +0 -0
- package/web/art/ui/guild-crest.png +0 -0
- package/web/art/ui/level-medallion.png +0 -0
- package/web/art/ui/padlock.png +0 -0
- package/web/icons/README.md +8 -0
- package/web/index.html +154 -0
- package/web/styles.css +226 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
// Remaining usage per provider, read from the same sign-in the coding tool
|
|
2
|
+
// uses. Credentials never leave the manager; clients only get percentages.
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import { resolveCommand, buildSpawnSpec, runSpec } from './command-resolver.mjs';
|
|
9
|
+
|
|
10
|
+
export const USAGE_TTL_MS = 60 * 1000;
|
|
11
|
+
const RATE_LIMITED_TTL_MS = 5 * 60 * 1000;
|
|
12
|
+
const CLAUDE_USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
13
|
+
const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
|
14
|
+
const CLAUDE_KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
15
|
+
const GEMINI_KEYCHAIN_SERVICE = 'gemini-cli-oauth';
|
|
16
|
+
const GEMINI_KEYCHAIN_ACCOUNT = 'main-account';
|
|
17
|
+
const GEMINI_CODE_ASSIST_URL = 'https://cloudcode-pa.googleapis.com/v1internal';
|
|
18
|
+
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
19
|
+
|
|
20
|
+
export class UsageError extends Error {}
|
|
21
|
+
|
|
22
|
+
function shortPath(file) {
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
return file.startsWith(home) ? `~${file.slice(home.length)}` : file;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A finite number from a number or a numeric string, else null. */
|
|
28
|
+
export function toNumber(value) {
|
|
29
|
+
if (typeof value === 'string' && value.trim() !== '') value = Number(value);
|
|
30
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A percentage from a number or numeric string, or null when it is unknown. */
|
|
34
|
+
export function clampPercent(value) {
|
|
35
|
+
const n = toNumber(value);
|
|
36
|
+
if (n === null) return null;
|
|
37
|
+
return Math.min(100, Math.max(0, Math.round(n * 10) / 10));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** ISO time from an ISO string, epoch seconds or epoch milliseconds. */
|
|
41
|
+
export function toIso(value) {
|
|
42
|
+
if (value === undefined || value === null || value === '') return null;
|
|
43
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) return toIso(Number(value));
|
|
44
|
+
const ms = typeof value === 'number' ? (value < 1e12 ? value * 1000 : value) : Date.parse(value);
|
|
45
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The first value that is a non-blank string, trimmed, else null. */
|
|
49
|
+
function firstText(...values) {
|
|
50
|
+
return values.find((v) => typeof v === 'string' && v.trim() !== '')?.trim() ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Adds a window, keeping labels within 40 characters and unique: a repeat gets " (2)", " (3)", … */
|
|
54
|
+
function addWindow(windows, window) {
|
|
55
|
+
const base = String(window.label).slice(0, 35);
|
|
56
|
+
let label = base;
|
|
57
|
+
for (let n = 2; windows.some((w) => w.label.toLowerCase() === label.toLowerCase()); n++) label = `${base} (${n})`;
|
|
58
|
+
windows.push({ ...window, label });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function windowLabel(seconds, fallback = 'usage') {
|
|
62
|
+
const s = Number(seconds);
|
|
63
|
+
if (!Number.isFinite(s) || s <= 0) return fallback;
|
|
64
|
+
if (s % 86400 === 0) return `${s / 86400}-day`;
|
|
65
|
+
return `${Math.round(s / 3600)}-hour`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---- Claude Code ----------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The directory Claude Code keeps credentials under. As in Claude Code,
|
|
72
|
+
* CLAUDE_SECURESTORAGE_CONFIG_DIR wins when defined (empty means the
|
|
73
|
+
* default directory), else CLAUDE_CONFIG_DIR, and paths are NFC-normalised.
|
|
74
|
+
*/
|
|
75
|
+
function claudeCredentialsDir(env) {
|
|
76
|
+
const secure = env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
77
|
+
const dir = (secure !== undefined ? secure : env.CLAUDE_CONFIG_DIR) || path.join(os.homedir(), '.claude');
|
|
78
|
+
return dir.normalize('NFC');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function claudeCredentialsFile(env = process.env) {
|
|
82
|
+
return path.join(claudeCredentialsDir(env), '.credentials.json');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The keychain item Claude Code uses: the default one, or one keyed to a custom directory. */
|
|
86
|
+
export function claudeKeychainService(env = process.env) {
|
|
87
|
+
const secure = env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
88
|
+
const custom = secure !== undefined ? secure : env.CLAUDE_CONFIG_DIR;
|
|
89
|
+
if (!custom) return CLAUDE_KEYCHAIN_SERVICE;
|
|
90
|
+
const hash = crypto.createHash('sha256').update(claudeCredentialsDir(env)).digest('hex').slice(0, 8);
|
|
91
|
+
return `${CLAUDE_KEYCHAIN_SERVICE}-${hash}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readKeychainItem(service) {
|
|
95
|
+
const spec = { file: 'security', args: ['find-generic-password', '-s', service, '-w'] };
|
|
96
|
+
return runSpec(spec, { timeoutMs: 30000 }).then((r) => r.stdout, () => null);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Claude Code keeps its OAuth token in the macOS keychain, and in
|
|
101
|
+
* .credentials.json elsewhere or when the keychain is unavailable.
|
|
102
|
+
*/
|
|
103
|
+
export async function readClaudeCredentials({
|
|
104
|
+
file = claudeCredentialsFile(),
|
|
105
|
+
keychain = process.platform === 'darwin',
|
|
106
|
+
service = CLAUDE_KEYCHAIN_SERVICE,
|
|
107
|
+
readKeychain = readKeychainItem,
|
|
108
|
+
} = {}) {
|
|
109
|
+
let raw = null;
|
|
110
|
+
if (keychain) raw = await readKeychain(service);
|
|
111
|
+
if (raw === null) {
|
|
112
|
+
try {
|
|
113
|
+
raw = await fs.promises.readFile(file, 'utf8');
|
|
114
|
+
} catch {
|
|
115
|
+
throw new UsageError(`Claude Code is not signed in on this machine (no ${keychain ? 'keychain item or ' : ''}${shortPath(file)})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let creds;
|
|
119
|
+
try { creds = JSON.parse(raw); } catch { throw new UsageError('Claude Code credentials could not be parsed'); }
|
|
120
|
+
const oauth = creds?.claudeAiOauth || creds;
|
|
121
|
+
if (typeof oauth?.accessToken !== 'string' || !oauth.accessToken) {
|
|
122
|
+
throw new UsageError('Claude Code is signed in with an API key, which has no subscription usage');
|
|
123
|
+
}
|
|
124
|
+
if (Number.isFinite(oauth.expiresAt) && oauth.expiresAt < Date.now()) {
|
|
125
|
+
throw new UsageError('Claude Code sign-in has expired; run claude once to refresh it');
|
|
126
|
+
}
|
|
127
|
+
const multiplier = typeof oauth.rateLimitTier === 'string' ? oauth.rateLimitTier.match(/_(\d+)x$/)?.[1] : null;
|
|
128
|
+
const plan = oauth.subscriptionType ? `${oauth.subscriptionType}${multiplier ? ` ${multiplier}x` : ''}` : null;
|
|
129
|
+
return { accessToken: oauth.accessToken, plan };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function fetchClaudeUsage({ accessToken, plan = null, version = null, fetchImpl = fetch } = {}) {
|
|
133
|
+
const res = await fetchImpl(CLAUDE_USAGE_URL, {
|
|
134
|
+
headers: {
|
|
135
|
+
Authorization: `Bearer ${accessToken}`,
|
|
136
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
137
|
+
'User-Agent': `claude-code/${version || '2.1.0'}`,
|
|
138
|
+
Accept: 'application/json',
|
|
139
|
+
},
|
|
140
|
+
signal: AbortSignal.timeout(10000),
|
|
141
|
+
});
|
|
142
|
+
if (!res.ok) throw httpUsageError(res.status, 'sign in again in Claude Code');
|
|
143
|
+
const body = await res.json();
|
|
144
|
+
const windows = [];
|
|
145
|
+
for (const [key, label] of [['five_hour', '5-hour'], ['seven_day', '7-day'], ['seven_day_opus', '7-day Opus'], ['seven_day_sonnet', '7-day Sonnet']]) {
|
|
146
|
+
const w = body?.[key];
|
|
147
|
+
const used = clampPercent(w?.utilization);
|
|
148
|
+
if (used !== null) windows.push({ label, usedPercent: used, resetsAt: toIso(w.resets_at) });
|
|
149
|
+
}
|
|
150
|
+
// Per-model weekly windows arrive as rows in `limits`, the way Claude
|
|
151
|
+
// Code's own /usage screen reads them; Fable is reported only there. A
|
|
152
|
+
// plan-wide row named for a fixed window above ("Sonnet") repeats it and
|
|
153
|
+
// is skipped. A row scoped to a surface as well ("Sonnet" in Cowork) is
|
|
154
|
+
// a different limit and is kept under both names, as is a versioned
|
|
155
|
+
// model ("Opus 4.8"), since whether it shares the family's pool is not
|
|
156
|
+
// knowable here and a repeated meter loses nothing. Every row is shown:
|
|
157
|
+
// `is_active` only marks the server's headline row.
|
|
158
|
+
const fixed = windows.map((w) => w.label.toLowerCase());
|
|
159
|
+
const rows = Array.isArray(body?.limits) ? body.limits.filter((row) => row && typeof row === 'object') : [];
|
|
160
|
+
for (const row of rows) {
|
|
161
|
+
if (row.kind !== 'weekly_scoped') continue;
|
|
162
|
+
const { model, surface } = row.scope ?? {};
|
|
163
|
+
const name = firstText(model?.display_name, model?.name, model?.id);
|
|
164
|
+
const where = firstText(surface?.display_name, surface?.name, surface?.id);
|
|
165
|
+
const used = clampPercent(row.percent);
|
|
166
|
+
if (!name || used === null) continue;
|
|
167
|
+
const label = where ? `7-day ${name} (${where})` : `7-day ${name}`;
|
|
168
|
+
if (where || !fixed.includes(label.toLowerCase())) addWindow(windows, { label, usedPercent: used, resetsAt: toIso(row.resets_at) });
|
|
169
|
+
}
|
|
170
|
+
// Extra usage is the paid pool that takes over once the included windows
|
|
171
|
+
// are spent. The share is computed from the spend and the monthly limit
|
|
172
|
+
// (both in minor currency units) when both are known, else taken from
|
|
173
|
+
// `utilization`, which is 0-100 like the windows above. Its period ends
|
|
174
|
+
// when the `spend` row says, the headline one when several are listed.
|
|
175
|
+
const extra = body?.extra_usage;
|
|
176
|
+
if (extra?.is_enabled === true) {
|
|
177
|
+
const limit = toNumber(extra.monthly_limit);
|
|
178
|
+
const spent = toNumber(extra.used_credits);
|
|
179
|
+
const used = clampPercent(limit !== null && limit > 0 && spent !== null ? (spent / limit) * 100 : extra.utilization);
|
|
180
|
+
const spend = rows.filter((row) => row.kind === 'spend');
|
|
181
|
+
const period = spend.find((row) => row.is_active === true) ?? spend[0];
|
|
182
|
+
if (used !== null) windows.push({ label: 'Extra usage', usedPercent: used, resetsAt: toIso(period?.resets_at) });
|
|
183
|
+
}
|
|
184
|
+
return { plan, windows };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---- Codex CLI ------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
export function codexAuthFile(env = process.env) {
|
|
190
|
+
return path.join(env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'auth.json');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function readCodexCredentials({ file = codexAuthFile() } = {}) {
|
|
194
|
+
let raw;
|
|
195
|
+
try {
|
|
196
|
+
raw = await fs.promises.readFile(file, 'utf8');
|
|
197
|
+
} catch {
|
|
198
|
+
throw new UsageError(`Codex CLI is not signed in on this machine (no ${shortPath(file)})`);
|
|
199
|
+
}
|
|
200
|
+
let auth;
|
|
201
|
+
try { auth = JSON.parse(raw); } catch { throw new UsageError('Codex CLI credentials could not be parsed'); }
|
|
202
|
+
const token = auth?.tokens?.access_token;
|
|
203
|
+
if (typeof token !== 'string' || !token) {
|
|
204
|
+
throw new UsageError('Codex CLI is signed in with an API key, which has no subscription usage');
|
|
205
|
+
}
|
|
206
|
+
return { accessToken: token, accountId: auth.tokens.account_id || null };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function fetchCodexUsage({ accessToken, accountId = null, fetchImpl = fetch } = {}) {
|
|
210
|
+
const headers = { Authorization: `Bearer ${accessToken}`, Accept: 'application/json', 'User-Agent': 'agent-guild' };
|
|
211
|
+
if (accountId) headers['ChatGPT-Account-Id'] = accountId;
|
|
212
|
+
const res = await fetchImpl(CODEX_USAGE_URL, { headers, signal: AbortSignal.timeout(10000) });
|
|
213
|
+
if (!res.ok) throw httpUsageError(res.status, 'sign in again in Codex CLI');
|
|
214
|
+
const body = await res.json();
|
|
215
|
+
const windows = codexWindows(body?.rate_limit ?? body?.rateLimit);
|
|
216
|
+
// Limits metered apart from the plan's main windows, one entry per
|
|
217
|
+
// feature or model (`limit_name`), each with its own windows.
|
|
218
|
+
const additional = body?.additional_rate_limits ?? body?.additionalRateLimits;
|
|
219
|
+
for (const entry of Array.isArray(additional) ? additional : []) {
|
|
220
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
221
|
+
const name = firstText(entry.limit_name, entry.limitName, entry.metered_feature, entry.meteredFeature);
|
|
222
|
+
if (!name) continue;
|
|
223
|
+
// The name is shortened, never the window, so both windows of one limit stay apart.
|
|
224
|
+
for (const w of codexWindows(entry.rate_limit ?? entry.rateLimit ?? entry)) addWindow(windows, { ...w, label: `${name.slice(0, 28)} ${w.label}` });
|
|
225
|
+
}
|
|
226
|
+
// Prepaid credits are a balance, not a window: null unless the account has
|
|
227
|
+
// a finite, metered balance.
|
|
228
|
+
const credits = body?.credits;
|
|
229
|
+
const balance = toNumber(credits?.balance);
|
|
230
|
+
return {
|
|
231
|
+
plan: body?.plan_type ?? body?.planType ?? null,
|
|
232
|
+
windows,
|
|
233
|
+
credits: credits?.has_credits === true && credits.unlimited !== true ? balance : null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The 5-hour and weekly windows of one Codex rate limit, in either key
|
|
239
|
+
* style. A window of unstated length keeps its position as its label.
|
|
240
|
+
*/
|
|
241
|
+
function codexWindows(limits) {
|
|
242
|
+
const windows = [];
|
|
243
|
+
if (!limits || typeof limits !== 'object') return windows;
|
|
244
|
+
for (const [key, position] of [['primary_window', 'primary'], ['primaryWindow', 'primary'], ['secondary_window', 'secondary'], ['secondaryWindow', 'secondary']]) {
|
|
245
|
+
const w = limits[key];
|
|
246
|
+
const used = clampPercent(w?.used_percent ?? w?.usedPercent);
|
|
247
|
+
if (used === null) continue;
|
|
248
|
+
const seconds = w.limit_window_seconds ?? w.limitWindowSeconds;
|
|
249
|
+
const resetAt = w.reset_at ?? w.resetAt;
|
|
250
|
+
const resetAfter = toNumber(w.reset_after_seconds ?? w.resetAfterSeconds);
|
|
251
|
+
const resetsAt = toIso(resetAt) ?? (resetAfter !== null ? new Date(Date.now() + resetAfter * 1000).toISOString() : null);
|
|
252
|
+
windows.push({ label: windowLabel(seconds, position), usedPercent: used, resetsAt });
|
|
253
|
+
}
|
|
254
|
+
return windows;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---- Gemini CLI -----------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
export function geminiCredentialsFile(env = process.env) {
|
|
260
|
+
return path.join(env.GEMINI_CLI_HOME || os.homedir(), '.gemini', 'oauth_creds.json');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Gemini CLI keeps its OAuth token in the OS keychain (service
|
|
265
|
+
* "gemini-cli-oauth", account "main-account") and, before it did, in
|
|
266
|
+
* oauth_creds.json. Its encrypted-file fallback cannot be read from here.
|
|
267
|
+
*/
|
|
268
|
+
export function geminiKeychainLookup(platform) {
|
|
269
|
+
if (platform === 'darwin') return { file: 'security', args: ['find-generic-password', '-s', GEMINI_KEYCHAIN_SERVICE, '-a', GEMINI_KEYCHAIN_ACCOUNT, '-w'] };
|
|
270
|
+
// keytar stores libsecret items with the attributes "service" and "account".
|
|
271
|
+
if (platform === 'linux') return { file: 'secret-tool', args: ['lookup', 'service', GEMINI_KEYCHAIN_SERVICE, 'account', GEMINI_KEYCHAIN_ACCOUNT] };
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function readGeminiKeychainItem(platform) {
|
|
276
|
+
const spec = geminiKeychainLookup(platform);
|
|
277
|
+
if (!spec) return Promise.resolve(null);
|
|
278
|
+
return runSpec(spec, { timeoutMs: 30000 }).then((r) => r.stdout, () => null);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function readGeminiCredentials({
|
|
282
|
+
file = geminiCredentialsFile(),
|
|
283
|
+
platform = process.platform,
|
|
284
|
+
readKeychain = readGeminiKeychainItem,
|
|
285
|
+
} = {}) {
|
|
286
|
+
const raw = await readKeychain(platform);
|
|
287
|
+
if (raw && raw.trim()) {
|
|
288
|
+
let item;
|
|
289
|
+
try { item = JSON.parse(raw); } catch { throw new UsageError('Gemini CLI credentials could not be parsed'); }
|
|
290
|
+
const token = item?.token ?? item;
|
|
291
|
+
if (typeof token?.accessToken !== 'string' || !token.accessToken) throw new UsageError('Gemini CLI credentials could not be parsed');
|
|
292
|
+
return { accessToken: token.accessToken, refreshToken: token.refreshToken || null, expiresAt: Number(token.expiresAt) || null, client: null };
|
|
293
|
+
}
|
|
294
|
+
let legacy;
|
|
295
|
+
try {
|
|
296
|
+
legacy = JSON.parse(await fs.promises.readFile(file, 'utf8'));
|
|
297
|
+
} catch {
|
|
298
|
+
throw new UsageError(`Gemini CLI is not signed in on this machine (no "${GEMINI_KEYCHAIN_SERVICE}" keychain item or ${shortPath(file)})`);
|
|
299
|
+
}
|
|
300
|
+
if (typeof legacy?.access_token !== 'string' || !legacy.access_token) throw new UsageError('Gemini CLI credentials could not be parsed');
|
|
301
|
+
const client = typeof legacy.client_id === 'string' && typeof legacy.client_secret === 'string'
|
|
302
|
+
? { id: legacy.client_id, secret: legacy.client_secret }
|
|
303
|
+
: null;
|
|
304
|
+
return { accessToken: legacy.access_token, refreshToken: legacy.refresh_token || null, expiresAt: Number(legacy.expiry_date) || null, client };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const geminiClients = new Map();
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The OAuth client Gemini CLI signed the user in with, read from the
|
|
311
|
+
* installed copy that `commandPath` starts. A refresh token can only be
|
|
312
|
+
* refreshed with the client that issued it, and the client belongs to
|
|
313
|
+
* Gemini CLI, so it is not kept here.
|
|
314
|
+
*/
|
|
315
|
+
export function geminiOAuthClientFromInstall(commandPath) {
|
|
316
|
+
if (!commandPath) return null;
|
|
317
|
+
if (geminiClients.has(commandPath)) return geminiClients.get(commandPath);
|
|
318
|
+
let client = null;
|
|
319
|
+
try {
|
|
320
|
+
let start = fs.realpathSync(commandPath);
|
|
321
|
+
if (/\.(cmd|bat|ps1)$/i.test(start)) {
|
|
322
|
+
// npm's shims start node_modules/... relative to their own folder.
|
|
323
|
+
const refersToGemini = /node_modules[\\/]@google[\\/]gemini-cli/.test(fs.readFileSync(start, 'utf8'));
|
|
324
|
+
start = refersToGemini ? path.join(path.dirname(start), 'node_modules', '@google', 'gemini-cli') : null;
|
|
325
|
+
}
|
|
326
|
+
let dir = start && (fs.statSync(start).isDirectory() ? start : path.dirname(start));
|
|
327
|
+
for (let depth = 0; dir && depth < 8; depth++) {
|
|
328
|
+
let name = null;
|
|
329
|
+
try { name = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).name; } catch { /* keep climbing */ }
|
|
330
|
+
if (name === '@google/gemini-cli') break;
|
|
331
|
+
const parent = path.dirname(dir);
|
|
332
|
+
dir = parent === dir ? null : parent;
|
|
333
|
+
}
|
|
334
|
+
for (const sub of dir ? ['bundle', 'dist'] : []) {
|
|
335
|
+
let files = [];
|
|
336
|
+
try { files = fs.readdirSync(path.join(dir, sub)).filter((f) => f.endsWith('.js')); } catch { continue; }
|
|
337
|
+
for (const file of files) {
|
|
338
|
+
const text = fs.readFileSync(path.join(dir, sub, file), 'latin1');
|
|
339
|
+
const id = text.match(/OAUTH_CLIENT_ID\s*=\s*"([^"]+)"/)?.[1];
|
|
340
|
+
const secret = text.match(/OAUTH_CLIENT_SECRET\s*=\s*"([^"]+)"/)?.[1];
|
|
341
|
+
if (id && secret) { client = { id, secret }; break; }
|
|
342
|
+
}
|
|
343
|
+
if (client) break;
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
client = null;
|
|
347
|
+
}
|
|
348
|
+
geminiClients.set(commandPath, client);
|
|
349
|
+
return client;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function refreshGoogleToken(refreshToken, client, fetchImpl) {
|
|
353
|
+
const body = new URLSearchParams({
|
|
354
|
+
client_id: client.id,
|
|
355
|
+
client_secret: client.secret,
|
|
356
|
+
refresh_token: refreshToken,
|
|
357
|
+
grant_type: 'refresh_token',
|
|
358
|
+
});
|
|
359
|
+
const res = await fetchImpl(GOOGLE_TOKEN_URL, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
362
|
+
body: body.toString(),
|
|
363
|
+
signal: AbortSignal.timeout(10000),
|
|
364
|
+
});
|
|
365
|
+
if (!res.ok) throw new UsageError('Gemini CLI sign-in could not be refreshed; run gemini once to sign in again');
|
|
366
|
+
const json = await res.json();
|
|
367
|
+
if (typeof json?.access_token !== 'string') throw new UsageError('Gemini CLI sign-in could not be refreshed; run gemini once to sign in again');
|
|
368
|
+
return { accessToken: json.access_token, expiresAt: Date.now() + (Number(json.expires_in) || 3600) * 1000 };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Asks the Code Assist API the way Gemini CLI does: loadCodeAssist for the
|
|
373
|
+
* account's project and tier (skipped when `project` is known), then
|
|
374
|
+
* retrieveUserQuota for one bucket per model. Returns the refreshed token
|
|
375
|
+
* and project too, so a caller can reuse them.
|
|
376
|
+
*/
|
|
377
|
+
export async function fetchGeminiUsage({
|
|
378
|
+
accessToken, refreshToken = null, expiresAt = null, client = null, project = null, version = null, fetchImpl = fetch,
|
|
379
|
+
} = {}) {
|
|
380
|
+
let token = { accessToken, expiresAt };
|
|
381
|
+
if (expiresAt && expiresAt < Date.now() + 60000) {
|
|
382
|
+
if (!refreshToken || !client) throw new UsageError('Gemini CLI sign-in has expired; run gemini once to refresh it');
|
|
383
|
+
token = await refreshGoogleToken(refreshToken, client, fetchImpl);
|
|
384
|
+
}
|
|
385
|
+
const headers = {
|
|
386
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
387
|
+
'Content-Type': 'application/json',
|
|
388
|
+
Accept: 'application/json',
|
|
389
|
+
'User-Agent': `GeminiCLI/${version || '0.62.0'} (agent-guild)`,
|
|
390
|
+
};
|
|
391
|
+
const post = async (method, body) => {
|
|
392
|
+
const res = await fetchImpl(`${GEMINI_CODE_ASSIST_URL}:${method}`, {
|
|
393
|
+
method: 'POST', headers, body: JSON.stringify(body), signal: AbortSignal.timeout(10000),
|
|
394
|
+
});
|
|
395
|
+
if (!res.ok) throw httpUsageError(res.status, 'sign in again in Gemini CLI');
|
|
396
|
+
return res.json();
|
|
397
|
+
};
|
|
398
|
+
let plan = null;
|
|
399
|
+
let projectId = project;
|
|
400
|
+
if (!projectId) {
|
|
401
|
+
const loaded = await post('loadCodeAssist', {
|
|
402
|
+
metadata: { ideType: 'IDE_UNSPECIFIED', platform: 'PLATFORM_UNSPECIFIED', pluginType: 'GEMINI' },
|
|
403
|
+
});
|
|
404
|
+
const found = loaded?.cloudaicompanionProject;
|
|
405
|
+
projectId = typeof found === 'string' ? found : typeof found?.id === 'string' ? found.id : null;
|
|
406
|
+
// A paid tier is the effective subscription, as Gemini CLI reads it.
|
|
407
|
+
const tier = loaded?.paidTier?.id || loaded?.paidTier?.name ? loaded.paidTier : loaded?.currentTier;
|
|
408
|
+
plan = tier?.name ?? tier?.id ?? null;
|
|
409
|
+
if (!projectId) throw new UsageError('Gemini CLI has no Code Assist project yet; finish signing in with gemini first');
|
|
410
|
+
}
|
|
411
|
+
const quota = await post('retrieveUserQuota', { project: projectId });
|
|
412
|
+
const windows = [];
|
|
413
|
+
for (const bucket of Array.isArray(quota?.buckets) ? quota.buckets : []) {
|
|
414
|
+
if (!bucket?.modelId || typeof bucket.remainingFraction !== 'number') continue;
|
|
415
|
+
const used = clampPercent((1 - bucket.remainingFraction) * 100);
|
|
416
|
+
if (used !== null) windows.push({ label: String(bucket.modelId).slice(0, 40), usedPercent: used, resetsAt: toIso(bucket.resetTime) });
|
|
417
|
+
}
|
|
418
|
+
return { plan, windows, project: projectId, token };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ---- any command that prints JSON ------------------------------------------
|
|
422
|
+
|
|
423
|
+
export async function commandUsage({ command, args = [] }, env, platform = process.platform) {
|
|
424
|
+
const resolved = resolveCommand(command, env, platform);
|
|
425
|
+
if (!resolved) throw new UsageError(`usage command "${command}" was not found on PATH`);
|
|
426
|
+
const spec = buildSpawnSpec(resolved, args, env, platform);
|
|
427
|
+
let stdout;
|
|
428
|
+
try {
|
|
429
|
+
({ stdout } = await runSpec(spec, { env }));
|
|
430
|
+
} catch (err) {
|
|
431
|
+
throw new UsageError(`usage command failed: ${String(err.stderr || err.message).trim().slice(0, 200)}`);
|
|
432
|
+
}
|
|
433
|
+
let body;
|
|
434
|
+
try { body = JSON.parse(stdout); } catch { throw new UsageError('usage command did not print JSON'); }
|
|
435
|
+
if (!Array.isArray(body?.windows)) throw new UsageError('usage command output has no "windows" array');
|
|
436
|
+
const windows = [];
|
|
437
|
+
for (const w of body.windows) {
|
|
438
|
+
const remaining = toNumber(w?.remainingPercent);
|
|
439
|
+
const used = clampPercent(w?.usedPercent ?? (remaining === null ? undefined : 100 - remaining));
|
|
440
|
+
if (used === null) continue;
|
|
441
|
+
windows.push({ label: String(w.label || 'usage').slice(0, 40), usedPercent: used, resetsAt: toIso(w.resetsAt) });
|
|
442
|
+
}
|
|
443
|
+
return { plan: body.plan ? String(body.plan).slice(0, 40) : null, windows };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function httpUsageError(status, signInHint) {
|
|
447
|
+
if (status === 401 || status === 403) return new UsageError(`usage endpoint refused the sign-in (HTTP ${status}); ${signInHint}`);
|
|
448
|
+
if (status === 429) return Object.assign(new UsageError('usage endpoint is rate limiting requests; retrying later'), { rateLimited: true });
|
|
449
|
+
return new UsageError(`usage endpoint answered HTTP ${status}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---- monitor ----------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
export class UsageMonitor {
|
|
455
|
+
/**
|
|
456
|
+
* @param {object} opts
|
|
457
|
+
* @param {import('./providers.mjs').ProviderRegistry} opts.registry
|
|
458
|
+
* @param {object} [opts.env]
|
|
459
|
+
* @param {Function} [opts.fetchImpl]
|
|
460
|
+
* @param {number} [opts.ttlMs]
|
|
461
|
+
* @param {object} [opts.readers] credential readers, replaceable in tests
|
|
462
|
+
*/
|
|
463
|
+
constructor({ registry, env = process.env, platform = process.platform, fetchImpl = fetch, ttlMs = USAGE_TTL_MS, readers = {} } = {}) {
|
|
464
|
+
this.registry = registry;
|
|
465
|
+
this.env = env;
|
|
466
|
+
this.platform = platform;
|
|
467
|
+
this.fetchImpl = fetchImpl;
|
|
468
|
+
this.ttlMs = ttlMs;
|
|
469
|
+
this.readers = { claude: readClaudeCredentials, codex: readCodexCredentials, gemini: readGeminiCredentials, ...readers };
|
|
470
|
+
this.cache = new Map();
|
|
471
|
+
this.gemini = new Map();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Snapshots for every provider that has a usage source. */
|
|
475
|
+
all() {
|
|
476
|
+
return Promise.all(this.registry.providers.filter((p) => p.usage).map((p) => this.snapshot(p)));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
snapshot(provider) {
|
|
480
|
+
const entry = this.cache.get(provider.id);
|
|
481
|
+
const now = Date.now();
|
|
482
|
+
if (entry?.inflight) return entry.inflight;
|
|
483
|
+
if (entry && now - entry.at < entry.ttl) return Promise.resolve(entry.snapshot);
|
|
484
|
+
const inflight = this._fetch(provider).then((snapshot) => {
|
|
485
|
+
const ttl = snapshot.rateLimited ? RATE_LIMITED_TTL_MS : this.ttlMs;
|
|
486
|
+
this.cache.set(provider.id, { snapshot, at: Date.now(), ttl });
|
|
487
|
+
return snapshot;
|
|
488
|
+
});
|
|
489
|
+
this.cache.set(provider.id, { ...entry, inflight });
|
|
490
|
+
return inflight;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async _fetch(provider) {
|
|
494
|
+
const base = { providerId: provider.id, plan: null, windows: [], credits: null, fetchedAt: new Date().toISOString(), error: null };
|
|
495
|
+
const env = { ...this.env, ...provider.env };
|
|
496
|
+
try {
|
|
497
|
+
let result;
|
|
498
|
+
if (provider.usage === 'claude') {
|
|
499
|
+
const creds = await this.readers.claude({
|
|
500
|
+
file: claudeCredentialsFile(env),
|
|
501
|
+
keychain: this.platform === 'darwin',
|
|
502
|
+
service: claudeKeychainService(env),
|
|
503
|
+
});
|
|
504
|
+
result = await fetchClaudeUsage({ ...creds, version: this.registry.versions.get(provider.id)?.installed, fetchImpl: this.fetchImpl });
|
|
505
|
+
} else if (provider.usage === 'codex') {
|
|
506
|
+
const creds = await this.readers.codex({ file: codexAuthFile(env) });
|
|
507
|
+
result = await fetchCodexUsage({ ...creds, fetchImpl: this.fetchImpl });
|
|
508
|
+
} else if (provider.usage === 'gemini') {
|
|
509
|
+
const creds = await this.readers.gemini({ file: geminiCredentialsFile(env), platform: this.platform });
|
|
510
|
+
// The refreshed token, project and plan belong to one sign-in; a new
|
|
511
|
+
// sign-in (another account, or the same one again) starts over.
|
|
512
|
+
const signIn = creds.refreshToken || creds.accessToken;
|
|
513
|
+
const cached = this.gemini.get(provider.id);
|
|
514
|
+
const known = cached?.signIn === signIn ? cached : {};
|
|
515
|
+
const fresh = known.token && known.token.expiresAt > Date.now() + 60000 ? known.token : null;
|
|
516
|
+
const { plan, windows, project, token } = await fetchGeminiUsage({
|
|
517
|
+
...creds,
|
|
518
|
+
client: creds.client || geminiOAuthClientFromInstall(this.registry.resolve(provider)),
|
|
519
|
+
...(fresh || {}),
|
|
520
|
+
project: env.GOOGLE_CLOUD_PROJECT || env.GOOGLE_CLOUD_PROJECT_ID || known.project || null,
|
|
521
|
+
version: this.registry.versions.get(provider.id)?.installed,
|
|
522
|
+
fetchImpl: this.fetchImpl,
|
|
523
|
+
});
|
|
524
|
+
this.gemini.set(provider.id, { signIn, project, token, plan: plan ?? known.plan ?? null });
|
|
525
|
+
result = { plan: plan ?? known.plan ?? null, windows };
|
|
526
|
+
} else {
|
|
527
|
+
result = await commandUsage(provider.usage, env, this.platform);
|
|
528
|
+
}
|
|
529
|
+
return { ...base, ...result };
|
|
530
|
+
} catch (err) {
|
|
531
|
+
const message = err instanceof UsageError ? err.message : `usage check failed: ${err.name === 'TimeoutError' ? 'timed out' : err.message}`;
|
|
532
|
+
return { ...base, error: message, ...(err.rateLimited ? { rateLimited: true } : {}) };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Installed and latest versions of provider tools.
|
|
2
|
+
|
|
3
|
+
import { runSpec } from './command-resolver.mjs';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
6
|
+
|
|
7
|
+
/** First semantic version in a tool's --version output, or null. */
|
|
8
|
+
export function parseVersion(text) {
|
|
9
|
+
const match = String(text || '').match(/(?<![\d.])(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?![\d.])/);
|
|
10
|
+
return match ? match[1] : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Semantic-version order: negative when a < b, positive when a > b. */
|
|
14
|
+
export function compareVersions(a, b) {
|
|
15
|
+
const [aMain, aPre] = String(a).split('-');
|
|
16
|
+
const [bMain, bPre] = String(b).split('-');
|
|
17
|
+
const an = aMain.split('.').map(Number);
|
|
18
|
+
const bn = bMain.split('.').map(Number);
|
|
19
|
+
for (let i = 0; i < 3; i++) {
|
|
20
|
+
if ((an[i] || 0) !== (bn[i] || 0)) return (an[i] || 0) - (bn[i] || 0);
|
|
21
|
+
}
|
|
22
|
+
if (Boolean(aPre) !== Boolean(bPre)) return aPre ? -1 : 1;
|
|
23
|
+
return aPre === bPre ? 0 : aPre < bPre ? -1 : 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function diagnosticLine(text) {
|
|
27
|
+
const lines = String(text || '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
28
|
+
const line = lines.find((l) => /^(?:[A-Za-z]*error|fatal|panic)\b[^A-Za-z]/i.test(l))
|
|
29
|
+
|| lines.find((l) => /\berror\b/i.test(l) && !/^(?:at|throw)\s/.test(l))
|
|
30
|
+
|| lines.at(-1)
|
|
31
|
+
|| '';
|
|
32
|
+
return line.slice(0, 240);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function probeVersion(spec, { env, timeoutMs = 15000 } = {}) {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout, stderr } = await runSpec(spec, { env, timeoutMs });
|
|
38
|
+
return { ok: true, version: parseVersion(`${stdout}\n${stderr}`), exitCode: 0, error: null };
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const error = err.killed
|
|
41
|
+
? `no answer within ${Math.round(timeoutMs / 1000)} seconds`
|
|
42
|
+
: diagnosticLine(`${err.stderr || ''}\n${err.stdout || ''}`) || String(err.message || err).slice(0, 240);
|
|
43
|
+
return { ok: false, version: null, exitCode: typeof err.code === 'number' ? err.code : null, error };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function fetchManifest(pkg, version = 'latest', { registryUrl = DEFAULT_NPM_REGISTRY, fetchImpl = fetch, timeoutMs = 10000 } = {}) {
|
|
48
|
+
const url = `${registryUrl.replace(/\/+$/, '')}/${pkg.replace('/', '%2f')}/${encodeURIComponent(version)}`;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetchImpl(url, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(timeoutMs) });
|
|
51
|
+
if (!res.ok) return { manifest: null, error: `the registry answered ${res.status ?? 'with an error'}` };
|
|
52
|
+
const body = await res.json();
|
|
53
|
+
if (typeof body?.version !== 'string') return { manifest: null, error: 'the registry sent no version' };
|
|
54
|
+
return { manifest: body, error: null };
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return { manifest: null, error: err?.name === 'TimeoutError' ? 'the registry did not answer in time' : String(err?.message || err) };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function latestVersion(pkg, options) {
|
|
61
|
+
return (await fetchManifest(pkg, 'latest', options)).manifest?.version ?? null;
|
|
62
|
+
}
|