drexler 0.2.16 → 0.2.18

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/README.md CHANGED
@@ -89,9 +89,9 @@ rm -rf ~/.config/drexler # optional: wipe stored key + settings
89
89
 
90
90
  ### Interactive UI
91
91
 
92
- Drexler runs as an Ink terminal UI when both stdin and stdout are TTYs. The normal launch shows one integrated startup panel with the mascot, tips, a **Mood** readout, and the **Drexler Deal Desk**. Short terminals automatically suppress oversized startup chrome so the chat stays usable.
92
+ Drexler runs as an Ink terminal UI when both stdin and stdout are TTYs. The normal launch shows one integrated startup panel with the mascot, tips, a **Mood** readout, and the **Drexler Deal Desk**. After startup, that dashboard remains the primary top chrome. Short terminals automatically suppress oversized startup chrome so the chat stays usable.
93
93
 
94
- After startup, the pet UI adapts to the terminal instead of disappearing. Wide terminals show the animated **Drexler Pet Desk** as a right-side panel. Medium terminals show a compact pet panel below the Deal Desk. Tiny terminals show a one-line pet ticker so pet status remains visible without crushing the chat.
94
+ Type `/pet` to toggle pet mode for the current session. Pet mode keeps the top dashboard frame and swaps the normal mascot/tips/deal desk layout for a Drexler scene plus pet stats. `/pet on` and `/pet off` set it explicitly. Pet stats still decay and pet commands still work when the pet dashboard is hidden.
95
95
 
96
96
  The startup panel is designed to stay stable while it boots: the mascot loading bar and Mood gauge animate without changing width, greeting copy is held in a fixed slot, and the Mood and Deal Desk boxes stay aligned when the greeting wraps. After boot, Mood resolves into a rotating Drexler-flavored posture with a short satirical subtext line.
97
97
 
@@ -136,6 +136,7 @@ Keyboard notes:
136
136
  | `/clear` | shred conversation history (system prompt pinned) |
137
137
  | `/exit` | meeting adjourned |
138
138
  | `/synergy` | run a rotating animated morale event |
139
+ | `/pet [on\|off]` | toggle pet dashboard mode for this session |
139
140
  | `/feed` | feed Drexler a deal memo |
140
141
  | `/play` | corporate synergy game with Drexler |
141
142
  | `/work` | Drexler grinds the deal pipeline |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drexler",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "CLI chat with Drexler, a corporate-executive AI persona built on OpenRouter Gemma 4 31B.",
5
5
  "license": "MIT",
6
6
  "author": "showOS",
package/src/commands.ts CHANGED
@@ -47,6 +47,7 @@ const HELP_TEXT = `New memo to staff! Drexler permit following directives:
47
47
  /clear - shred all documents (reset history)
48
48
  /exit - meeting adjourned
49
49
  /synergy - SYNERGY!
50
+ /pet [on|off] - toggle pet dashboard mode
50
51
  /feed - feed Drexler a deal memo
51
52
  /play - corporate synergy game with Drexler
52
53
  /work - Drexler grinds the deal pipeline
@@ -86,6 +87,7 @@ export const COMMAND_PALETTE: ReadonlyArray<SlashCommand> = [
86
87
  { name: "/clear", description: "Reset conversation", group: "directives" },
87
88
  { name: "/exit", description: "Adjourn meeting", group: "directives" },
88
89
  { name: "/synergy", description: "SYNERGY!", group: "directives" },
90
+ { name: "/pet", description: "Toggle pet dashboard mode", group: "directives" },
89
91
  { name: "/feed", description: "Feed Drexler a deal memo", group: "directives" },
90
92
  { name: "/play", description: "Play with Drexler", group: "directives" },
91
93
  { name: "/work", description: "Drexler grinds deals", group: "directives" },
@@ -369,6 +371,7 @@ export function dispatch(input: string, ctx: CommandContext): CommandAction {
369
371
  );
370
372
  return { type: "continue" };
371
373
 
374
+ case "pet":
372
375
  case "feed":
373
376
  case "play":
374
377
  case "work":
package/src/index.ts CHANGED
@@ -57,6 +57,7 @@ Slash commands inside REPL:
57
57
  /clear reset conversation
58
58
  /exit exit
59
59
  /synergy SYNERGY!
60
+ /pet [on|off] toggle pet dashboard mode
60
61
  /feed feed Drexler a deal memo
61
62
  /play corporate synergy game (flexing included)
62
63
  /work Drexler grinds the pipeline
package/src/ui/App.tsx CHANGED
@@ -31,8 +31,6 @@ import {
31
31
  CompactPetPanel,
32
32
  COMPACT_PET_PANEL_MIN_WIDTH,
33
33
  COMPACT_PET_PANEL_ROWS,
34
- PetPanel,
35
- PET_PANEL_WIDTH,
36
34
  TINY_PET_PANEL_ROWS,
37
35
  type Environment,
38
36
  } from "./PetPanel.tsx";
@@ -93,10 +91,6 @@ import {
93
91
  import { getActiveTheme } from "./themes.ts";
94
92
 
95
93
  const TRANSCRIPT_CHROME_ROWS = 12;
96
- const PET_PANEL_MIN_MAIN_COLUMNS = 75;
97
- const PET_PANEL_GAP_COLUMNS = 1;
98
- const PET_PANEL_MIN_COLUMNS =
99
- PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS + PET_PANEL_MIN_MAIN_COLUMNS;
100
94
 
101
95
  export function transcriptRowsForTerminalRows(rows: number): number {
102
96
  return Math.max(1, Math.min(24, rows - TRANSCRIPT_CHROME_ROWS));
@@ -198,6 +192,7 @@ interface AppProps {
198
192
  fetchFn?: FetchFn;
199
193
  greeting?: string;
200
194
  showIntroChrome?: boolean;
195
+ introInitiallyDone?: boolean;
201
196
  }
202
197
 
203
198
  export function App({
@@ -207,6 +202,7 @@ export function App({
207
202
  fetchFn,
208
203
  greeting,
209
204
  showIntroChrome = false,
205
+ introInitiallyDone = false,
210
206
  }: AppProps) {
211
207
  const { exit } = useApp();
212
208
  const { stdout } = useStdout();
@@ -228,36 +224,38 @@ export function App({
228
224
  const mode = useMemo(() => pickLayout(cols), [cols]);
229
225
  const chromeWidth = useMemo(() => Math.max(1, cols), [cols]);
230
226
  const isCompact = mode === "very-narrow";
231
- const [introDone, setIntroDone] = useState(false);
232
- const integratedIntro =
233
- showIntroChrome &&
234
- typeof greeting === "string" &&
235
- rows >= 32 &&
236
- !introDone;
237
- const showPetSidePanel = cols >= PET_PANEL_MIN_COLUMNS && !integratedIntro;
238
- const showCompactPetPanel = !showPetSidePanel && !integratedIntro;
239
- const compactPetRowBudget = showCompactPetPanel
227
+ const [introDone, setIntroDone] = useState(introInitiallyDone);
228
+ const dashboardAllowed = showIntroChrome && typeof greeting === "string";
229
+ const showFullDashboard = dashboardAllowed && rows >= 32;
230
+ const introActive = showFullDashboard && !introDone;
231
+ const [petMode, setPetMode] = useState(false);
232
+ const petModeRef = useRef(false);
233
+ const showFallbackPetPanel = petMode && !showFullDashboard;
234
+ const fallbackPetRowBudget = showFallbackPetPanel
240
235
  ? cols >= COMPACT_PET_PANEL_MIN_WIDTH
241
236
  ? COMPACT_PET_PANEL_ROWS
242
237
  : TINY_PET_PANEL_ROWS
243
238
  : 0;
244
- const petPanelReservedWidth = showPetSidePanel
245
- ? PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS
246
- : 0;
247
- const contentWidth = showPetSidePanel
248
- ? Math.max(1, cols - petPanelReservedWidth)
249
- : chromeWidth;
239
+ const contentWidth = chromeWidth;
250
240
  const contentInputWidth = Math.max(1, contentWidth);
251
241
  const contentStatusWidth = Math.max(1, contentInputWidth - 2);
252
- const introRowBudget =
253
- integratedIntro ? (chromeWidth >= 112 ? 14 : chromeWidth >= 72 ? 26 : 6) : 0;
242
+ const dashboardRowBudget =
243
+ showFullDashboard
244
+ ? chromeWidth >= 112
245
+ ? 14
246
+ : chromeWidth >= 72
247
+ ? 26
248
+ : 6
249
+ : 0;
254
250
  const maxTranscriptRows = useMemo(
255
251
  () =>
256
252
  Math.max(
257
253
  1,
258
- transcriptRowsForTerminalRows(rows) - introRowBudget - compactPetRowBudget,
254
+ transcriptRowsForTerminalRows(rows) -
255
+ dashboardRowBudget -
256
+ fallbackPetRowBudget,
259
257
  ),
260
- [compactPetRowBudget, introRowBudget, rows],
258
+ [dashboardRowBudget, fallbackPetRowBudget, rows],
261
259
  );
262
260
 
263
261
  const [items, setItems] = useState<ChatItem[]>([]);
@@ -315,7 +313,7 @@ export function App({
315
313
  }, []);
316
314
  const intro = useIntroAnimation(
317
315
  chromeWidth,
318
- integratedIntro,
316
+ introActive,
319
317
  handleIntroComplete,
320
318
  );
321
319
 
@@ -328,12 +326,7 @@ export function App({
328
326
  const petActivityTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
329
327
  const petDecayTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
330
328
 
331
- const petEnv = useMemo((): Environment => {
332
- const h = new Date().getHours();
333
- if (h >= 9 && h < 18) return "office";
334
- if (h >= 6 && h < 23) return "home";
335
- return "outdoors";
336
- }, []);
329
+ const petEnv: Environment = "office";
337
330
 
338
331
  const PET_MESSAGES = useMemo(() => ({
339
332
  feed: [
@@ -391,6 +384,10 @@ export function App({
391
384
  },
392
385
  [],
393
386
  );
387
+ const setDashboardPetMode = useCallback((next: boolean) => {
388
+ petModeRef.current = next;
389
+ setPetMode(next);
390
+ }, []);
394
391
  const updatePetStats = useCallback((updater: (stats: PetStats) => PetStats) => {
395
392
  setPetStats((stats) => {
396
393
  const next = updater(stats);
@@ -687,6 +684,38 @@ export function App({
687
684
  runSynergyEvent();
688
685
  return;
689
686
  }
687
+ if (slashCommand === "/pet") {
688
+ const arg = lower.slice("/pet".length).trim();
689
+ if (arg === "") {
690
+ const next = !petModeRef.current;
691
+ setDashboardPetMode(next);
692
+ addItem(
693
+ "system",
694
+ next
695
+ ? "Pet dashboard enabled. Deal desk converted to habitat."
696
+ : "Pet dashboard disabled. Deal desk restored.",
697
+ );
698
+ return;
699
+ }
700
+ if (arg === "on" || arg === "off") {
701
+ const next = arg === "on";
702
+ const changed = petModeRef.current !== next;
703
+ setDashboardPetMode(next);
704
+ addItem(
705
+ "system",
706
+ changed
707
+ ? next
708
+ ? "Pet dashboard enabled. Deal desk converted to habitat."
709
+ : "Pet dashboard disabled. Deal desk restored."
710
+ : next
711
+ ? "Pet dashboard already enabled."
712
+ : "Pet dashboard already disabled.",
713
+ );
714
+ return;
715
+ }
716
+ addItem("system", "Usage: /pet, /pet on, or /pet off.");
717
+ return;
718
+ }
690
719
  const isPetCommand =
691
720
  slashCommand === "/feed" ||
692
721
  slashCommand === "/play" ||
@@ -861,11 +890,11 @@ export function App({
861
890
  config,
862
891
  activeTheme,
863
892
  model,
864
- petStats,
865
893
  PET_MESSAGES,
866
894
  removeLastAssistantItem,
867
895
  runLLM,
868
896
  runSynergyEvent,
897
+ setDashboardPetMode,
869
898
  triggerExit,
870
899
  triggerPetActivity,
871
900
  updatePetStats,
@@ -1123,15 +1152,15 @@ export function App({
1123
1152
  const isBusy =
1124
1153
  requestInFlight || streaming !== null || thinking !== null || synergyEvent !== null;
1125
1154
  const headerStatus = isBusy ? "streaming" : deskStatus;
1126
- const renderDealDeskHeader = (width: number) => (
1155
+ const renderDealDeskHeader = (width: number, marginBottom = 1) => (
1127
1156
  <DealDeskHeader
1128
1157
  mood={mood}
1129
1158
  messageCount={msgCount}
1130
1159
  status={headerStatus}
1131
1160
  compact={isCompact}
1132
- notice={!integratedIntro ? deskNotice ?? undefined : undefined}
1161
+ notice={!introActive ? deskNotice ?? undefined : undefined}
1133
1162
  maxWidth={Math.max(1, width)}
1134
- marginBottom={integratedIntro ? 0 : 1}
1163
+ marginBottom={marginBottom}
1135
1164
  />
1136
1165
  );
1137
1166
  const dealDeskHeader = renderDealDeskHeader(chromeWidth);
@@ -1148,24 +1177,26 @@ export function App({
1148
1177
  return (
1149
1178
  <ThemeProvider value={activeTheme}>
1150
1179
  <Box flexDirection="column">
1151
- {integratedIntro ? (
1180
+ {showFullDashboard && typeof greeting === "string" ? (
1152
1181
  <Box marginBottom={1}>
1153
1182
  <MascotDashboard
1154
1183
  greeting={greeting}
1155
1184
  width={chromeWidth}
1156
1185
  mood={mood}
1157
- bootProgress={intro.progress}
1158
- state={intro.state}
1159
- bar={intro.bar}
1160
- barColor={introBarColor}
1161
- mascotStatus={intro.status}
1162
- dealDesk={renderDealDeskHeader}
1186
+ mode={petMode && !introActive ? "pet" : "normal"}
1187
+ petStats={petStats}
1188
+ petActivity={petActivity}
1189
+ petEnv={petEnv}
1190
+ petPaused={isBusy}
1191
+ bootProgress={introActive ? intro.progress : 1}
1192
+ state={introActive ? intro.state : undefined}
1193
+ bar={introActive ? intro.bar : undefined}
1194
+ barColor={introActive ? introBarColor : undefined}
1195
+ mascotStatus={introActive ? intro.status : undefined}
1196
+ dealDesk={(width) => renderDealDeskHeader(width, 0)}
1163
1197
  />
1164
1198
  </Box>
1165
- ) : (
1166
- dealDeskHeader
1167
- )}
1168
- {showCompactPetPanel && (
1199
+ ) : showFallbackPetPanel ? (
1169
1200
  <Box marginBottom={1}>
1170
1201
  <CompactPetPanel
1171
1202
  stats={petStats}
@@ -1175,18 +1206,10 @@ export function App({
1175
1206
  width={chromeWidth}
1176
1207
  />
1177
1208
  </Box>
1209
+ ) : (
1210
+ dealDeskHeader
1178
1211
  )}
1179
1212
  <Box flexDirection="row" alignItems="flex-start">
1180
- {showPetSidePanel && (
1181
- <Box marginRight={PET_PANEL_GAP_COLUMNS}>
1182
- <PetPanel
1183
- stats={petStats}
1184
- activity={petActivity}
1185
- env={petEnv}
1186
- isPaused={isBusy}
1187
- />
1188
- </Box>
1189
- )}
1190
1213
  <Box flexDirection="column" flexGrow={1}>
1191
1214
  <TranscriptViewport
1192
1215
  items={items}