u-foo 1.0.3 → 1.0.6
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 +67 -8
- package/README.zh-CN.md +9 -7
- package/SKILLS/ufoo/SKILL.md +117 -0
- package/SKILLS/uinit/SKILL.md +73 -0
- package/SKILLS/ustatus/SKILL.md +36 -0
- package/bin/uclaude.js +13 -0
- package/bin/ucodex.js +13 -0
- package/bin/ufoo +9 -31
- package/bin/ufoo.js +13 -0
- package/modules/AGENTS.template.md +15 -7
- package/modules/bus/README.md +28 -23
- package/modules/bus/SKILLS/ubus/SKILL.md +18 -8
- package/modules/context/README.md +18 -40
- package/modules/context/SKILLS/uctx/SKILL.md +61 -1
- package/package.json +16 -4
- package/scripts/.archived/bash-to-js-migration/README.md +46 -0
- package/scripts/.archived/bash-to-js-migration/banner.sh +89 -0
- package/scripts/{bus-inject.sh → .archived/bash-to-js-migration/bus-inject.sh} +35 -3
- package/scripts/{bus.sh → .archived/bash-to-js-migration/bus.sh} +3 -1
- package/scripts/banner.sh +2 -89
- package/scripts/postinstall.js +59 -0
- package/src/agent/cliRunner.js +33 -5
- package/src/agent/internalRunner.js +78 -51
- package/src/agent/launcher.js +702 -0
- package/src/agent/notifier.js +200 -0
- package/src/agent/ptyRunner.js +377 -0
- package/src/agent/ptyWrapper.js +354 -0
- package/src/agent/readyDetector.js +159 -0
- package/src/agent/ufooAgent.js +37 -42
- package/src/bus/API_DESIGN.md +204 -0
- package/src/bus/activate.js +156 -0
- package/src/bus/daemon.js +308 -0
- package/src/bus/index.js +785 -0
- package/src/bus/inject.js +285 -0
- package/src/bus/message.js +302 -0
- package/src/bus/nickname.js +86 -0
- package/src/bus/queue.js +131 -0
- package/src/bus/shake.js +26 -0
- package/src/bus/subscriber.js +296 -0
- package/src/bus/utils.js +357 -0
- package/src/chat/index.js +1842 -249
- package/src/cli.js +658 -95
- package/src/config.js +9 -2
- package/src/context/decisions.js +314 -0
- package/src/context/doctor.js +183 -0
- package/src/context/index.js +38 -0
- package/src/daemon/index.js +749 -94
- package/src/daemon/ops.js +395 -51
- package/src/daemon/providerSessions.js +291 -0
- package/src/daemon/run.js +34 -1
- package/src/daemon/status.js +24 -7
- package/src/doctor/index.js +50 -0
- package/src/init/index.js +264 -0
- package/src/skills/index.js +159 -0
- package/src/status/index.js +252 -0
- package/src/terminal/detect.js +64 -0
- package/src/terminal/index.js +8 -0
- package/src/terminal/iterm2.js +126 -0
- package/src/ufoo/agentsStore.js +41 -0
- package/src/ufoo/paths.js +46 -0
- package/src/utils/banner.js +73 -0
- package/bin/uclaude +0 -65
- package/bin/ucodex +0 -65
- package/modules/bus/scripts/bus-alert.sh +0 -185
- package/modules/bus/scripts/bus-listen.sh +0 -117
- package/modules/context/ASSUMPTIONS.md +0 -7
- package/modules/context/CONSTRAINTS.md +0 -7
- package/modules/context/CONTEXT-STRUCTURE.md +0 -49
- package/modules/context/DECISION-PROTOCOL.md +0 -62
- package/modules/context/HANDOFF.md +0 -33
- package/modules/context/RULES.md +0 -15
- package/modules/context/SKILLS/README.md +0 -14
- package/modules/context/SYSTEM.md +0 -18
- package/modules/context/TEMPLATES/assumptions.md +0 -4
- package/modules/context/TEMPLATES/constraints.md +0 -4
- package/modules/context/TEMPLATES/decision.md +0 -16
- package/modules/context/TEMPLATES/project-context-readme.md +0 -6
- package/modules/context/TEMPLATES/system.md +0 -3
- package/modules/context/TEMPLATES/terminology.md +0 -4
- package/modules/context/TERMINOLOGY.md +0 -10
- /package/scripts/{bus-alert.sh → .archived/bash-to-js-migration/bus-alert.sh} +0 -0
- /package/scripts/{bus-autotrigger.sh → .archived/bash-to-js-migration/bus-autotrigger.sh} +0 -0
- /package/scripts/{bus-daemon.sh → .archived/bash-to-js-migration/bus-daemon.sh} +0 -0
- /package/scripts/{bus-listen.sh → .archived/bash-to-js-migration/bus-listen.sh} +0 -0
- /package/scripts/{context-decisions.sh → .archived/bash-to-js-migration/context-decisions.sh} +0 -0
- /package/scripts/{context-doctor.sh → .archived/bash-to-js-migration/context-doctor.sh} +0 -0
- /package/scripts/{context-lint.sh → .archived/bash-to-js-migration/context-lint.sh} +0 -0
- /package/scripts/{doctor.sh → .archived/bash-to-js-migration/doctor.sh} +0 -0
- /package/scripts/{init.sh → .archived/bash-to-js-migration/init.sh} +0 -0
- /package/scripts/{skills.sh → .archived/bash-to-js-migration/skills.sh} +0 -0
- /package/scripts/{status.sh → .archived/bash-to-js-migration/status.sh} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal type detection
|
|
3
|
+
*
|
|
4
|
+
* Detects the terminal emulator and its capabilities from environment variables.
|
|
5
|
+
* Results are cached for the lifetime of the process.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TERMINAL_TYPES = {
|
|
9
|
+
ITERM2: "iterm2",
|
|
10
|
+
APPLE_TERMINAL: "apple-terminal",
|
|
11
|
+
KITTY: "kitty",
|
|
12
|
+
WEZTERM: "wezterm",
|
|
13
|
+
ALACRITTY: "alacritty",
|
|
14
|
+
UNKNOWN: "unknown",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let cached = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect the current terminal emulator.
|
|
21
|
+
* @returns {{ type: string, version: string, truecolor: boolean }}
|
|
22
|
+
*/
|
|
23
|
+
function detect() {
|
|
24
|
+
if (cached) return cached;
|
|
25
|
+
|
|
26
|
+
const prog = process.env.TERM_PROGRAM || "";
|
|
27
|
+
const ver = process.env.TERM_PROGRAM_VERSION || "";
|
|
28
|
+
const colorterm = (process.env.COLORTERM || "").toLowerCase();
|
|
29
|
+
const truecolor = colorterm === "truecolor" || colorterm === "24bit";
|
|
30
|
+
|
|
31
|
+
let type = TERMINAL_TYPES.UNKNOWN;
|
|
32
|
+
|
|
33
|
+
if (prog === "iTerm.app" || process.env.ITERM_SESSION_ID) {
|
|
34
|
+
type = TERMINAL_TYPES.ITERM2;
|
|
35
|
+
} else if (prog === "Apple_Terminal") {
|
|
36
|
+
type = TERMINAL_TYPES.APPLE_TERMINAL;
|
|
37
|
+
} else if (prog === "kitty" || process.env.KITTY_PID) {
|
|
38
|
+
type = TERMINAL_TYPES.KITTY;
|
|
39
|
+
} else if (prog === "WezTerm") {
|
|
40
|
+
type = TERMINAL_TYPES.WEZTERM;
|
|
41
|
+
} else if (prog === "Alacritty") {
|
|
42
|
+
type = TERMINAL_TYPES.ALACRITTY;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cached = { type, version: ver, truecolor };
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isITerm2() {
|
|
50
|
+
return detect().type === TERMINAL_TYPES.ITERM2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isAppleTerminal() {
|
|
54
|
+
return detect().type === TERMINAL_TYPES.APPLE_TERMINAL;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reset cached detection (for testing).
|
|
59
|
+
*/
|
|
60
|
+
function resetCache() {
|
|
61
|
+
cached = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { detect, isITerm2, isAppleTerminal, resetCache, TERMINAL_TYPES };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iTerm2-specific terminal features via OSC escape sequences.
|
|
3
|
+
*
|
|
4
|
+
* All functions are guarded — they no-op when not running in iTerm2
|
|
5
|
+
* or when stdout is not a TTY.
|
|
6
|
+
*
|
|
7
|
+
* References:
|
|
8
|
+
* https://iterm2.com/documentation-escape-codes.html
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { isITerm2 } = require("./detect");
|
|
12
|
+
|
|
13
|
+
const ESC = "\x1b";
|
|
14
|
+
const BEL = "\x07";
|
|
15
|
+
const OSC = `${ESC}]`;
|
|
16
|
+
|
|
17
|
+
function canWrite() {
|
|
18
|
+
return isITerm2() && process.stdout && process.stdout.isTTY;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Notify iTerm2 of the current working directory (OSC 1337).
|
|
23
|
+
* Enables the Shell Integration "Recent Directories" feature and
|
|
24
|
+
* makes "Open in Finder" point to the correct location.
|
|
25
|
+
*/
|
|
26
|
+
function setCwd(cwd) {
|
|
27
|
+
if (!canWrite() || !cwd) return;
|
|
28
|
+
process.stdout.write(`${OSC}1337;CurrentDir=${cwd}${BEL}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set a user-defined badge in the upper-right of the session.
|
|
33
|
+
* Supports iTerm2 interpolated-string variables like \(session.name).
|
|
34
|
+
*/
|
|
35
|
+
function setBadge(text) {
|
|
36
|
+
if (!canWrite()) return;
|
|
37
|
+
const encoded = Buffer.from(text || "").toString("base64");
|
|
38
|
+
process.stdout.write(`${OSC}1337;SetBadgeFormat=${encoded}${BEL}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clear the session badge.
|
|
43
|
+
*/
|
|
44
|
+
function clearBadge() {
|
|
45
|
+
if (!canWrite()) return;
|
|
46
|
+
process.stdout.write(`${OSC}1337;SetBadgeFormat=${BEL}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Post a macOS notification through iTerm2 (OSC 9).
|
|
51
|
+
* Only fires when the session tab is NOT focused, so it
|
|
52
|
+
* naturally avoids spamming the user.
|
|
53
|
+
*/
|
|
54
|
+
function notify(message) {
|
|
55
|
+
if (!canWrite() || !message) return;
|
|
56
|
+
process.stdout.write(`${OSC}9;${message}${BEL}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Emit a shell-integration prompt mark (OSC 133).
|
|
61
|
+
* A = start of prompt B = end of prompt / start of command
|
|
62
|
+
* C = start of output D = end of output (with exit status)
|
|
63
|
+
*/
|
|
64
|
+
function promptMark(code) {
|
|
65
|
+
if (!canWrite()) return;
|
|
66
|
+
const valid = ["A", "B", "C", "D"];
|
|
67
|
+
if (!valid.includes(code)) return;
|
|
68
|
+
process.stdout.write(`${OSC}133;${code}${BEL}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set cursor shape.
|
|
73
|
+
* 0 = block 1 = vertical bar 2 = underline
|
|
74
|
+
*/
|
|
75
|
+
function setCursorShape(shape) {
|
|
76
|
+
if (!canWrite()) return;
|
|
77
|
+
if (![0, 1, 2].includes(shape)) return;
|
|
78
|
+
process.stdout.write(`${OSC}1337;CursorShape=${shape}${BEL}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set the tab color for this session (RGB).
|
|
83
|
+
* Pass null/undefined to reset to default.
|
|
84
|
+
*/
|
|
85
|
+
function setTabColor(r, g, b) {
|
|
86
|
+
if (!canWrite()) return;
|
|
87
|
+
if (r == null) {
|
|
88
|
+
// reset
|
|
89
|
+
process.stdout.write(`${OSC}6;1;bg;*;default${BEL}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(`${OSC}6;1;bg;red;brightness;${r}${BEL}`);
|
|
93
|
+
process.stdout.write(`${OSC}6;1;bg;green;brightness;${g}${BEL}`);
|
|
94
|
+
process.stdout.write(`${OSC}6;1;bg;blue;brightness;${b}${BEL}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Add an annotation at the current cursor position.
|
|
99
|
+
*/
|
|
100
|
+
function annotation(message) {
|
|
101
|
+
if (!canWrite() || !message) return;
|
|
102
|
+
const len = message.length;
|
|
103
|
+
process.stdout.write(`${OSC}1337;AddAnnotation=${len}|${message}${BEL}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Report current directory using OSC 7 (semantic URL).
|
|
108
|
+
* Understood by iTerm2, Terminal.app, and VTE-based terminals.
|
|
109
|
+
*/
|
|
110
|
+
function reportCwd(cwd) {
|
|
111
|
+
if (!process.stdout || !process.stdout.isTTY || !cwd) return;
|
|
112
|
+
const hostname = require("os").hostname();
|
|
113
|
+
process.stdout.write(`${OSC}7;file://${hostname}${cwd}${BEL}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
setCwd,
|
|
118
|
+
setBadge,
|
|
119
|
+
clearBadge,
|
|
120
|
+
notify,
|
|
121
|
+
promptMark,
|
|
122
|
+
setCursorShape,
|
|
123
|
+
setTabColor,
|
|
124
|
+
annotation,
|
|
125
|
+
reportCwd,
|
|
126
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { getTimestamp, readJSON, writeJSON } = require("../bus/utils");
|
|
2
|
+
|
|
3
|
+
const AGENTS_SCHEMA_VERSION = 1;
|
|
4
|
+
|
|
5
|
+
function normalizeAgentsData(data) {
|
|
6
|
+
const base = data && typeof data === "object" ? { ...data } : {};
|
|
7
|
+
const { subscribers: _legacy, ...rest } = base;
|
|
8
|
+
const agents = base.agents && typeof base.agents === "object"
|
|
9
|
+
? base.agents
|
|
10
|
+
: {};
|
|
11
|
+
const createdAt = typeof base.created_at === "string" && base.created_at
|
|
12
|
+
? base.created_at
|
|
13
|
+
: getTimestamp();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...rest,
|
|
17
|
+
schema_version: AGENTS_SCHEMA_VERSION,
|
|
18
|
+
created_at: createdAt,
|
|
19
|
+
agents,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function loadAgentsData(filePath) {
|
|
24
|
+
const data = readJSON(filePath, null);
|
|
25
|
+
if (!data) {
|
|
26
|
+
return normalizeAgentsData({});
|
|
27
|
+
}
|
|
28
|
+
return normalizeAgentsData(data);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function saveAgentsData(filePath, data) {
|
|
32
|
+
const normalized = normalizeAgentsData(data);
|
|
33
|
+
writeJSON(filePath, normalized);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
AGENTS_SCHEMA_VERSION,
|
|
38
|
+
loadAgentsData,
|
|
39
|
+
saveAgentsData,
|
|
40
|
+
normalizeAgentsData,
|
|
41
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
function getUfooPaths(projectRoot) {
|
|
4
|
+
const ufooDir = path.join(projectRoot, ".ufoo");
|
|
5
|
+
const busDir = path.join(ufooDir, "bus");
|
|
6
|
+
const agentDir = path.join(ufooDir, "agent");
|
|
7
|
+
const agentsFile = path.join(agentDir, "all-agents.json");
|
|
8
|
+
|
|
9
|
+
const busQueuesDir = path.join(busDir, "queues");
|
|
10
|
+
const busEventsDir = path.join(busDir, "events");
|
|
11
|
+
const busLogsDir = path.join(busDir, "logs");
|
|
12
|
+
const busOffsetsDir = path.join(busDir, "offsets");
|
|
13
|
+
|
|
14
|
+
const busDaemonDir = path.join(ufooDir, "daemon");
|
|
15
|
+
const busDaemonPid = path.join(busDaemonDir, "daemon.pid");
|
|
16
|
+
const busDaemonLog = path.join(busDaemonDir, "daemon.log");
|
|
17
|
+
const busDaemonCountsDir = path.join(busDaemonDir, "counts");
|
|
18
|
+
|
|
19
|
+
const runDir = path.join(ufooDir, "run");
|
|
20
|
+
const ufooDaemonPid = path.join(runDir, "ufoo-daemon.pid");
|
|
21
|
+
const ufooDaemonLog = path.join(runDir, "ufoo-daemon.log");
|
|
22
|
+
const ufooSock = path.join(runDir, "ufoo.sock");
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
ufooDir,
|
|
26
|
+
busDir,
|
|
27
|
+
agentDir,
|
|
28
|
+
agentsFile,
|
|
29
|
+
busQueuesDir,
|
|
30
|
+
busEventsDir,
|
|
31
|
+
busLogsDir,
|
|
32
|
+
busOffsetsDir,
|
|
33
|
+
busDaemonDir,
|
|
34
|
+
busDaemonPid,
|
|
35
|
+
busDaemonLog,
|
|
36
|
+
busDaemonCountsDir,
|
|
37
|
+
runDir,
|
|
38
|
+
ufooDaemonPid,
|
|
39
|
+
ufooDaemonLog,
|
|
40
|
+
ufooSock,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
getUfooPaths,
|
|
46
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 显示 agent 启动横幅
|
|
5
|
+
*/
|
|
6
|
+
function showBanner(options) {
|
|
7
|
+
const { agentType, sessionId, nickname, daemonStatus } = options;
|
|
8
|
+
|
|
9
|
+
// Compact logo (3 行)
|
|
10
|
+
const logo = [
|
|
11
|
+
"█ █ █▀▀ █▀█ █▀█",
|
|
12
|
+
"█ █ █▀ █ █ █ █",
|
|
13
|
+
"▀▀▀ ▀ ▀▀▀ ▀▀▀",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// 准备右侧信息行
|
|
17
|
+
const infoLines = [];
|
|
18
|
+
if (nickname) {
|
|
19
|
+
infoLines.push(`${chalk.dim("Nickname:")} ${chalk.cyan.bold(nickname)}`);
|
|
20
|
+
}
|
|
21
|
+
infoLines.push(`${chalk.dim("Agent:")} ${chalk.green.bold(agentType)}${chalk.dim(":")}${chalk.yellow(sessionId)}`);
|
|
22
|
+
if (daemonStatus) {
|
|
23
|
+
const statusColor = daemonStatus === "running" ? chalk.green : chalk.blue;
|
|
24
|
+
infoLines.push(`${chalk.dim("Daemon:")} ${statusColor(daemonStatus)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 计算垂直居中偏移
|
|
28
|
+
const verticalOffset = Math.floor((logo.length - infoLines.length) / 2);
|
|
29
|
+
|
|
30
|
+
// 输出:Logo 和信息并排显示
|
|
31
|
+
console.log("");
|
|
32
|
+
logo.forEach((line, index) => {
|
|
33
|
+
const logoLine = chalk.cyan(line);
|
|
34
|
+
const infoIndex = index - verticalOffset;
|
|
35
|
+
const infoLine = (infoIndex >= 0 && infoIndex < infoLines.length)
|
|
36
|
+
? infoLines[infoIndex]
|
|
37
|
+
: "";
|
|
38
|
+
console.log(` ${logoLine} ${infoLine}`);
|
|
39
|
+
});
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 显示 ufoo 主命令横幅
|
|
45
|
+
*/
|
|
46
|
+
function showUfooBanner(options = {}) {
|
|
47
|
+
const { version = "1.0.0" } = options;
|
|
48
|
+
|
|
49
|
+
// Compact logo (3 行)
|
|
50
|
+
const logo = [
|
|
51
|
+
"█ █ █▀▀ █▀█ █▀█",
|
|
52
|
+
"█ █ █▀ █ █ █ █",
|
|
53
|
+
"▀▀▀ ▀ ▀▀▀ ▀▀▀",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// 右侧信息
|
|
57
|
+
const infoLines = [
|
|
58
|
+
`${chalk.cyan.bold(`v${version}`)} ${chalk.gray("Multi-Agent Workspace Protocol")}`,
|
|
59
|
+
"",
|
|
60
|
+
chalk.dim("uclaude") + chalk.gray(" · ") + chalk.dim("ucodex") + chalk.gray(" · ") + chalk.dim("ufoo init") + chalk.gray(" · ") + chalk.dim("ufoo ctx") + chalk.gray(" · ") + chalk.dim("ufoo bus"),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// 输出:Logo 和信息并排显示
|
|
64
|
+
console.log("");
|
|
65
|
+
logo.forEach((line, index) => {
|
|
66
|
+
const logoLine = chalk.cyan(line);
|
|
67
|
+
const infoLine = (index < infoLines.length) ? infoLines[index] : "";
|
|
68
|
+
console.log(` ${logoLine} ${infoLine}`);
|
|
69
|
+
});
|
|
70
|
+
console.log("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { showBanner, showUfooBanner };
|
package/bin/uclaude
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# uclaude: Launch Claude Code and auto-join event bus
|
|
3
|
-
#
|
|
4
|
-
# Usage: uclaude [claude args...]
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
# Resolve symlinks to get real script location
|
|
9
|
-
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
10
|
-
while [[ -L "$SCRIPT_PATH" ]]; do
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
12
|
-
SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
|
|
13
|
-
[[ "$SCRIPT_PATH" != /* ]] && SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH"
|
|
14
|
-
done
|
|
15
|
-
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
16
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
-
|
|
18
|
-
# Source banner
|
|
19
|
-
source "$PROJECT_ROOT/scripts/banner.sh" 2>/dev/null || true
|
|
20
|
-
|
|
21
|
-
# Check and initialize .ufoo (silent)
|
|
22
|
-
if [[ ! -d ".ufoo/bus" ]]; then
|
|
23
|
-
ufoo init --modules context,bus &>/dev/null || true
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# Check if AGENTS.md has ufoo template (inject if not)
|
|
27
|
-
if [[ -f "AGENTS.md" ]] && ! grep -q "<!-- ufoo -->" "AGENTS.md" 2>/dev/null; then
|
|
28
|
-
ufoo init --modules context,bus &>/dev/null || true
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Generate or reuse session ID
|
|
32
|
-
if [[ -z "${CLAUDE_SESSION_ID:-}" ]]; then
|
|
33
|
-
export CLAUDE_SESSION_ID="$(date +%s%N | shasum | head -c 8)"
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Join event bus (using fixed session ID)
|
|
37
|
-
# Export our PID so bus.sh registers the correct process
|
|
38
|
-
export UFOO_PARENT_PID=$$
|
|
39
|
-
NICKNAME="${UFOO_NICKNAME:-}"
|
|
40
|
-
SUBSCRIBER=$(ufoo bus join "$CLAUDE_SESSION_ID" claude-code "$NICKNAME" 2>/dev/null | tail -1)
|
|
41
|
-
|
|
42
|
-
# Auto-start project-level daemon (if not running)
|
|
43
|
-
DAEMON_STATUS=""
|
|
44
|
-
PID_FILE=".ufoo/bus/.daemon.pid"
|
|
45
|
-
if [[ -f "$PID_FILE" ]]; then
|
|
46
|
-
existing="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
47
|
-
if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
|
|
48
|
-
DAEMON_STATUS="running"
|
|
49
|
-
else
|
|
50
|
-
ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
|
|
51
|
-
fi
|
|
52
|
-
else
|
|
53
|
-
ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Show banner
|
|
57
|
-
if type show_banner &>/dev/null; then
|
|
58
|
-
show_banner "claude" "$CLAUDE_SESSION_ID" "$SUBSCRIBER" "$DAEMON_STATUS"
|
|
59
|
-
else
|
|
60
|
-
echo "[uclaude] Connected -> $SUBSCRIBER"
|
|
61
|
-
echo "[uclaude] Daemon: $DAEMON_STATUS"
|
|
62
|
-
echo ""
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
exec claude "$@"
|
package/bin/ucodex
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ucodex: Launch Codex and auto-join event bus
|
|
3
|
-
#
|
|
4
|
-
# Usage: ucodex [codex args...]
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
# Resolve symlinks to get real script location
|
|
9
|
-
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
10
|
-
while [[ -L "$SCRIPT_PATH" ]]; do
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
12
|
-
SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
|
|
13
|
-
[[ "$SCRIPT_PATH" != /* ]] && SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH"
|
|
14
|
-
done
|
|
15
|
-
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
16
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
-
|
|
18
|
-
# Source banner
|
|
19
|
-
source "$PROJECT_ROOT/scripts/banner.sh" 2>/dev/null || true
|
|
20
|
-
|
|
21
|
-
# Check and initialize .ufoo (silent)
|
|
22
|
-
if [[ ! -d ".ufoo/bus" ]]; then
|
|
23
|
-
ufoo init --modules context,bus &>/dev/null || true
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# Check if AGENTS.md has ufoo template (inject if not)
|
|
27
|
-
if [[ -f "AGENTS.md" ]] && ! grep -q "<!-- ufoo -->" "AGENTS.md" 2>/dev/null; then
|
|
28
|
-
ufoo init --modules context,bus &>/dev/null || true
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Generate or reuse session ID
|
|
32
|
-
if [[ -z "${CODEX_SESSION_ID:-}" ]]; then
|
|
33
|
-
export CODEX_SESSION_ID="$(date +%s%N | shasum | head -c 8)"
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Join event bus (using fixed session ID)
|
|
37
|
-
# Export our PID so bus.sh registers the correct process
|
|
38
|
-
export UFOO_PARENT_PID=$$
|
|
39
|
-
NICKNAME="${UFOO_NICKNAME:-}"
|
|
40
|
-
SUBSCRIBER=$(ufoo bus join "$CODEX_SESSION_ID" codex "$NICKNAME" 2>/dev/null | tail -1)
|
|
41
|
-
|
|
42
|
-
# Auto-start project-level daemon (if not running)
|
|
43
|
-
DAEMON_STATUS=""
|
|
44
|
-
PID_FILE=".ufoo/bus/.daemon.pid"
|
|
45
|
-
if [[ -f "$PID_FILE" ]]; then
|
|
46
|
-
existing="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
47
|
-
if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
|
|
48
|
-
DAEMON_STATUS="running"
|
|
49
|
-
else
|
|
50
|
-
ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
|
|
51
|
-
fi
|
|
52
|
-
else
|
|
53
|
-
ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Show banner
|
|
57
|
-
if type show_banner &>/dev/null; then
|
|
58
|
-
show_banner "codex" "$CODEX_SESSION_ID" "$SUBSCRIBER" "$DAEMON_STATUS"
|
|
59
|
-
else
|
|
60
|
-
echo "[ucodex] Connected -> $SUBSCRIBER"
|
|
61
|
-
echo "[ucodex] Daemon: $DAEMON_STATUS"
|
|
62
|
-
echo ""
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
exec codex "$@"
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# bus-alert.sh
|
|
5
|
-
# Background notification daemon for a single subscriber.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# bash bus-alert.sh <subscriber> [interval] [options]
|
|
9
|
-
#
|
|
10
|
-
# Options:
|
|
11
|
-
# --notify Enable macOS Notification Center
|
|
12
|
-
# --daemon Run in background
|
|
13
|
-
# --stop Stop running alert for this subscriber
|
|
14
|
-
# --no-title Disable terminal title badge
|
|
15
|
-
# --no-bell Disable terminal bell
|
|
16
|
-
|
|
17
|
-
BUS_DIR=".ufoo/bus"
|
|
18
|
-
INTERVAL=2
|
|
19
|
-
DAEMON_MODE=0
|
|
20
|
-
USE_NOTIFY=0
|
|
21
|
-
USE_TITLE=1
|
|
22
|
-
USE_BELL=1
|
|
23
|
-
STOP_MODE=0
|
|
24
|
-
|
|
25
|
-
usage() {
|
|
26
|
-
cat <<'USAGE'
|
|
27
|
-
bus-alert.sh - Background notification daemon for a single subscriber
|
|
28
|
-
|
|
29
|
-
Usage:
|
|
30
|
-
bus-alert.sh <subscriber> [interval] [options]
|
|
31
|
-
|
|
32
|
-
Options:
|
|
33
|
-
--notify Enable macOS Notification Center
|
|
34
|
-
--daemon Run in background
|
|
35
|
-
--stop Stop running alert for this subscriber
|
|
36
|
-
--no-title Disable terminal title badge
|
|
37
|
-
--no-bell Disable terminal bell
|
|
38
|
-
-h, --help Show this help
|
|
39
|
-
USAGE
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
# Handle --help first
|
|
43
|
-
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
44
|
-
usage
|
|
45
|
-
exit 0
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
SUBSCRIBER="${1:-}"
|
|
49
|
-
shift || true
|
|
50
|
-
|
|
51
|
-
if [[ -z "$SUBSCRIBER" ]]; then
|
|
52
|
-
usage >&2
|
|
53
|
-
exit 1
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Parse interval if numeric
|
|
57
|
-
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
|
58
|
-
INTERVAL="$1"
|
|
59
|
-
shift || true
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
while [[ $# -gt 0 ]]; do
|
|
63
|
-
case "$1" in
|
|
64
|
-
--notify)
|
|
65
|
-
USE_NOTIFY=1
|
|
66
|
-
shift
|
|
67
|
-
;;
|
|
68
|
-
--daemon)
|
|
69
|
-
DAEMON_MODE=1
|
|
70
|
-
shift
|
|
71
|
-
;;
|
|
72
|
-
--stop)
|
|
73
|
-
STOP_MODE=1
|
|
74
|
-
shift
|
|
75
|
-
;;
|
|
76
|
-
--no-title)
|
|
77
|
-
USE_TITLE=0
|
|
78
|
-
shift
|
|
79
|
-
;;
|
|
80
|
-
--no-bell)
|
|
81
|
-
USE_BELL=0
|
|
82
|
-
shift
|
|
83
|
-
;;
|
|
84
|
-
*)
|
|
85
|
-
shift
|
|
86
|
-
;;
|
|
87
|
-
esac
|
|
88
|
-
done
|
|
89
|
-
|
|
90
|
-
# Sanitize subscriber for filename: claude-code:abc123 -> claude-code_abc123
|
|
91
|
-
SAFE_SUB="${SUBSCRIBER//:/_}"
|
|
92
|
-
PID_FILE="$BUS_DIR/pids/alert-${SAFE_SUB}.pid"
|
|
93
|
-
QUEUE_FILE="$BUS_DIR/queues/${SAFE_SUB}/pending.jsonl"
|
|
94
|
-
|
|
95
|
-
mkdir -p "$BUS_DIR/pids"
|
|
96
|
-
|
|
97
|
-
# Stop mode
|
|
98
|
-
if [[ "$STOP_MODE" == "1" ]]; then
|
|
99
|
-
if [[ -f "$PID_FILE" ]]; then
|
|
100
|
-
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
101
|
-
if [[ -n "$pid" ]]; then
|
|
102
|
-
kill "$pid" 2>/dev/null && echo "[alert] Stopped $SUBSCRIBER (pid=$pid)" || echo "[alert] Not running"
|
|
103
|
-
fi
|
|
104
|
-
rm -f "$PID_FILE"
|
|
105
|
-
else
|
|
106
|
-
echo "[alert] Not running for $SUBSCRIBER"
|
|
107
|
-
fi
|
|
108
|
-
exit 0
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# Daemon mode - fork to background
|
|
112
|
-
if [[ "$DAEMON_MODE" == "1" ]]; then
|
|
113
|
-
# Check if already running
|
|
114
|
-
if [[ -f "$PID_FILE" ]]; then
|
|
115
|
-
existing="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
116
|
-
if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
|
|
117
|
-
echo "[alert] Already running for $SUBSCRIBER (pid=$existing)"
|
|
118
|
-
exit 0
|
|
119
|
-
fi
|
|
120
|
-
fi
|
|
121
|
-
|
|
122
|
-
LOG_FILE="$BUS_DIR/logs/alert-${SAFE_SUB}.log"
|
|
123
|
-
mkdir -p "$BUS_DIR/logs"
|
|
124
|
-
|
|
125
|
-
args=("$SUBSCRIBER" "$INTERVAL")
|
|
126
|
-
[[ "$USE_NOTIFY" == "1" ]] && args+=("--notify")
|
|
127
|
-
[[ "$USE_TITLE" == "0" ]] && args+=("--no-title")
|
|
128
|
-
[[ "$USE_BELL" == "0" ]] && args+=("--no-bell")
|
|
129
|
-
|
|
130
|
-
nohup bash "$0" "${args[@]}" >> "$LOG_FILE" 2>&1 &
|
|
131
|
-
echo $! > "$PID_FILE"
|
|
132
|
-
echo "[alert] Started for $SUBSCRIBER (pid=$!, log=$LOG_FILE)"
|
|
133
|
-
exit 0
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# Record PID for foreground mode too
|
|
137
|
-
echo $$ > "$PID_FILE"
|
|
138
|
-
trap 'rm -f "$PID_FILE" 2>/dev/null || true' EXIT
|
|
139
|
-
|
|
140
|
-
echo "[alert] Watching $SUBSCRIBER (interval=${INTERVAL}s)"
|
|
141
|
-
|
|
142
|
-
LAST_COUNT=0
|
|
143
|
-
|
|
144
|
-
# Get initial count
|
|
145
|
-
if [[ -f "$QUEUE_FILE" ]] && [[ -s "$QUEUE_FILE" ]]; then
|
|
146
|
-
LAST_COUNT="$(wc -l < "$QUEUE_FILE" | tr -d ' ')"
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
while true; do
|
|
150
|
-
# Get current count
|
|
151
|
-
if [[ -f "$QUEUE_FILE" ]] && [[ -s "$QUEUE_FILE" ]]; then
|
|
152
|
-
count="$(wc -l < "$QUEUE_FILE" | tr -d ' ')"
|
|
153
|
-
else
|
|
154
|
-
count=0
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
# New messages arrived
|
|
158
|
-
if [[ "$count" -gt "$LAST_COUNT" ]]; then
|
|
159
|
-
new_count=$((count - LAST_COUNT))
|
|
160
|
-
echo "[alert] $(date '+%H:%M:%S') +${new_count} new message(s)"
|
|
161
|
-
|
|
162
|
-
# Terminal bell
|
|
163
|
-
if [[ "$USE_BELL" == "1" ]]; then
|
|
164
|
-
printf '\a'
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
# Terminal title badge
|
|
168
|
-
if [[ "$USE_TITLE" == "1" ]]; then
|
|
169
|
-
printf '\033]0;[%d] %s\007' "$count" "$SUBSCRIBER"
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
# macOS notification
|
|
173
|
-
if [[ "$USE_NOTIFY" == "1" ]]; then
|
|
174
|
-
osascript -e "display notification \"${new_count} new message(s)\" with title \"ufoo bus\" subtitle \"$SUBSCRIBER\"" 2>/dev/null || true
|
|
175
|
-
fi
|
|
176
|
-
fi
|
|
177
|
-
|
|
178
|
-
# Update title even if no new messages (show current count)
|
|
179
|
-
if [[ "$USE_TITLE" == "1" ]] && [[ "$count" -gt 0 ]]; then
|
|
180
|
-
printf '\033]0;[%d] %s\007' "$count" "$SUBSCRIBER"
|
|
181
|
-
fi
|
|
182
|
-
|
|
183
|
-
LAST_COUNT="$count"
|
|
184
|
-
sleep "$INTERVAL"
|
|
185
|
-
done
|