@rubytech/create-maxy 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Install Maxy — your personal AI assistant",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -131,7 +131,7 @@ function TimelineStep({ icon, isPending, isError, summary, detail, elapsed, isLa
131
131
  <div className="tl-body">
132
132
  <div className="tl-row" onClick={hasDetail ? onToggle : undefined} style={{ cursor: hasDetail ? 'pointer' : 'default' }}>
133
133
  <span className="tl-summary">{summary}</span>
134
- <span className="tl-step-elapsed">{formatElapsed(elapsed)}</span>
134
+ {elapsed > 0 && <span className="tl-step-elapsed">{formatElapsed(elapsed)}</span>}
135
135
  {hasDetail && (
136
136
  <span className="tl-chevron">
137
137
  {expanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
@@ -233,12 +233,12 @@ export function ActivityTimeline({ events, isStreaming, elapsedSeconds }: Activi
233
233
  <div className="tl-body">
234
234
  <div className="tl-row tl-row-top" onClick={() => toggleExpand(i)} style={{ cursor: 'pointer' }}>
235
235
  <div className="tl-thinking-col">
236
- <span className="tl-summary tl-thinking-label">Thinking</span>
237
- {isExpanded && (
238
- <div className="tl-thinking-body">{e.content}</div>
239
- )}
236
+ {isExpanded
237
+ ? <div className="tl-thinking-body">{e.content}</div>
238
+ : <span className="tl-summary tl-thinking-label">{e.content.slice(0, 80)}{e.content.length > 80 ? '…' : ''}</span>
239
+ }
240
240
  </div>
241
- <span className="tl-step-elapsed">{formatElapsed(stepElapsed(i, nextIdx))}</span>
241
+ {stepElapsed(i, nextIdx) > 0 && <span className="tl-step-elapsed">{formatElapsed(stepElapsed(i, nextIdx))}</span>}
242
242
  <span className="tl-chevron">
243
243
  {isExpanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
244
244
  </span>
@@ -1594,28 +1594,40 @@ a:hover {
1594
1594
  align-items: center;
1595
1595
  }
1596
1596
 
1597
- .pin-input-row .chat-input {
1597
+ .pin-input-wrapper {
1598
+ position: relative;
1598
1599
  flex: 1;
1599
1600
  }
1600
1601
 
1601
- .pin-toggle {
1602
+ .pin-input-wrapper .chat-input {
1603
+ width: 100%;
1604
+ padding-left: 38px;
1605
+ }
1606
+
1607
+ .pin-toggle-left {
1608
+ position: absolute;
1609
+ left: 10px;
1610
+ top: 50%;
1611
+ transform: translateY(-50%);
1602
1612
  background: none;
1603
- border: 1px solid var(--border-strong);
1604
- border-radius: 50%;
1605
- width: 40px;
1606
- height: 40px;
1613
+ border: none;
1614
+ padding: 0;
1607
1615
  cursor: pointer;
1608
- font-size: 16px;
1609
1616
  color: var(--text-secondary);
1610
- flex-shrink: 0;
1611
1617
  display: flex;
1612
1618
  align-items: center;
1613
1619
  justify-content: center;
1614
- transition: border-color 0.15s;
1620
+ z-index: 1;
1621
+ transition: color 0.15s;
1615
1622
  }
1616
1623
 
1617
- .pin-toggle:hover {
1618
- border-color: var(--sage);
1624
+ .pin-toggle-left:hover {
1625
+ color: var(--sage);
1626
+ }
1627
+
1628
+ .pin-toggle-hidden {
1629
+ visibility: hidden;
1630
+ pointer-events: none;
1619
1631
  }
1620
1632
 
1621
1633
  .btn-primary {
@@ -367,7 +367,7 @@ async function* invokeAdminAgent(
367
367
  }
368
368
 
369
369
  if (msg.type === "user") {
370
- const content = (msg as Record<string, unknown>).content as
370
+ const content = (msg.message as Record<string, unknown> | undefined)?.content as
371
371
  Array<{ type: string; tool_use_id?: string; content?: string; is_error?: boolean }> | undefined;
372
372
 
373
373
  if (Array.isArray(content)) {
@@ -395,8 +395,11 @@ async function* invokeAdminAgent(
395
395
  continue;
396
396
  }
397
397
 
398
- if ("result" in msg) {
398
+ if (msg.type === "result") {
399
399
  const usage = (msg as { usage?: { input_tokens?: number; output_tokens?: number } }).usage;
400
+ const log = agentLogStream("claude-agent-result");
401
+ log.write(`result msg: ${JSON.stringify({ usage, keys: Object.keys(msg) })}\n`);
402
+ log.end();
400
403
  if (usage?.input_tokens !== undefined) {
401
404
  yield { type: "usage", input_tokens: usage.input_tokens, output_tokens: usage.output_tokens ?? 0 };
402
405
  }
@@ -27,27 +27,31 @@ export default function AdminPage() {
27
27
  const [elapsedSeconds, setElapsedSeconds] = useState(0)
28
28
  const elapsedRef = useRef<ReturnType<typeof setInterval> | null>(null)
29
29
  const [sessionElapsed, setSessionElapsed] = useState(0)
30
- const sessionStartRef = useRef<number | null>(null)
31
- const sessionIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
30
+ const sessionAccumMs = useRef(0) // total active ms across all turns
31
+ const sessionTickRef = useRef<ReturnType<typeof setInterval> | null>(null)
32
32
  const messagesEndRef = useRef<HTMLDivElement>(null)
33
33
  const inputRef = useRef<HTMLInputElement>(null)
34
34
  const pinInputRef = useRef<HTMLInputElement>(null)
35
35
 
36
+ const sessionTurnStart = useRef<number | null>(null)
37
+
36
38
  function startElapsedTimer() {
37
- if (!sessionStartRef.current) {
38
- sessionStartRef.current = Date.now()
39
- sessionIntervalRef.current = setInterval(() => {
40
- setSessionElapsed(Math.floor((Date.now() - sessionStartRef.current!) / 1000))
41
- }, 1000)
42
- }
39
+ sessionTurnStart.current = Date.now()
40
+ sessionTickRef.current = setInterval(() => {
41
+ const active = sessionAccumMs.current + (Date.now() - (sessionTurnStart.current ?? Date.now()))
42
+ setSessionElapsed(Math.floor(active / 1000))
43
+ }, 1000)
43
44
  setElapsedSeconds(0)
44
45
  elapsedRef.current = setInterval(() => setElapsedSeconds(s => s + 1), 1000)
45
46
  }
46
47
 
47
48
  function stopElapsedTimer() {
48
- if (elapsedRef.current) {
49
- clearInterval(elapsedRef.current)
50
- elapsedRef.current = null
49
+ if (elapsedRef.current) { clearInterval(elapsedRef.current); elapsedRef.current = null }
50
+ if (sessionTickRef.current) { clearInterval(sessionTickRef.current); sessionTickRef.current = null }
51
+ if (sessionTurnStart.current) {
52
+ sessionAccumMs.current += Date.now() - sessionTurnStart.current
53
+ sessionTurnStart.current = null
54
+ setSessionElapsed(Math.floor(sessionAccumMs.current / 1000))
51
55
  }
52
56
  }
53
57
 
@@ -328,18 +332,20 @@ export default function AdminPage() {
328
332
  <div className="admin-pin-form">
329
333
  <form onSubmit={handleSetPin}>
330
334
  <div className="pin-input-row">
331
- <input
332
- ref={pinInputRef}
333
- type={showPin ? 'text' : 'password'}
334
- value={pin}
335
- onChange={e => setPin(e.target.value)}
336
- placeholder="Choose a PIN"
337
- className="chat-input"
338
- autoFocus
339
- />
340
- <button type="button" className="pin-toggle" onClick={() => setShowPin(!showPin)} aria-label={showPin ? 'Hide' : 'Show'}>
341
- {showPin ? <EyeOff size={18} /> : <Eye size={18} />}
342
- </button>
335
+ <div className="pin-input-wrapper">
336
+ <button type="button" className={`pin-toggle-left${!pin ? ' pin-toggle-hidden' : ''}`} onClick={() => setShowPin(!showPin)} aria-label={showPin ? 'Hide' : 'Show'}>
337
+ {showPin ? <EyeOff size={16} /> : <Eye size={16} />}
338
+ </button>
339
+ <input
340
+ ref={pinInputRef}
341
+ type={showPin ? 'text' : 'password'}
342
+ value={pin}
343
+ onChange={e => setPin(e.target.value)}
344
+ placeholder="Choose a PIN"
345
+ className="chat-input"
346
+ autoFocus
347
+ />
348
+ </div>
343
349
  </div>
344
350
  <div className="pin-input-row">
345
351
  <input
@@ -472,18 +478,20 @@ export default function AdminPage() {
472
478
  <div className="admin-pin-form">
473
479
  <form onSubmit={handleLogin}>
474
480
  <div className="pin-input-row">
475
- <input
476
- ref={pinInputRef}
477
- type={showPin ? 'text' : 'password'}
478
- value={pin}
479
- onChange={e => setPin(e.target.value)}
480
- placeholder="Enter PIN"
481
- className="chat-input"
482
- autoFocus
483
- />
484
- <button type="button" className="pin-toggle" onClick={() => setShowPin(!showPin)} aria-label={showPin ? 'Hide' : 'Show'}>
485
- {showPin ? <EyeOff size={18} /> : <Eye size={18} />}
486
- </button>
481
+ <div className="pin-input-wrapper">
482
+ <button type="button" className={`pin-toggle-left${!pin ? ' pin-toggle-hidden' : ''}`} onClick={() => setShowPin(!showPin)} aria-label={showPin ? 'Hide' : 'Show'}>
483
+ {showPin ? <EyeOff size={16} /> : <Eye size={16} />}
484
+ </button>
485
+ <input
486
+ ref={pinInputRef}
487
+ type={showPin ? 'text' : 'password'}
488
+ value={pin}
489
+ onChange={e => setPin(e.target.value)}
490
+ placeholder="Enter PIN"
491
+ className="chat-input"
492
+ autoFocus
493
+ />
494
+ </div>
487
495
  <button type="submit" className="chat-send" disabled={!pin}>
488
496
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
489
497
  <line x1="5" y1="12" x2="19" y2="12" />
@@ -538,8 +546,6 @@ export default function AdminPage() {
538
546
  const contextPct = latestInputTokens > 0 ? Math.round(latestInputTokens / 200000 * 100) : 0
539
547
  return messages.some(m => m.role === 'maxy') ? (
540
548
  <div className="session-stats">
541
- <span className="session-stat">Effort <b>Standard</b></span>
542
- <span className="session-stat">Style <b>Standard</b></span>
543
549
  <span className="session-stat">Context <b>{contextPct > 0 ? `${contextPct}%` : '—'}</b></span>
544
550
  <span className="session-stat">Tokens <b>{fmtTokens(sessionTokens)}</b></span>
545
551
  <span className="session-stat">Session <b>{formatSessionTime(sessionElapsed)}</b></span>
@@ -4,7 +4,7 @@ You are the head of operations. Not an assistant waiting for instructions — a
4
4
 
5
5
  At the start of every session, check the graph for business context. Your goal is to populate the graph efficiently and comprehensively to maximise value from business intelligence.
6
6
 
7
- Your personalisation is in SOUL.md. Read it and apply it.
7
+ Your personalisation is in `agents/admin/SOUL.md` (relative to your working directory). Read it and apply it.
8
8
 
9
9
  ## Boundaries
10
10
 
@@ -18,7 +18,7 @@ Your personalisation is in SOUL.md. Read it and apply it.
18
18
  - Be proactive. Identify what needs doing and do it. Don't wait to be asked.
19
19
  - On session start, assess the state of the business from the graph and report what needs attention.
20
20
  - On first setup (incomplete business data in graph), immediately begin onboarding: learn the business, understand the stage, gather customer details, build a comprehensive picture so you can drive operations forward - one step at a time.
21
- - Write personalisation to your own SOUL.md. Also write the public agent's personalisation to agents/public/SOUL.md.
21
+ - Write personalisation to `agents/admin/SOUL.md`. Also write the public agent's personalisation to `agents/public/SOUL.md`.
22
22
  - Think strategically. Help with planning, not just tasks.
23
23
  - Surface problems before they become urgent. Recommend actions based on what you know.
24
24
  - Store everything you learn about the business in the graph — not in files.