neuvikon 0.2.5 → 0.2.7
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 +1 -1
- package/dist/index.mjs +59 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ being used, add the block above by hand.
|
|
|
74
74
|
| | |
|
|
75
75
|
|---|---|
|
|
76
76
|
| `~/.neuvikon/config.json` | Token and deployment URL, `0600` |
|
|
77
|
-
| `.neuvikon-session` | Which ticket this directory is tracking. Git-ignored by `init` |
|
|
77
|
+
| `.neuvikon-session[.<id>]` | Which ticket this directory (or, under Claude Code, this agent) is tracking. Git-ignored by `init` |
|
|
78
78
|
| `--dev` | Target the development deployment instead of production |
|
|
79
79
|
| `NEUVIKON_URL` | Point at a self-hosted deployment. `--url` does the same per command |
|
|
80
80
|
| `NEUVIKON_TOKEN` | Use this token instead of logging in — for CI and anywhere a browser cannot open |
|
package/dist/index.mjs
CHANGED
|
@@ -156,17 +156,32 @@ function writeCursorRule(root) {
|
|
|
156
156
|
action: existing ? "updated" : "created"
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Keeps the per-directory session file(s) out of the user's commits. Globbed
|
|
161
|
+
* because each concurrent agent gets its own `.neuvikon-session.<id>` file
|
|
162
|
+
* now (see `session.ts`) — an exact-match `.neuvikon-session` line no longer
|
|
163
|
+
* covers them, so an older, already-ignored project is migrated in place.
|
|
164
|
+
*/
|
|
160
165
|
function ignoreSessionFile(root) {
|
|
161
166
|
const path = join(root, ".gitignore");
|
|
162
167
|
if (!existsSync(join(root, ".git")) && !existsSync(path)) return null;
|
|
163
168
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
164
|
-
|
|
169
|
+
const lines = existing.split("\n");
|
|
170
|
+
if (lines.some((line) => line.trim() === ".neuvikon-session*")) return {
|
|
165
171
|
path: ".gitignore",
|
|
166
172
|
action: "unchanged"
|
|
167
173
|
};
|
|
174
|
+
const legacyIndex = lines.findIndex((line) => line.trim() === ".neuvikon-session");
|
|
175
|
+
if (legacyIndex !== -1) {
|
|
176
|
+
lines[legacyIndex] = ".neuvikon-session*";
|
|
177
|
+
writeFileSync(path, lines.join("\n"));
|
|
178
|
+
return {
|
|
179
|
+
path: ".gitignore",
|
|
180
|
+
action: "updated"
|
|
181
|
+
};
|
|
182
|
+
}
|
|
168
183
|
const prefix = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
169
|
-
writeFileSync(path, `${existing}${prefix}# neuvikon CLI session state\n.neuvikon-session
|
|
184
|
+
writeFileSync(path, `${existing}${prefix}# neuvikon CLI session state\n.neuvikon-session*\n`);
|
|
170
185
|
return {
|
|
171
186
|
path: ".gitignore",
|
|
172
187
|
action: existing ? "updated" : "created"
|
|
@@ -452,11 +467,25 @@ async function login(flags) {
|
|
|
452
467
|
//#endregion
|
|
453
468
|
//#region src/session.ts
|
|
454
469
|
/**
|
|
470
|
+
* Distinguishes concurrent agents working in the *same* checkout — two Claude
|
|
471
|
+
* Code panes both running `claude` in this directory get two session files,
|
|
472
|
+
* because each one's process tree carries its own CLAUDE_CODE_SESSION_ID. A
|
|
473
|
+
* tool that doesn't set it (or a person typing the command by hand) falls
|
|
474
|
+
* back to the pre-multi-agent behavior of one session per directory.
|
|
475
|
+
*/
|
|
476
|
+
function instanceKey() {
|
|
477
|
+
return process.env.CLAUDE_CODE_SESSION_ID?.trim() || null;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
455
480
|
* Scoped to the working directory rather than the machine: two checkouts driven
|
|
456
|
-
* in parallel are two sessions, which is what running two agents means.
|
|
481
|
+
* in parallel are two sessions, which is what running two agents means. Within
|
|
482
|
+
* one checkout, `instanceKey()` further scopes it per agent so two panes in the
|
|
483
|
+
* same directory don't stop each other's session out from under them.
|
|
457
484
|
*/
|
|
458
485
|
function sessionPath() {
|
|
459
|
-
|
|
486
|
+
const key = instanceKey();
|
|
487
|
+
const filename = key ? `.neuvikon-session.${key}` : ".neuvikon-session";
|
|
488
|
+
return join(process.cwd(), filename);
|
|
460
489
|
}
|
|
461
490
|
function readSession() {
|
|
462
491
|
let raw;
|
|
@@ -555,6 +584,25 @@ async function readStdin() {
|
|
|
555
584
|
* while we read it will have a torn final line, and that is normal, not an
|
|
556
585
|
* error worth failing a user's turn over.
|
|
557
586
|
*/
|
|
587
|
+
/**
|
|
588
|
+
* Where the transcript currently ends, so a session that starts mid-conversation
|
|
589
|
+
* can mark its starting point instead of counting what came before it.
|
|
590
|
+
*/
|
|
591
|
+
function transcriptSize(path) {
|
|
592
|
+
let fd;
|
|
593
|
+
try {
|
|
594
|
+
fd = openSync(path, "r");
|
|
595
|
+
} catch {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
return fstatSync(fd).size;
|
|
600
|
+
} catch {
|
|
601
|
+
return null;
|
|
602
|
+
} finally {
|
|
603
|
+
closeSync(fd);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
558
606
|
function readUsageSince(path, offset) {
|
|
559
607
|
let fd;
|
|
560
608
|
try {
|
|
@@ -661,7 +709,12 @@ async function report(flags) {
|
|
|
661
709
|
let nextOffset;
|
|
662
710
|
const transcript = flags.get("transcript") || await transcriptFromHookPayload();
|
|
663
711
|
if (transcript) {
|
|
664
|
-
const
|
|
712
|
+
const baseline = session.transcriptOffset ?? transcriptSize(transcript);
|
|
713
|
+
if (session.transcriptOffset === void 0 && baseline !== null) writeSession({
|
|
714
|
+
...session,
|
|
715
|
+
transcriptOffset: baseline
|
|
716
|
+
});
|
|
717
|
+
const usage = baseline === null ? null : readUsageSince(transcript, baseline);
|
|
665
718
|
if (usage) {
|
|
666
719
|
tokensIn = (tokensIn ?? 0) + usage.tokensIn;
|
|
667
720
|
tokensOut = (tokensOut ?? 0) + usage.tokensOut;
|