sakuraai 0.0.3 → 0.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.
Files changed (2) hide show
  1. package/dist/index.js +65 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -379,16 +379,16 @@ function findFile(sessionId) {
379
379
  }
380
380
  function readMessages(file) {
381
381
  const msgs = [];
382
- let startSecs = null;
382
+ let lastUserSecs = null;
383
383
  for (const entry of jsonlEntries(file)) {
384
384
  if (entry?.isSidechain === true) continue;
385
385
  const tsStr = entry?.timestamp ?? "";
386
386
  const tsSecs = parseIsoUnix(tsStr);
387
- if (startSecs == null) startSecs = tsSecs;
388
387
  if (entry?.type === "user") {
389
388
  if (entry?.userType !== "external") continue;
390
389
  const text = extractTextFromContent(entry?.message?.content);
391
390
  if (text) {
391
+ lastUserSecs = tsSecs;
392
392
  msgs.push({
393
393
  id: entry?.uuid ?? "",
394
394
  role: "user",
@@ -400,15 +400,16 @@ function readMessages(file) {
400
400
  } else if (entry?.type === "assistant") {
401
401
  const { text, hasTools } = extractAssistantText(entry?.message?.content);
402
402
  if (!text) continue;
403
- const inputTokens = entry?.message?.usage?.input_tokens;
404
- const elapsed = startSecs != null && tsSecs != null ? Math.max(0, tsSecs - startSecs) : void 0;
403
+ const u = entry?.message?.usage;
404
+ const total = (u?.input_tokens ?? 0) + (u?.cache_read_input_tokens ?? 0) + (u?.cache_creation_input_tokens ?? 0);
405
+ const elapsed = lastUserSecs != null && tsSecs != null ? Math.max(0, tsSecs - lastUserSecs) : void 0;
405
406
  msgs.push({
406
407
  id: entry?.uuid ?? "",
407
408
  role: "assistant",
408
409
  content: text,
409
410
  timestamp: tsStr,
410
411
  hasToolUse: hasTools,
411
- inputTokens: typeof inputTokens === "number" ? inputTokens : void 0,
412
+ inputTokens: total > 0 ? total : void 0,
412
413
  elapsedSecs: elapsed
413
414
  });
414
415
  }
@@ -431,31 +432,62 @@ function send(sessionId, message, onData) {
431
432
  return new Promise((resolve) => {
432
433
  const bin = process.env.CLAUDE_BIN || "claude";
433
434
  const cwd = sessionCwd(sessionId);
434
- const child = spawn(bin, ["--resume", sessionId, "-p", message], {
435
- cwd: cwd && fs3.existsSync(cwd) ? cwd : void 0,
436
- stdio: ["ignore", "pipe", "pipe"]
437
- });
438
- let out = "";
435
+ const child = spawn(
436
+ bin,
437
+ [
438
+ "--resume",
439
+ sessionId,
440
+ "-p",
441
+ message,
442
+ "--output-format",
443
+ "stream-json",
444
+ "--include-partial-messages",
445
+ "--verbose"
446
+ ],
447
+ {
448
+ cwd: cwd && fs3.existsSync(cwd) ? cwd : void 0,
449
+ stdio: ["ignore", "pipe", "pipe"]
450
+ }
451
+ );
452
+ let buf = "";
453
+ let finalText = "";
439
454
  let err = "";
440
455
  child.stdout.on("data", (d) => {
441
- out += d.toString();
442
- onData?.(d.toString());
456
+ buf += d.toString();
457
+ let nl;
458
+ while ((nl = buf.indexOf("\n")) >= 0) {
459
+ const line = buf.slice(0, nl);
460
+ buf = buf.slice(nl + 1);
461
+ if (!line.trim()) continue;
462
+ try {
463
+ const delta = textDelta(JSON.parse(line));
464
+ if (delta) {
465
+ finalText += delta;
466
+ onData?.(delta);
467
+ }
468
+ } catch {
469
+ }
470
+ }
443
471
  });
444
472
  child.stderr.on("data", (d) => err += d.toString());
445
- child.on(
446
- "error",
447
- (e) => resolve({ ok: false, output: "", error: e.message })
448
- );
473
+ child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
449
474
  child.on(
450
475
  "close",
451
476
  (code) => resolve({
452
477
  ok: code === 0,
453
- output: out.trim(),
478
+ output: finalText.trim(),
454
479
  error: code === 0 ? void 0 : err.trim() || `exited ${code}`
455
480
  })
456
481
  );
457
482
  });
458
483
  }
484
+ function textDelta(ev) {
485
+ const obj = ev?.type === "stream_event" ? ev.event : ev;
486
+ if (obj?.type === "content_block_delta" && obj?.delta?.type === "text_delta") {
487
+ return obj.delta.text ?? null;
488
+ }
489
+ return null;
490
+ }
459
491
  var PROJECTS_DIR;
460
492
  var init_claude = __esm({
461
493
  "src/runtime/claude.ts"() {
@@ -540,6 +572,7 @@ function readMessages2(file) {
540
572
  return [];
541
573
  }
542
574
  const msgs = [];
575
+ let lastUserSecs = null;
543
576
  for (const line of content.split("\n")) {
544
577
  if (!line.trim()) continue;
545
578
  let entry;
@@ -559,7 +592,16 @@ function readMessages2(file) {
559
592
  const text = part?.text ?? "";
560
593
  if (!text) continue;
561
594
  const ts = entry?.timestamp ?? "";
562
- msgs.push({ id: ts, role, content: text, timestamp: ts, hasToolUse: false });
595
+ const tsSecs = parseIsoUnix(ts);
596
+ if (role === "user") lastUserSecs = tsSecs;
597
+ msgs.push({
598
+ id: ts,
599
+ role,
600
+ content: text,
601
+ timestamp: ts,
602
+ hasToolUse: false,
603
+ elapsedSecs: role === "assistant" && lastUserSecs != null && tsSecs != null ? Math.max(0, tsSecs - lastUserSecs) : void 0
604
+ });
563
605
  }
564
606
  return msgs;
565
607
  }
@@ -683,8 +725,8 @@ async function messages3(sessionId) {
683
725
  } catch {
684
726
  }
685
727
  }
686
- const startMs = msgRows[0]?.time_created ?? 0;
687
728
  const out = [];
729
+ let lastUserMs = null;
688
730
  for (const m of msgRows) {
689
731
  let v;
690
732
  try {
@@ -697,13 +739,15 @@ async function messages3(sessionId) {
697
739
  const text = (partsMap.get(m.id) ?? []).join("");
698
740
  if (!text) continue;
699
741
  const mtime = Math.floor(m.time_created / 1e3);
742
+ if (role === "user") lastUserMs = m.time_created;
700
743
  out.push({
701
744
  id: m.id,
702
745
  role,
703
746
  content: text,
704
747
  timestamp: chronoIso(mtime),
705
748
  hasToolUse: false,
706
- elapsedSecs: role === "assistant" ? Math.floor((m.time_created - startMs) / 1e3) : void 0
749
+ // Per-turn duration: assistant time the user msg that started it.
750
+ elapsedSecs: role === "assistant" && lastUserMs != null ? Math.max(0, Math.floor((m.time_created - lastUserMs) / 1e3)) : void 0
707
751
  });
708
752
  }
709
753
  return out;
@@ -1746,7 +1790,7 @@ var init_pair = __esm({
1746
1790
  import { Command } from "commander";
1747
1791
 
1748
1792
  // src/version.ts
1749
- var VERSION = "0.0.3";
1793
+ var VERSION = "0.0.4";
1750
1794
 
1751
1795
  // src/index.ts
1752
1796
  init_config();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sakuraai",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Sakura Agent CLI + local runtime for managing AI coding sessions (Claude Code, Codex, OpenCode) and reaching them privately from the Sakura mobile app over Tailscale",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",