feat: a whole bunch of nice things
This commit is contained in:
parent
7c6b39872d
commit
e6022b3fa6
12 changed files with 472 additions and 22 deletions
|
|
@ -34,6 +34,29 @@
|
|||
|
||||
services.resolved.enable = true;
|
||||
|
||||
services.searx = {
|
||||
enable = true;
|
||||
openFirewall = false;
|
||||
settings = {
|
||||
use_default_settings.engines.keep_only = [ "google" ];
|
||||
general.instance_name = "Local Google";
|
||||
search = {
|
||||
autocomplete = "";
|
||||
formats = [ "html" ];
|
||||
};
|
||||
server = {
|
||||
bind_address = "127.0.0.1";
|
||||
port = 8888;
|
||||
base_url = "http://127.0.0.1:8888/";
|
||||
limiter = false;
|
||||
public_instance = false;
|
||||
image_proxy = true;
|
||||
method = "GET";
|
||||
secret_key = "local-only-google-searxng";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.enable = true;
|
||||
# Required for Tailscale
|
||||
networking.firewall.checkReversePath = "loose";
|
||||
|
|
@ -95,7 +118,7 @@
|
|||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
TimeoutStartSec = 75;
|
||||
ExecStart = "/etc/profiles/per-user/jet/bin/opencode serve --hostname 127.0.0.1 --port 4096";
|
||||
ExecStart = "/etc/profiles/per-user/jet/bin/o serve --hostname 127.0.0.1 --port 4096";
|
||||
WorkingDirectory = config.users.users.jet.home;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -362,11 +362,11 @@
|
|||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779821945,
|
||||
"narHash": "sha256-6NHOS9mQiUMEDqgnuQXhqgckQ9ZR03PPW3b6P7XdUYQ=",
|
||||
"lastModified": 1780182789,
|
||||
"narHash": "sha256-PSRa+XhcJI/y+j7iSxGQZIQMPKYAq8od36RO8CcXL+c=",
|
||||
"owner": "anomalyco",
|
||||
"repo": "opencode",
|
||||
"rev": "fdfd0afed7fddbe852ea53b5a75ce1ea8ad725a2",
|
||||
"rev": "30f9780561e703147745945490a46646ca27670b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
14
flake.nix
14
flake.nix
|
|
@ -7,9 +7,7 @@
|
|||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||
ghostty.url = "github:ghostty-org/ghostty/main";
|
||||
helix.url = "github:helix-editor/helix/master";
|
||||
opencode = {
|
||||
url = "github:anomalyco/opencode/dev";
|
||||
};
|
||||
opencode.url = "github:anomalyco/opencode/dev";
|
||||
t3code.url = "github:jetpham/nix-t3code";
|
||||
nixos-hardware.url = "github:NixOS/nixos-hardware";
|
||||
zen-browser = {
|
||||
|
|
@ -63,8 +61,16 @@
|
|||
inputs.nur.overlays.default
|
||||
inputs.ghostty.overlays.default
|
||||
inputs.helix.overlays.default
|
||||
opencode.overlays.default
|
||||
(final: prev: {
|
||||
opencode = opencode.packages.${prev.stdenv.hostPlatform.system}.opencode;
|
||||
# opencode's dev branch asks for Bun 1.3.14, but this revision builds and runs with nixpkgs' Bun 1.3.13.
|
||||
opencode = prev.opencode.overrideAttrs (old: {
|
||||
postPatch = (old.postPatch or "") + ''
|
||||
substituteInPlace package.json \
|
||||
--replace-fail "bun@1.3.14" "bun@1.3.13"
|
||||
'';
|
||||
});
|
||||
opencode-original = final.opencode;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
|
|||
207
gnome-extensions/opencode-token-usage/extension.js
Normal file
207
gnome-extensions/opencode-token-usage/extension.js
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import Clutter from 'gi://Clutter';
|
||||
import Cogl from 'gi://Cogl';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import St from 'gi://St';
|
||||
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
|
||||
const COMMAND = '@opencodeTokenUsage@/bin/opencode-token-usage';
|
||||
const REFRESH_SECONDS = 60;
|
||||
const GRAPH_WIDTH = 96;
|
||||
const GRAPH_HEIGHT = 16;
|
||||
const CLASSES = [
|
||||
'opencode-token-usage-normal',
|
||||
'opencode-token-usage-warning',
|
||||
'opencode-token-usage-critical',
|
||||
'opencode-token-usage-missing',
|
||||
];
|
||||
|
||||
function colorFromString(colorString) {
|
||||
if (Cogl.Color.from_string) {
|
||||
const [ok, color] = Cogl.Color.from_string(colorString);
|
||||
if (ok)
|
||||
return color;
|
||||
}
|
||||
|
||||
return Clutter.Color.from_string(colorString)[1];
|
||||
}
|
||||
|
||||
function setSourceColor(cr, color) {
|
||||
if (Clutter.cairo_set_source_color)
|
||||
Clutter.cairo_set_source_color(cr, color);
|
||||
else
|
||||
cr.setSourceColor(color);
|
||||
}
|
||||
|
||||
const TokenUsageGraph = GObject.registerClass(
|
||||
class TokenUsageGraph extends St.DrawingArea {
|
||||
constructor() {
|
||||
super({
|
||||
style_class: 'opencode-token-usage-graph',
|
||||
reactive: false,
|
||||
});
|
||||
|
||||
this._values = [];
|
||||
this._background = colorFromString('#ffffff16');
|
||||
this._fill = colorFromString('#23863699');
|
||||
this._line = colorFromString('#58a6ff');
|
||||
this._scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
this.set_width(GRAPH_WIDTH * this._scaleFactor);
|
||||
this.set_height(GRAPH_HEIGHT * this._scaleFactor);
|
||||
this.connect('repaint', this._draw.bind(this));
|
||||
}
|
||||
|
||||
setValues(values) {
|
||||
this._values = Array.isArray(values) ? values.filter(value => Number.isFinite(value)) : [];
|
||||
this.queue_repaint();
|
||||
}
|
||||
|
||||
_point(index, width, height, max) {
|
||||
const x = this._values.length <= 1 ? width : index * (width / (this._values.length - 1));
|
||||
const y = height - 1 - (this._values[index] / max) * Math.max(1, height - 2);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
_draw() {
|
||||
const [width, height] = this.get_surface_size();
|
||||
const cr = this.get_context();
|
||||
|
||||
setSourceColor(cr, this._background);
|
||||
cr.rectangle(0, 0, width, height);
|
||||
cr.fill();
|
||||
|
||||
if (this._values.length === 0) {
|
||||
cr.$dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const max = Math.max(1, ...this._values);
|
||||
|
||||
cr.moveTo(0, height);
|
||||
for (let index = 0; index < this._values.length; index++) {
|
||||
const [x, y] = this._point(index, width, height, max);
|
||||
cr.lineTo(x, y);
|
||||
}
|
||||
cr.lineTo(width, height);
|
||||
cr.closePath();
|
||||
setSourceColor(cr, this._fill);
|
||||
cr.fill();
|
||||
|
||||
const [x0, y0] = this._point(0, width, height, max);
|
||||
cr.moveTo(x0, y0);
|
||||
for (let index = 1; index < this._values.length; index++) {
|
||||
const [x, y] = this._point(index, width, height, max);
|
||||
cr.lineTo(x, y);
|
||||
}
|
||||
cr.setLineWidth(Math.max(1, this._scaleFactor));
|
||||
setSourceColor(cr, this._line);
|
||||
cr.stroke();
|
||||
cr.$dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const TokenUsageIndicator = GObject.registerClass(
|
||||
class TokenUsageIndicator extends PanelMenu.Button {
|
||||
constructor() {
|
||||
super(0.0, 'OpenCode Token Usage');
|
||||
|
||||
this.add_style_class_name('opencode-token-usage');
|
||||
this._prefix = new St.Label({
|
||||
text: 'tok',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
style_class: 'opencode-token-usage-label',
|
||||
});
|
||||
this._graph = new TokenUsageGraph();
|
||||
this._value = new St.Label({
|
||||
text: '0',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
style_class: 'opencode-token-usage-label',
|
||||
});
|
||||
this.add_child(this._prefix);
|
||||
this.add_child(this._graph);
|
||||
this.add_child(this._value);
|
||||
|
||||
this._refresh();
|
||||
this._timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, REFRESH_SECONDS, () => {
|
||||
this._refresh();
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
_setClass(nextClass) {
|
||||
for (const item of CLASSES)
|
||||
this.remove_style_class_name(item);
|
||||
|
||||
this.add_style_class_name(`opencode-token-usage-${nextClass || 'normal'}`);
|
||||
}
|
||||
|
||||
_applyPayload(payload) {
|
||||
this._value.text = payload.value || '0';
|
||||
this._graph.setValues(payload.values || []);
|
||||
this.menu.removeAll();
|
||||
for (const line of (payload.tooltip || 'OpenCode token usage').split('\n')) {
|
||||
const item = new PopupMenu.PopupMenuItem(line, {reactive: false, can_focus: false});
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
this._setClass(payload.class || 'normal');
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
let proc;
|
||||
try {
|
||||
proc = Gio.Subprocess.new(
|
||||
[COMMAND],
|
||||
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
||||
);
|
||||
} catch (error) {
|
||||
this._applyPayload({
|
||||
text: 'tok error',
|
||||
tooltip: `Unable to start token usage command: ${error.message}`,
|
||||
class: 'critical',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
proc.communicate_utf8_async(null, null, (subprocess, result) => {
|
||||
try {
|
||||
const [, stdout, stderr] = subprocess.communicate_utf8_finish(result);
|
||||
if (!subprocess.get_successful())
|
||||
throw new Error(stderr.trim() || `command exited ${subprocess.get_exit_status()}`);
|
||||
|
||||
this._applyPayload(JSON.parse(stdout));
|
||||
} catch (error) {
|
||||
this._applyPayload({
|
||||
text: 'tok error',
|
||||
tooltip: `Unable to read OpenCode token usage: ${error.message}`,
|
||||
class: 'critical',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._timer) {
|
||||
GLib.Source.remove(this._timer);
|
||||
this._timer = null;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
export default class OpenCodeTokenUsageExtension extends Extension {
|
||||
enable() {
|
||||
this._indicator = new TokenUsageIndicator();
|
||||
Main.panel.addToStatusArea('opencode-token-usage', this._indicator, 0, 'right');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._indicator?.destroy();
|
||||
this._indicator = null;
|
||||
}
|
||||
}
|
||||
6
gnome-extensions/opencode-token-usage/metadata.json
Normal file
6
gnome-extensions/opencode-token-usage/metadata.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"uuid": "opencode-token-usage@jetpham.github.com",
|
||||
"name": "OpenCode Token Usage",
|
||||
"description": "Shows OpenCode token usage and daily cost estimates in the GNOME top bar.",
|
||||
"shell-version": ["49"]
|
||||
}
|
||||
22
gnome-extensions/opencode-token-usage/stylesheet.css
Normal file
22
gnome-extensions/opencode-token-usage/stylesheet.css
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.opencode-token-usage {
|
||||
spacing: 5px;
|
||||
}
|
||||
|
||||
.opencode-token-usage-label {
|
||||
font-family: "CommitMono Nerd Font", monospace;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.opencode-token-usage-graph {
|
||||
min-width: 96px;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
.opencode-token-usage-warning .opencode-token-usage-label {
|
||||
color: #d29922;
|
||||
}
|
||||
|
||||
.opencode-token-usage-critical .opencode-token-usage-label,
|
||||
.opencode-token-usage-missing .opencode-token-usage-label {
|
||||
color: #f85149;
|
||||
}
|
||||
|
|
@ -100,6 +100,8 @@ in
|
|||
isDefault = true;
|
||||
settings = {
|
||||
"identity.fxaccounts.enabled" = false;
|
||||
"browser.search.suggest.enabled" = false;
|
||||
"browser.urlbar.suggest.searches" = false;
|
||||
"font.default.ja" = "sans-serif";
|
||||
"font.default.ko" = "sans-serif";
|
||||
"font.default.x-unicode" = "sans-serif";
|
||||
|
|
@ -172,10 +174,18 @@ in
|
|||
'';
|
||||
extensions.packages = zenQolExtensions;
|
||||
search = {
|
||||
default = "SearXNG";
|
||||
privateDefault = "SearXNG";
|
||||
default = "Local Google";
|
||||
privateDefault = "Local Google";
|
||||
force = true;
|
||||
engines = {
|
||||
"Local Google" = {
|
||||
urls = [ { template = "http://127.0.0.1:8888/search?q={searchTerms}"; } ];
|
||||
definedAliases = [ "@lg" ];
|
||||
};
|
||||
"Google Web" = {
|
||||
urls = [ { template = "https://www.google.com/search?q={searchTerms}&pws=0&udm=14"; } ];
|
||||
definedAliases = [ "@g" ];
|
||||
};
|
||||
"SearXNG" = {
|
||||
urls = [ { template = "https://search.extremist.software/search?q={searchTerms}"; } ];
|
||||
definedAliases = [ "@s" ];
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ in
|
|||
"auto-move-windows@gnome-shell-extensions.gcampax.github.com"
|
||||
"gnome-shell-extension-maximized-by-default@stiggimy.github.com"
|
||||
"no-titlebar-when-maximized@alec.ninja"
|
||||
"opencode-token-usage@jetpham.github.com"
|
||||
"evil-bit-toggle@jetpham.github.com"
|
||||
"reduced-motion-toggle@jetpham.github.com"
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,14 +10,165 @@ let
|
|||
name = "Jet";
|
||||
email = "jet@extremist.software";
|
||||
sshSigningKey = "~/.ssh/id_ed25519";
|
||||
wrappedOpencode = pkgs.symlinkJoin {
|
||||
name = "opencode-wrapped";
|
||||
paths = [ pkgs.opencode ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
postBuild = ''
|
||||
wrapProgram "$out/bin/opencode" \
|
||||
--set OPENCODE_DB opencode.db \
|
||||
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}"
|
||||
opencodeLibraryPath = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ];
|
||||
opencodeMine = pkgs.writeShellApplication {
|
||||
name = "o";
|
||||
runtimeInputs = [ pkgs.curl ];
|
||||
text = ''
|
||||
export OPENCODE_DB=opencode.db
|
||||
export LD_LIBRARY_PATH="${opencodeLibraryPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
|
||||
if [ "$#" -eq 0 ] && curl \
|
||||
--fail \
|
||||
--silent \
|
||||
--connect-timeout 0.2 \
|
||||
--max-time 0.5 \
|
||||
--output /dev/null \
|
||||
http://127.0.0.1:4096/global/health; then
|
||||
exec ${pkgs.opencode}/bin/opencode attach http://127.0.0.1:4096 --dir "$PWD"
|
||||
fi
|
||||
|
||||
exec ${pkgs.opencode}/bin/opencode "$@"
|
||||
'';
|
||||
};
|
||||
opencodeDefault = pkgs.writeShellApplication {
|
||||
name = "opencode";
|
||||
text = ''
|
||||
export OPENCODE_DB=opencode.db
|
||||
export LD_LIBRARY_PATH="${opencodeLibraryPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
exec ${pkgs.opencode}/bin/opencode "$@"
|
||||
'';
|
||||
};
|
||||
opencodeOriginal = pkgs.writeShellApplication {
|
||||
name = "oo";
|
||||
text = ''
|
||||
export OPENCODE_DB=opencode.db
|
||||
export LD_LIBRARY_PATH="${opencodeLibraryPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
exec ${pkgs.opencode-original}/bin/opencode "$@"
|
||||
'';
|
||||
};
|
||||
opencodeTokenUsage = pkgs.writeShellApplication {
|
||||
name = "opencode-token-usage";
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.gawk
|
||||
pkgs.jq
|
||||
pkgs.sqlite
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
data_home="''${XDG_DATA_HOME:-''${HOME}/.local/share}"
|
||||
db_specs="''${OPENCODE_DBS:-opencode.db}"
|
||||
plan_usd="''${CHATGPT_PLAN_USD:-200}"
|
||||
|
||||
read -r -a db_spec_array <<< "$db_specs"
|
||||
dbs=()
|
||||
missing_dbs=()
|
||||
db_summary=""
|
||||
missing_summary=""
|
||||
for db_spec in "''${db_spec_array[@]}"; do
|
||||
case "$db_spec" in
|
||||
/*) db="$db_spec" ;;
|
||||
*) db="$data_home/opencode/$db_spec" ;;
|
||||
esac
|
||||
|
||||
if [ -r "$db" ]; then
|
||||
dbs+=("$db")
|
||||
db_name="''${db##*/}"
|
||||
if [ -n "$db_summary" ]; then
|
||||
db_summary="$db_summary, $db_name"
|
||||
else
|
||||
db_summary="$db_name"
|
||||
fi
|
||||
else
|
||||
missing_dbs+=("$db")
|
||||
db_name="''${db##*/}"
|
||||
if [ -n "$missing_summary" ]; then
|
||||
missing_summary="$missing_summary, $db_name"
|
||||
else
|
||||
missing_summary="$db_name"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "''${#dbs[@]}" -eq 0 ]; then
|
||||
jq -cn --arg text "tok 0" --arg tooltip "OpenCode token DBs not found: $missing_summary" '{ text: $text, value: "0", values: [], tooltip: $tooltip, class: "missing" }'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
now_ms=$(( $(date +%s) * 1000 ))
|
||||
start_ms=$(( $(date -d 'today 00:00' +%s) * 1000 ))
|
||||
|
||||
sessions=0
|
||||
input=0
|
||||
output=0
|
||||
reasoning=0
|
||||
cache_read=0
|
||||
cache_write=0
|
||||
for db in "''${dbs[@]}"; do
|
||||
row=$(sqlite3 -separator '|' "$db" "
|
||||
SELECT
|
||||
COUNT(*),
|
||||
COALESCE(SUM(tokens_input), 0),
|
||||
COALESCE(SUM(tokens_output), 0),
|
||||
COALESCE(SUM(tokens_reasoning), 0),
|
||||
COALESCE(SUM(tokens_cache_read), 0),
|
||||
COALESCE(SUM(tokens_cache_write), 0)
|
||||
FROM session
|
||||
WHERE time_created >= $start_ms;
|
||||
")
|
||||
IFS='|' read -r db_sessions db_input db_output db_reasoning db_cache_read db_cache_write <<< "$row"
|
||||
sessions=$(( sessions + db_sessions ))
|
||||
input=$(( input + db_input ))
|
||||
output=$(( output + db_output ))
|
||||
reasoning=$(( reasoning + db_reasoning ))
|
||||
cache_read=$(( cache_read + db_cache_read ))
|
||||
cache_write=$(( cache_write + db_cache_write ))
|
||||
done
|
||||
|
||||
billable=$(( input + output + reasoning ))
|
||||
with_cache=$(( billable + cache_read + cache_write ))
|
||||
cost=$(awk -v input="$input" -v output="$output" -v reasoning="$reasoning" -v cache_read="$cache_read" -v cache_write="$cache_write" 'BEGIN { printf "%.2f", (input * 0.25 + (output + reasoning) * 2.00 + cache_read * 0.025 + cache_write * 0.25) / 1000000 }')
|
||||
plan_pct=$(awk -v cost="$cost" -v plan="$plan_usd" 'BEGIN { if (plan > 0) printf "%.1f", cost / plan * 100; else printf "0.0" }')
|
||||
|
||||
short_tokens() {
|
||||
awk -v n="$1" 'BEGIN { if (n >= 1000000000) printf "%.1fB", n / 1000000000; else if (n >= 1000000) printf "%.1fM", n / 1000000; else if (n >= 1000) printf "%.1fk", n / 1000; else printf "%d", n }'
|
||||
}
|
||||
|
||||
graph=()
|
||||
for ((i = 0; i < 24; i++)); do
|
||||
graph[i]=0
|
||||
done
|
||||
for db in "''${dbs[@]}"; do
|
||||
values=$(sqlite3 -separator '|' -noheader "$db" "
|
||||
WITH RECURSIVE buckets(start_ms, stop_ms, i) AS (
|
||||
SELECT (($now_ms / 3600000) - 23) * 3600000, (($now_ms / 3600000) - 22) * 3600000, 0
|
||||
UNION ALL
|
||||
SELECT start_ms + 3600000, stop_ms + 3600000, i + 1 FROM buckets WHERE i < 23
|
||||
)
|
||||
SELECT buckets.i, COALESCE(SUM(COALESCE(tokens_input, 0) + COALESCE(tokens_output, 0) + COALESCE(tokens_reasoning, 0)), 0)
|
||||
FROM buckets
|
||||
LEFT JOIN session ON session.time_created >= buckets.start_ms AND session.time_created < buckets.stop_ms
|
||||
GROUP BY buckets.i
|
||||
ORDER BY buckets.i;
|
||||
")
|
||||
while IFS='|' read -r index value; do
|
||||
if [ -n "$index" ]; then
|
||||
graph[index]=$(( graph[index] + value ))
|
||||
fi
|
||||
done <<< "$values"
|
||||
done
|
||||
graph_values=$(printf '%s\n' "''${graph[@]}" | jq -Rcs 'split("\n") | map(select(length > 0) | tonumber)')
|
||||
billable_short=$(short_tokens "$billable")
|
||||
text="tok $billable_short"
|
||||
tooltip=$(printf 'OpenCode tokens today\nSources: %s\nGraph: last 24 hourly billable-token buckets\nSessions: %s\nBillable excl. cache: %s\nIncluding cache reads: %s\nInput: %s\nOutput: %s\nReasoning: %s\nCache read: %s\nEstimated GPT-5.5 Fast cost: $%s (%s%% of $%s)\nChatGPT plan limits: not exposed locally; this is an API-equivalent estimate.' "$db_summary" "$sessions" "$billable" "$with_cache" "$input" "$output" "$reasoning" "$cache_read" "$cost" "$plan_pct" "$plan_usd")
|
||||
if [ "''${#missing_dbs[@]}" -gt 0 ]; then
|
||||
tooltip=$(printf '%s\nMissing sources: %s' "$tooltip" "$missing_summary")
|
||||
fi
|
||||
class=$(awk -v pct="$plan_pct" 'BEGIN { if (pct >= 80) print "critical"; else if (pct >= 50) print "warning"; else print "normal" }')
|
||||
|
||||
jq -cn --arg text "$text" --arg value "$billable_short" --arg tooltip "$tooltip" --arg class "$class" --argjson values "$graph_values" '{ text: $text, value: $value, values: $values, tooltip: $tooltip, class: $class }'
|
||||
'';
|
||||
};
|
||||
greptileSkills = pkgs.fetchFromGitHub {
|
||||
|
|
@ -491,10 +642,13 @@ in
|
|||
inthAgentSkills
|
||||
name
|
||||
nasaApodWallpaper
|
||||
opencodeDefault
|
||||
opencodeMine
|
||||
opencodeOriginal
|
||||
opencodeTokenUsage
|
||||
signalStartup
|
||||
sshPublicKeys
|
||||
sshSigningKey
|
||||
wrappedOpencode
|
||||
zenStartup
|
||||
zellijNewTabZoxide
|
||||
zellijPersistentSession
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
"--no-performance-crux"
|
||||
];
|
||||
enabled = true;
|
||||
env = {
|
||||
environment = {
|
||||
CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS = "1";
|
||||
NO_UPDATE_NOTIFIER = "1";
|
||||
NPM_CONFIG_AUDIT = "false";
|
||||
|
|
@ -84,6 +84,7 @@
|
|||
- If there is no `flake.nix` and the tool is only needed temporarily, prefer `nix shell nixpkgs#<pkg> -c <command>`.
|
||||
- For persistent tools, prefer declarative Nix configuration.
|
||||
- Prefer `direnv` or `nix develop` before deciding a tool is missing.
|
||||
- Do not put temporary code work, clones, generated project files, or Git worktrees under `/tmp`; use `~/Documents/tmp` instead so work is less likely to be cleared.
|
||||
- Never run `nixos-rebuild`, `nh os switch`, `nhs`, or other system switch commands unless explicitly asked.
|
||||
'';
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,24 @@ let
|
|||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
|
||||
opencodeTokenUsageExtension = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "gnome-shell-extension-opencode-token-usage";
|
||||
version = "1";
|
||||
src = ../gnome-extensions/opencode-token-usage;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
substituteInPlace extension.js \
|
||||
--replace-fail @opencodeTokenUsage@ ${homeLib.opencodeTokenUsage}
|
||||
|
||||
mkdir -p "$out/share/gnome-shell/extensions/opencode-token-usage@jetpham.github.com"
|
||||
cp -r . "$out/share/gnome-shell/extensions/opencode-token-usage@jetpham.github.com"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
|
|
@ -116,7 +134,9 @@ in
|
|||
claude-code
|
||||
codex
|
||||
ffmpeg-full
|
||||
homeLib.wrappedOpencode
|
||||
homeLib.opencodeDefault
|
||||
homeLib.opencodeMine
|
||||
homeLib.opencodeOriginal
|
||||
inputs.t3code.packages.${pkgs.stdenv.hostPlatform.system}.t3code-nightly
|
||||
skills
|
||||
homeLib.zellijNewTabZoxide
|
||||
|
|
@ -190,6 +210,7 @@ in
|
|||
tailscaleQsGnome49
|
||||
gnomeExtensions.wifi-qrcode
|
||||
evilBitToggleExtension
|
||||
opencodeTokenUsageExtension
|
||||
reducedMotionToggleExtension
|
||||
|
||||
nerd-fonts.commit-mono
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@
|
|||
"dr" = "direnv reload";
|
||||
"da" = "direnv allow";
|
||||
"nfu" = "nix flake update";
|
||||
"o" = "opencode";
|
||||
".." = "z ..";
|
||||
j = "jj";
|
||||
jgf = "jj git fetch";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue