@yugabytedb/meko-mcp 2.1.0 → 2.3.0
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 +233 -40
- package/assets/hooks-handlers/codex-session-end.sh +4 -69
- package/assets/hooks-handlers/cursor-session-start.sh +19 -1
- package/assets/hooks-handlers/lib/capture-cleanup-cli.js +1473 -0
- package/assets/hooks-handlers/lib/capture-notices.js +438 -0
- package/assets/hooks-handlers/lib/capture-state.js +2173 -0
- package/assets/hooks-handlers/lib/capture-status-cli.js +86 -0
- package/assets/hooks-handlers/lib/capture.js +2782 -403
- package/assets/hooks-handlers/lib/checkpoint-timer.js +485 -81
- package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
- package/assets/hooks-handlers/lib/kiro-handler.js +760 -0
- package/assets/hooks-handlers/lib/opencode-handler.js +620 -0
- package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
- package/assets/hooks-handlers/lib/terminal-handoff.js +427 -0
- package/assets/hooks-handlers/session-end.sh +3 -34
- package/assets/opencode-plugin/meko.js +213 -0
- package/assets/scripts/meko-statusline.sh +123 -16
- package/assets/skills/meko-mcp-tools/SKILL.md +116 -126
- package/assets/skills/meko-mcp-tools/references/_sections.md +3 -3
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +25 -18
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +99 -24
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +53 -6
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +25 -79
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +37 -26
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +14 -8
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +66 -1
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +7 -40
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +56 -20
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +15 -8
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +99 -36
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +22 -5
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +18 -14
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +39 -28
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +14 -8
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +67 -1
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +4 -4
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +8 -8
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +7 -5
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +4 -4
- package/bin/create-meko-setup.mjs +113 -12
- package/lib/capture-cleanup.mjs +63 -0
- package/lib/capture-status.mjs +67 -0
- package/lib/clients/aliases.mjs +5 -0
- package/lib/clients/claude-code.mjs +1 -0
- package/lib/clients/claude-desktop.mjs +120 -5
- package/lib/clients/codex.mjs +85 -37
- package/lib/clients/common.mjs +7 -0
- package/lib/clients/index.mjs +4 -0
- package/lib/clients/kiro.mjs +705 -0
- package/lib/clients/opencode.mjs +532 -0
- package/lib/config.mjs +158 -1
- package/lib/installer.mjs +18 -1
- package/lib/migrate/adapters/claude-code.mjs +11 -2
- package/lib/migrate/adapters/codex.mjs +497 -0
- package/lib/migrate/adapters/memory-md.mjs +27 -8
- package/lib/migrate/adapters/opencode.mjs +148 -0
- package/lib/migrate/claude-parser.mjs +153 -9
- package/lib/migrate/id.mjs +217 -10
- package/lib/migrate/mcp-client.mjs +25 -1
- package/lib/migrate/opencode-normalizer.mjs +132 -0
- package/lib/migrate/orchestrator.mjs +299 -56
- package/lib/migrate/progress.mjs +239 -5
- package/lib/migrate/registry.mjs +72 -0
- package/lib/migrate.mjs +100 -10
- package/lib/prompt.mjs +16 -1
- package/package.json +4 -4
- package/scripts/bundle-assets.mjs +4 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Managed by meko installer; reinstall rather than editing.
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const MEKO_ROOT = path.join(PLUGIN_DIR, "..", "meko");
|
|
10
|
+
const ENV_PATH = path.join(MEKO_ROOT, "opencode-hook-env.json");
|
|
11
|
+
const HANDLER_PATH = path.join(MEKO_ROOT, "hooks-handlers", "lib", "opencode-handler.js");
|
|
12
|
+
const MANAGED_MARKER = "Managed by meko installer";
|
|
13
|
+
function readEnv() {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(ENV_PATH, "utf8"));
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// MEKO-635: staged snapshots live under the durable Capture v2 watermark root
|
|
21
|
+
// (matches skills/hooks-handlers/lib/capture-state.js). This keeps them
|
|
22
|
+
// outside installer-owned runtime and lets uninstall preserve them.
|
|
23
|
+
function resolveStagingDir(env) {
|
|
24
|
+
const watermarkDir =
|
|
25
|
+
(typeof env.MEKO_WATERMARK_DIR === "string" && env.MEKO_WATERMARK_DIR) ||
|
|
26
|
+
path.join(os.homedir(), ".claude", "meko-capture");
|
|
27
|
+
return path.join(watermarkDir, "opencode-snapshots");
|
|
28
|
+
}
|
|
29
|
+
function safeSegment(value) {
|
|
30
|
+
return String(value || "unknown").replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 120);
|
|
31
|
+
}
|
|
32
|
+
function sanitizeSnapshot(session, messages) {
|
|
33
|
+
return {
|
|
34
|
+
info: {
|
|
35
|
+
id: session?.id,
|
|
36
|
+
title: session?.title,
|
|
37
|
+
directory: session?.directory,
|
|
38
|
+
parentID: session?.parentID,
|
|
39
|
+
time: session?.time,
|
|
40
|
+
},
|
|
41
|
+
messages: (Array.isArray(messages) ? messages : []).map((message) => ({
|
|
42
|
+
info: {
|
|
43
|
+
id: message?.info?.id,
|
|
44
|
+
role: message?.info?.role,
|
|
45
|
+
type: message?.info?.type,
|
|
46
|
+
order: message?.info?.order,
|
|
47
|
+
synthetic: message?.info?.synthetic,
|
|
48
|
+
compaction: message?.info?.compaction,
|
|
49
|
+
subtask: message?.info?.subtask,
|
|
50
|
+
continuation: message?.info?.continuation,
|
|
51
|
+
time: message?.info?.time,
|
|
52
|
+
createdAt: message?.info?.createdAt,
|
|
53
|
+
timestamp: message?.info?.timestamp,
|
|
54
|
+
},
|
|
55
|
+
parts: (Array.isArray(message?.parts) ? message.parts : [])
|
|
56
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
57
|
+
.map((part) => ({
|
|
58
|
+
type: "text",
|
|
59
|
+
text: part.text,
|
|
60
|
+
synthetic: Boolean(part.synthetic),
|
|
61
|
+
ignored: Boolean(part.ignored),
|
|
62
|
+
})),
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function stageSnapshot(sessionId, snapshot, terminalError, stagingDir) {
|
|
67
|
+
fs.mkdirSync(stagingDir, { recursive: true, mode: 0o700 });
|
|
68
|
+
// MEKO-635: two events in the same PID and millisecond can race. The 8-byte
|
|
69
|
+
// random suffix makes both the target and the temp path collision-proof so a
|
|
70
|
+
// session.error and a session.idle firing back-to-back cannot overwrite each
|
|
71
|
+
// other (nor let a late pending snapshot clobber one the reconciler is
|
|
72
|
+
// about to read).
|
|
73
|
+
const unique = randomBytes(8).toString("hex");
|
|
74
|
+
const target = path.join(
|
|
75
|
+
stagingDir,
|
|
76
|
+
`${safeSegment(sessionId)}.${process.pid}.${Date.now()}.${unique}.json`,
|
|
77
|
+
);
|
|
78
|
+
const tmp = `${target}.tmp`;
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
tmp,
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
schema_version: 1,
|
|
83
|
+
terminalError: Boolean(terminalError),
|
|
84
|
+
export: snapshot,
|
|
85
|
+
}),
|
|
86
|
+
{ encoding: "utf8", mode: 0o600 },
|
|
87
|
+
);
|
|
88
|
+
fs.renameSync(tmp, target);
|
|
89
|
+
try { fs.chmodSync(target, 0o600); } catch { /* best effort */ }
|
|
90
|
+
return target;
|
|
91
|
+
}
|
|
92
|
+
function launchHandler(stagedPath, nodeExecutable, spawnProcess) {
|
|
93
|
+
if (!nodeExecutable) {
|
|
94
|
+
throw new Error("MEKO_NODE_EXECUTABLE is missing; reinstall Meko for OpenCode");
|
|
95
|
+
}
|
|
96
|
+
const child = spawnProcess(nodeExecutable, [HANDLER_PATH], {
|
|
97
|
+
detached: false,
|
|
98
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
99
|
+
env: {
|
|
100
|
+
...process.env,
|
|
101
|
+
MEKO_OPENCODE_ENV_FILE: ENV_PATH,
|
|
102
|
+
MEKO_HOOK_CLIENT: "opencode",
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
child.once?.("error", (err) => {
|
|
106
|
+
console.error(`[meko-opencode] handler launch failed: ${err.message}`);
|
|
107
|
+
});
|
|
108
|
+
child.stdin?.on?.("error", () => {});
|
|
109
|
+
child.stdin?.end?.(JSON.stringify({ snapshotPath: stagedPath }));
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
if (!child || typeof child.once !== "function") {
|
|
112
|
+
resolve();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const done = () => resolve();
|
|
116
|
+
child.once("exit", done);
|
|
117
|
+
child.once("close", done);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function captureSession(
|
|
121
|
+
client,
|
|
122
|
+
sessionID,
|
|
123
|
+
terminalError,
|
|
124
|
+
nodeExecutable,
|
|
125
|
+
spawnProcess,
|
|
126
|
+
stagingDir,
|
|
127
|
+
) {
|
|
128
|
+
if (!sessionID) return;
|
|
129
|
+
let staged = "";
|
|
130
|
+
try {
|
|
131
|
+
const [sessionResponse, messagesResponse] = await Promise.all([
|
|
132
|
+
client.session.get({ path: { id: sessionID } }),
|
|
133
|
+
client.session.messages({ path: { id: sessionID } }),
|
|
134
|
+
]);
|
|
135
|
+
const session = sessionResponse?.data;
|
|
136
|
+
const messages = messagesResponse?.data;
|
|
137
|
+
if (!session || !Array.isArray(messages)) {
|
|
138
|
+
throw new Error("OpenCode SDK returned an incomplete session snapshot");
|
|
139
|
+
}
|
|
140
|
+
staged = stageSnapshot(
|
|
141
|
+
session.id || sessionID,
|
|
142
|
+
sanitizeSnapshot(session, messages),
|
|
143
|
+
terminalError,
|
|
144
|
+
stagingDir,
|
|
145
|
+
);
|
|
146
|
+
await launchHandler(staged, nodeExecutable, spawnProcess);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(`[meko-opencode] capture failed: ${err.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export default async function MekoPlugin({ client }, options = {}) {
|
|
152
|
+
const runtimeEnv = readEnv();
|
|
153
|
+
const nodeExecutable = runtimeEnv.MEKO_NODE_EXECUTABLE;
|
|
154
|
+
const stagingDir = resolveStagingDir(runtimeEnv);
|
|
155
|
+
const spawnProcess =
|
|
156
|
+
typeof options.__mekoSpawn === "function" ? options.__mekoSpawn : spawn;
|
|
157
|
+
const sessionTails = new Map();
|
|
158
|
+
function enqueue(sessionID, task) {
|
|
159
|
+
const key = sessionID || "_";
|
|
160
|
+
const next = (sessionTails.get(key) || Promise.resolve()).then(task, task);
|
|
161
|
+
sessionTails.set(key, next);
|
|
162
|
+
return next;
|
|
163
|
+
}
|
|
164
|
+
if (process.env.MEKO_OPENCODE_DISABLE_RECOVERY !== "1") {
|
|
165
|
+
try {
|
|
166
|
+
if (!nodeExecutable) {
|
|
167
|
+
throw new Error("MEKO_NODE_EXECUTABLE is missing; reinstall Meko for OpenCode");
|
|
168
|
+
}
|
|
169
|
+
spawnProcess(nodeExecutable, [HANDLER_PATH, "--recovery-scan"], {
|
|
170
|
+
detached: true,
|
|
171
|
+
windowsHide: true,
|
|
172
|
+
stdio: "ignore",
|
|
173
|
+
env: {
|
|
174
|
+
...process.env,
|
|
175
|
+
MEKO_OPENCODE_ENV_FILE: ENV_PATH,
|
|
176
|
+
MEKO_HOOK_CLIENT: "opencode",
|
|
177
|
+
},
|
|
178
|
+
}).unref();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(`[meko-opencode] recovery scan failed: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
async config(config) {
|
|
186
|
+
const env = readEnv();
|
|
187
|
+
const serverName = env.MEKO_MCP_SERVER_NAME || "meko";
|
|
188
|
+
if (!env.MEKO_MCP_URL) return;
|
|
189
|
+
const headers = {
|
|
190
|
+
"User-Agent": env.MEKO_MCP_USER_AGENT || "meko-mcp-installer/1.0",
|
|
191
|
+
};
|
|
192
|
+
if (env.MEKO_API_KEY) headers.Authorization = `Bearer ${env.MEKO_API_KEY}`;
|
|
193
|
+
config.mcp ??= {};
|
|
194
|
+
config.mcp[serverName] = {
|
|
195
|
+
type: "remote",
|
|
196
|
+
url: env.MEKO_MCP_URL,
|
|
197
|
+
enabled: true,
|
|
198
|
+
oauth: false,
|
|
199
|
+
headers,
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
async event({ event }) {
|
|
203
|
+
const sessionID = event.properties?.sessionID;
|
|
204
|
+
if (event?.type === "session.idle") {
|
|
205
|
+
await enqueue(sessionID, () =>
|
|
206
|
+
captureSession(client, sessionID, false, nodeExecutable, spawnProcess, stagingDir));
|
|
207
|
+
} else if (event?.type === "session.error") {
|
|
208
|
+
await enqueue(sessionID, () =>
|
|
209
|
+
captureSession(client, sessionID, true, nodeExecutable, spawnProcess, stagingDir));
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -46,12 +46,18 @@ fi
|
|
|
46
46
|
|
|
47
47
|
# --- Derive agent_id from cwd ---
|
|
48
48
|
#
|
|
49
|
-
# Mirrors deriveAgentId() in
|
|
50
|
-
#
|
|
49
|
+
# Mirrors deriveAgentId() + resolveRepoRoot() in
|
|
50
|
+
# skills/hooks-handlers/lib/capture.js:
|
|
51
|
+
# - walk parents of cwd for a '.git' entry (dir OR file, for worktrees and
|
|
52
|
+
# submodules); the walk stops at $HOME from below, so a git-tracked home
|
|
53
|
+
# does not claim every directory beneath it
|
|
54
|
+
# - basename of that repo root, lowercased
|
|
51
55
|
# - non-[a-z0-9-]+ → '-'
|
|
52
56
|
# - trim leading/trailing '-'
|
|
53
57
|
# - max 64 chars
|
|
54
58
|
# - prefix with 'claude_code:'
|
|
59
|
+
# - no repo root found → bare 'claude_code' (NOT 'claude_code:<leaf>'), so
|
|
60
|
+
# this segment reads the same pin file the hook writes
|
|
55
61
|
#
|
|
56
62
|
# Then mirrors datapackPinSlug() in capture.js:341-348:
|
|
57
63
|
# - non-[A-Za-z0-9._-]+ → '_'
|
|
@@ -74,19 +80,72 @@ if [[ -n "$STDIN_PAYLOAD" ]]; then
|
|
|
74
80
|
fi
|
|
75
81
|
fi
|
|
76
82
|
|
|
83
|
+
if [[ -n "${HOME:-}" ]]; then
|
|
84
|
+
USER_HOME="$(cd -L -- "$HOME" 2>/dev/null && pwd -L)"
|
|
85
|
+
else
|
|
86
|
+
USER_HOME="$(cd -L -- ~ 2>/dev/null && pwd -L)"
|
|
87
|
+
fi
|
|
88
|
+
|
|
77
89
|
# MEKO_AGENT_ID overrides the cwd-derived id, matching resolveSessionAgentId() in
|
|
78
90
|
# capture.js (the env override wins verbatim when non-empty).
|
|
79
91
|
if [[ -n "${MEKO_AGENT_ID:-}" ]]; then
|
|
80
92
|
AGENT_ID="$MEKO_AGENT_ID"
|
|
93
|
+
LEGACY_AGENT_ID="$MEKO_AGENT_ID"
|
|
81
94
|
else
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
# Walk up from $CWD for a '.git' entry. $HOME bounds the walk from below.
|
|
96
|
+
REPO_ROOT=""
|
|
97
|
+
# Normalize relative paths and '..' before the textual $HOME comparison,
|
|
98
|
+
# matching path.resolve() for live directories. Unlike JavaScript's lexical
|
|
99
|
+
# resolution, cd/pwd requires the directory to exist; statusline cwd does.
|
|
100
|
+
START_DIR="$(cd -L -- "$CWD" 2>/dev/null && pwd -L)"
|
|
101
|
+
if [[ -z "$START_DIR" ]]; then
|
|
102
|
+
START_DIR="${CWD%/}"
|
|
103
|
+
[[ -z "$START_DIR" ]] && START_DIR="$CWD"
|
|
104
|
+
fi
|
|
105
|
+
WALK_DIR="$START_DIR"
|
|
106
|
+
HOME_BOUND="$USER_HOME"
|
|
107
|
+
while [[ -n "$WALK_DIR" ]]; do
|
|
108
|
+
# A directory below a git-tracked home must not inherit home's .git.
|
|
109
|
+
if [[ -n "$HOME_BOUND" && "$WALK_DIR" == "$HOME_BOUND" && "$WALK_DIR" != "$START_DIR" ]]; then
|
|
110
|
+
break
|
|
111
|
+
fi
|
|
112
|
+
if [[ -e "$WALK_DIR/.git" ]]; then
|
|
113
|
+
REPO_ROOT="$WALK_DIR"
|
|
114
|
+
break
|
|
115
|
+
fi
|
|
116
|
+
# Check home itself for .git, then stop. A non-git home must not inherit
|
|
117
|
+
# repository state from /Users, /home, or /.
|
|
118
|
+
if [[ -n "$HOME_BOUND" && "$WALK_DIR" == "$HOME_BOUND" ]]; then
|
|
119
|
+
break
|
|
120
|
+
fi
|
|
121
|
+
PARENT="$(dirname "$WALK_DIR")"
|
|
122
|
+
[[ "$PARENT" == "$WALK_DIR" ]] && break
|
|
123
|
+
WALK_DIR="$PARENT"
|
|
124
|
+
done
|
|
85
125
|
|
|
86
|
-
if [[ -z "$
|
|
126
|
+
if [[ -z "$REPO_ROOT" ]]; then
|
|
127
|
+
# Outside any repo: bare client name, matching deriveAgentId().
|
|
87
128
|
AGENT_ID="claude_code"
|
|
88
129
|
else
|
|
89
|
-
|
|
130
|
+
BASENAME="$(basename "$REPO_ROOT")"
|
|
131
|
+
PROJECT_SLUG="$(printf '%s' "$BASENAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9-]+/-/g; s/^-+|-+$//g')"
|
|
132
|
+
PROJECT_SLUG="${PROJECT_SLUG:0:64}"
|
|
133
|
+
|
|
134
|
+
if [[ -z "$PROJECT_SLUG" || "$PROJECT_SLUG" == "-" ]]; then
|
|
135
|
+
AGENT_ID="claude_code"
|
|
136
|
+
else
|
|
137
|
+
AGENT_ID="claude_code:$PROJECT_SLUG"
|
|
138
|
+
fi
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Read-only compatibility alias for pins written before MEKO-590.
|
|
142
|
+
LEGACY_BASENAME="$(basename "$CWD")"
|
|
143
|
+
LEGACY_SLUG="$(printf '%s' "$LEGACY_BASENAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9-]+/-/g; s/^-+|-+$//g')"
|
|
144
|
+
LEGACY_SLUG="${LEGACY_SLUG:0:64}"
|
|
145
|
+
if [[ -n "$LEGACY_SLUG" && "$LEGACY_SLUG" != "-" ]]; then
|
|
146
|
+
LEGACY_AGENT_ID="claude_code:$LEGACY_SLUG"
|
|
147
|
+
else
|
|
148
|
+
LEGACY_AGENT_ID="claude_code"
|
|
90
149
|
fi
|
|
91
150
|
fi
|
|
92
151
|
|
|
@@ -96,22 +155,70 @@ PIN_SLUG="$(printf '%s' "$AGENT_ID" | sed -E 's/[^A-Za-z0-9._-]+/_/g; s/^_+|_+$/
|
|
|
96
155
|
if [[ -z "$PIN_SLUG" ]]; then
|
|
97
156
|
PIN_SLUG="meko_agent"
|
|
98
157
|
fi
|
|
99
|
-
|
|
158
|
+
PIN_DIR="${MEKO_WATERMARK_DIR:-$USER_HOME/.claude/meko-capture}"
|
|
159
|
+
PIN_FILE="${PIN_DIR}/pin-${PIN_SLUG}.json"
|
|
160
|
+
LEGACY_PIN_SLUG="$(printf '%s' "$LEGACY_AGENT_ID" | sed -E 's/[^A-Za-z0-9._-]+/_/g; s/^_+|_+$//g')"
|
|
161
|
+
[[ -z "$LEGACY_PIN_SLUG" ]] && LEGACY_PIN_SLUG="meko_agent"
|
|
162
|
+
LEGACY_PIN_FILE="${PIN_DIR}/pin-${LEGACY_PIN_SLUG}.json"
|
|
100
163
|
|
|
101
164
|
# --- Read the pin file and append the suffix if a valid pin is present ---
|
|
102
165
|
|
|
166
|
+
HAVE_SEGMENT=0
|
|
167
|
+
if [[ -n "${INNER_OUT:-}" ]]; then
|
|
168
|
+
HAVE_SEGMENT=1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
PIN_NAME=""
|
|
103
172
|
if [[ -f "$PIN_FILE" ]]; then
|
|
104
173
|
# grep -oE isolates the exact "datapack_name":"…" pair before extracting, so a
|
|
105
174
|
# greedy `.*` can't grab a different key that merely ends in datapack_name.
|
|
106
175
|
PIN_NAME="$(grep -oE '"datapack_name"[[:space:]]*:[[:space:]]*"[^"]+"' "$PIN_FILE" | sed -E 's/.*"([^"]+)"$/\1/' | head -n 1)"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
176
|
+
fi
|
|
177
|
+
if [[ -z "$PIN_NAME" && "$LEGACY_PIN_FILE" != "$PIN_FILE" && -f "$LEGACY_PIN_FILE" ]]; then
|
|
178
|
+
PIN_NAME="$(grep -oE '"datapack_name"[[:space:]]*:[[:space:]]*"[^"]+"' "$LEGACY_PIN_FILE" | sed -E 's/.*"([^"]+)"$/\1/' | head -n 1)"
|
|
179
|
+
fi
|
|
180
|
+
if [[ -n "$PIN_NAME" ]]; then
|
|
181
|
+
SUFFIX="Meko: $PIN_NAME"
|
|
182
|
+
# Add a separator only if the inner command produced output.
|
|
183
|
+
if [[ "$HAVE_SEGMENT" -eq 1 ]]; then
|
|
184
|
+
printf ' \xe2\x80\xa2 %s' "$SUFFIX"
|
|
185
|
+
else
|
|
186
|
+
printf '%s' "$SUFFIX"
|
|
187
|
+
fi
|
|
188
|
+
HAVE_SEGMENT=1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
# --- Capture health suffix (Capture V2) ---
|
|
192
|
+
#
|
|
193
|
+
# Composable: appends only for catching_up / degraded / blocked. Healthy adds
|
|
194
|
+
# nothing. Reads rebuildable capture-health.json (no full watermark scan).
|
|
195
|
+
# Format mirrors statuslineSuffixFromHealth() in capture-notices.js.
|
|
196
|
+
|
|
197
|
+
WATERMARK_DIR="$PIN_DIR"
|
|
198
|
+
HEALTH_FILE="${WATERMARK_DIR}/capture-health.json"
|
|
199
|
+
CAPTURE_SUFFIX=""
|
|
200
|
+
if [[ -f "$HEALTH_FILE" ]]; then
|
|
201
|
+
HEALTH_STATUS="$(grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]+"' "$HEALTH_FILE" | sed -E 's/.*"([^"]+)"$/\1/' | head -n 1)"
|
|
202
|
+
QUEUED="$(grep -oE '"queued_exchanges"[[:space:]]*:[[:space:]]*[0-9]+' "$HEALTH_FILE" | sed -E 's/.*:[[:space:]]*([0-9]+)$/\1/' | head -n 1)"
|
|
203
|
+
QUEUED="${QUEUED:-0}"
|
|
204
|
+
case "$HEALTH_STATUS" in
|
|
205
|
+
catching_up)
|
|
206
|
+
CAPTURE_SUFFIX="capture:catching_up(${QUEUED})"
|
|
207
|
+
;;
|
|
208
|
+
degraded_retrying)
|
|
209
|
+
CAPTURE_SUFFIX="capture:degraded(${QUEUED})"
|
|
210
|
+
;;
|
|
211
|
+
blocked_action_required)
|
|
212
|
+
CAPTURE_SUFFIX="capture:blocked(${QUEUED})"
|
|
213
|
+
;;
|
|
214
|
+
esac
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
if [[ -n "$CAPTURE_SUFFIX" ]]; then
|
|
218
|
+
if [[ "$HAVE_SEGMENT" -eq 1 ]]; then
|
|
219
|
+
printf ' \xe2\x80\xa2 %s' "$CAPTURE_SUFFIX"
|
|
220
|
+
else
|
|
221
|
+
printf '%s' "$CAPTURE_SUFFIX"
|
|
115
222
|
fi
|
|
116
223
|
fi
|
|
117
224
|
|