@kitfunso/aura 0.1.0 → 0.1.1
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 +4 -3
- package/package.json +2 -2
- package/src/decide.js +117 -113
package/README.md
CHANGED
|
@@ -61,9 +61,10 @@ names none of them. So identity has two more sources.
|
|
|
61
61
|
**Your tab name.** Rename a Windows Terminal tab and aura colors it by that
|
|
62
62
|
name, deterministically, the same way it colors a repo. Nothing to run. Tabs you
|
|
63
63
|
have not renamed are left alone, because aura only takes a title that behaves
|
|
64
|
-
like a name: under 40 characters, no path in it, not a shell's own default,
|
|
65
|
-
unchanged across two prompts. An agent's title
|
|
66
|
-
|
|
64
|
+
like a name: under 40 characters, no path in it, not a shell's own default, no
|
|
65
|
+
agent status marker, and unchanged across two prompts. An agent's title carries
|
|
66
|
+
a marker and moves with every prompt, so it never qualifies. A session colored
|
|
67
|
+
by its tab name keeps that name, because
|
|
67
68
|
writing a new title would change the color it was just read from.
|
|
68
69
|
|
|
69
70
|
**A tag**, when you want a session to wear a specific repo's color:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitfunso/aura",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Color identity for
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Color identity for terminal windows: repo = hue, branch = shade, tab name when no repo names it. Any shell, any agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kitfunso",
|
|
7
7
|
"repository": {
|
package/src/decide.js
CHANGED
|
@@ -1,113 +1,117 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Pure decision core. No fs, no child processes, no Win32: hook.js owns all I/O.
|
|
3
|
-
// Rules and the measurements behind them: docs/ARCHITECTURE.md.
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
// A tab the user named is an identity; a title an agent or a shell wrote is not,
|
|
7
|
-
// and reading one of those back would feed the color its own output.
|
|
8
|
-
const MAX_WINDOW_NAME = 40;
|
|
9
|
-
// Every terminal ships these, so they name a shell, never a project.
|
|
10
|
-
const DEFAULT_TITLES = [
|
|
11
|
-
"windows powershell", "powershell", "pwsh", "command prompt", "cmd", "cmd.exe",
|
|
12
|
-
"windows terminal", "git bash", "bash", "sh", "zsh", "node", "ubuntu", "wsl",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pure decision core. No fs, no child processes, no Win32: hook.js owns all I/O.
|
|
3
|
+
// Rules and the measurements behind them: docs/ARCHITECTURE.md.
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
// A tab the user named is an identity; a title an agent or a shell wrote is not,
|
|
7
|
+
// and reading one of those back would feed the color its own output.
|
|
8
|
+
const MAX_WINDOW_NAME = 40;
|
|
9
|
+
// Every terminal ships these, so they name a shell, never a project.
|
|
10
|
+
const DEFAULT_TITLES = [
|
|
11
|
+
"windows powershell", "powershell", "pwsh", "command prompt", "cmd", "cmd.exe",
|
|
12
|
+
"windows terminal", "git bash", "bash", "sh", "zsh", "node", "ubuntu", "wsl",
|
|
13
|
+
];
|
|
14
|
+
// An agent prefixes its title with one of these. A name never carries one, and
|
|
15
|
+
// a repeated short prompt would otherwise settle a status line into an identity.
|
|
16
|
+
const STATUS_MARKERS = ["·", "✳", "✴", "✶", "✹"];
|
|
17
|
+
|
|
18
|
+
function usableWindowTitle(raw) {
|
|
19
|
+
const name = String(raw == null ? "" : raw).replace(/[\u0000-\u001f]+/g, " ").trim();
|
|
20
|
+
if (!name || name.length > MAX_WINDOW_NAME) return null;
|
|
21
|
+
if (STATUS_MARKERS.some(function (m) { return name.indexOf(m) !== -1; })) return null;
|
|
22
|
+
// A separator or a drive colon means a shell wrote the path in; a name has neither.
|
|
23
|
+
if (name.indexOf("/") !== -1 || name.indexOf(":") !== -1) return null;
|
|
24
|
+
return DEFAULT_TITLES.indexOf(name.toLowerCase()) === -1 ? name : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Identity precedence: origin remote URL > repo root path > window title > cwd.
|
|
28
|
+
// isRepo stays literal; hasColor is the question every caller actually asks.
|
|
29
|
+
function identityFrom({ gitCombined, remoteUrl, cwd, windowTitle }) {
|
|
30
|
+
if (!gitCombined) {
|
|
31
|
+
const named = usableWindowTitle(windowTitle);
|
|
32
|
+
// Namespaced so a tab called "aura" cannot land on the aura repo's color.
|
|
33
|
+
if (named) {
|
|
34
|
+
return { repoId: "window:" + named, branch: null, name: named, isRepo: false, hasColor: true, fromWindowTitle: true, root: null };
|
|
35
|
+
}
|
|
36
|
+
const normalized = path.resolve(cwd);
|
|
37
|
+
return { repoId: normalized, branch: null, name: path.basename(normalized), isRepo: false, hasColor: false, fromWindowTitle: false, root: null };
|
|
38
|
+
}
|
|
39
|
+
const lines = gitCombined.split(/\r?\n/);
|
|
40
|
+
const root = lines[0];
|
|
41
|
+
// "HEAD" means unborn (no commits yet) or detached: a repo, with no branch.
|
|
42
|
+
const branch = lines[1] && lines[1] !== "HEAD" ? lines[1] : null;
|
|
43
|
+
return { repoId: remoteUrl || root, branch, name: path.basename(root), isRepo: true, hasColor: true, fromWindowTitle: false, root };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A name holds still; an agent's title follows the prompt. So a title becomes an
|
|
47
|
+
// identity only once two prompts have read it the same.
|
|
48
|
+
function settleWindowName(probe, found) {
|
|
49
|
+
if (found && probe === undefined) return { probe: found, name: null };
|
|
50
|
+
return { probe: null, name: found && found === probe ? found : "" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// "prompt" is the shell caller's name for the same thing: the window is already
|
|
54
|
+
// up, so there is no TUI init race to wait out.
|
|
55
|
+
function isPromptEvent(eventName) {
|
|
56
|
+
return eventName === "UserPromptSubmit" || eventName === "prompt";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Proof the session runs in a terminal the user launched; headless runs set none.
|
|
60
|
+
const TERMINAL_MARKERS = ["WT_SESSION", "WEZTERM_PANE", "ALACRITTY_WINDOW_ID", "GHOSTTY_RESOURCES_DIR"];
|
|
61
|
+
|
|
62
|
+
function hasTerminalMarker(env) {
|
|
63
|
+
return TERMINAL_MARKERS.some(function (name) { return Boolean(env[name]); });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Tabs share one window frame, so ownership is a property of the window.
|
|
67
|
+
function coloredSessionHwnds(sessions, exceptSessionId) {
|
|
68
|
+
const hwnds = [];
|
|
69
|
+
Object.keys(sessions || {}).forEach(function (id) {
|
|
70
|
+
const session = sessions[id];
|
|
71
|
+
if (id !== exceptSessionId && session && session.hasColor === true && session.hwnd) {
|
|
72
|
+
const key = String(session.hwnd);
|
|
73
|
+
if (hwnds.indexOf(key) === -1) hwnds.push(key);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return hwnds;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function windowHasColoredSession(sessions, hwnd, exceptSessionId) {
|
|
80
|
+
if (!hwnd) return false;
|
|
81
|
+
return coloredSessionHwnds(sessions, exceptSessionId).indexOf(String(hwnd)) !== -1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function decideEvent({ eventName, platform, session, frameHex, vtSignature, hasColor, windowFrameCleared }) {
|
|
85
|
+
const isPrompt = isPromptEvent(eventName);
|
|
86
|
+
// A session start may land in a new tab or window, so it re-handshakes.
|
|
87
|
+
const clearHandshake = !isPrompt;
|
|
88
|
+
const cachedVtSent = clearHandshake ? undefined : session.vtSent;
|
|
89
|
+
const needsVtDelivery = platform === "win32" && cachedVtSent !== vtSignature;
|
|
90
|
+
// The start-time window is a guess (the user may be looking elsewhere); the
|
|
91
|
+
// first prompt proves which window is theirs, and already spawns for VT.
|
|
92
|
+
const reresolveWindow = isPrompt && needsVtDelivery;
|
|
93
|
+
const cachedHwnd = (clearHandshake || reresolveWindow) ? null : session.hwnd || null;
|
|
94
|
+
// A bare shell in the window may have cleared the color this session owns.
|
|
95
|
+
const reclaimFrame = hasColor && Boolean(windowFrameCleared);
|
|
96
|
+
// With no identity the frame resets once, so a window aura colored earlier goes back.
|
|
97
|
+
const needsReset = !hasColor && !(clearHandshake ? false : session.frameCleared);
|
|
98
|
+
const needsFrame = !cachedHwnd || session.frameHex !== frameHex || reclaimFrame || needsReset;
|
|
99
|
+
return {
|
|
100
|
+
isPrompt,
|
|
101
|
+
clearHandshake,
|
|
102
|
+
cachedHwnd,
|
|
103
|
+
spawnAdapter: needsFrame || needsVtDelivery,
|
|
104
|
+
// An immediate start-time write races Claude Code's TUI init and is wiped.
|
|
105
|
+
vtDelayMs: isPrompt ? 0 : 2000,
|
|
106
|
+
markVtSent: isPrompt && needsVtDelivery,
|
|
107
|
+
// No repo and no tab name: the window keeps the terminal's own default.
|
|
108
|
+
paintsFrame: hasColor,
|
|
109
|
+
resetFrame: !hasColor,
|
|
110
|
+
usesColor: hasColor,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
identityFrom, usableWindowTitle, settleWindowName, isPromptEvent, decideEvent,
|
|
116
|
+
hasTerminalMarker, windowHasColoredSession, coloredSessionHwnds,
|
|
117
|
+
};
|