slacklocalvibe 0.1.3 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slacklocalvibe",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "SlackLocalVibe: Codex/Claude Code turn notifications and reply→resume bridge for Slack DM (Socket Mode)",
5
5
  "bin": {
6
6
  "slacklocalvibe": "src/cli.js"
@@ -69,7 +69,8 @@ async function runNotify({ tool }) {
69
69
  throw new Error("通知対象のイベントではありません。");
70
70
  }
71
71
  if (input.skip) {
72
- log(LEVELS.WARNING, "notify.skip.empty_input_messages", {
72
+ const reason = input.skip_reason || "unknown";
73
+ log(LEVELS.WARNING, `notify.skip.${reason}`, {
73
74
  meta: input.meta || {},
74
75
  duration_ms: Date.now() - startedAt,
75
76
  });
@@ -109,7 +109,7 @@ async function runWizard() {
109
109
  { title: "アップデートを確認する", value: "update" },
110
110
  { title: "テストから始める", value: "test" },
111
111
  { title: "リセットして最初からセットアップ", value: "reset" },
112
- { title: "終了(保存せず終了)", value: "exit" },
112
+ { title: "終了", value: "exit" },
113
113
  ],
114
114
  initial: 0,
115
115
  });
@@ -137,7 +137,7 @@ async function runWizard() {
137
137
  choices: [
138
138
  { title: "アップデートする", value: "update" },
139
139
  { title: "戻る", value: "back" },
140
- { title: "終了(保存せず終了)", value: "exit" },
140
+ { title: "終了", value: "exit" },
141
141
  ],
142
142
  initial: 0,
143
143
  });
@@ -202,7 +202,7 @@ async function runWizard() {
202
202
  message: "次の操作を選んでください",
203
203
  choices: [
204
204
  { title: "リセットして最初から", value: "reset" },
205
- { title: "終了(保存せず終了)", value: "exit" },
205
+ { title: "終了", value: "exit" },
206
206
  ],
207
207
  });
208
208
  if (next === "reset") {
@@ -404,7 +404,7 @@ async function stepReplySetup({ log }) {
404
404
  message: "次の操作を選んでください",
405
405
  choices: [
406
406
  { title: "npm i -g slacklocalvibe で登録する(必須)", value: "install" },
407
- { title: "終了(保存せず終了)", value: "exit" },
407
+ { title: "終了", value: "exit" },
408
408
  ],
409
409
  initial: 0,
410
410
  });
@@ -467,7 +467,7 @@ async function stepNotifyTest({ log, botToken, dmConfig }) {
467
467
  choices: [
468
468
  { title: "Bot Token を再入力する", value: "retry_token" },
469
469
  { title: "送信先(DM)を見直す", value: "retry_dm" },
470
- { title: "終了(保存せず終了)", value: "exit" },
470
+ { title: "終了", value: "exit" },
471
471
  ],
472
472
  });
473
473
  if (choice === "retry_token") {
@@ -527,7 +527,7 @@ async function stepCodexConfig({ log }) {
527
527
  message: "Codex設定の更新に失敗しました。次の操作を選んでください",
528
528
  choices: [
529
529
  { title: "再試行", value: "retry" },
530
- { title: "終了(保存せず終了)", value: "exit" },
530
+ { title: "終了", value: "exit" },
531
531
  ],
532
532
  });
533
533
  if (next === "retry") continue;
@@ -561,7 +561,7 @@ async function stepClaudeConfig({ log }) {
561
561
  message: "Claude設定の更新に失敗しました。次の操作を選んでください",
562
562
  choices: [
563
563
  { title: "再試行", value: "retry" },
564
- { title: "終了(保存せず終了)", value: "exit" },
564
+ { title: "終了", value: "exit" },
565
565
  ],
566
566
  });
567
567
  if (next === "retry") continue;
@@ -596,7 +596,7 @@ async function stepDeliveryConfirmation({ log, stage }) {
596
596
  choices: [
597
597
  { title: "届いたので次へ進む", value: "ok" },
598
598
  { title: "届いていないのでログを表示する", value: "logs" },
599
- { title: "終了(保存せず終了)", value: "exit" },
599
+ { title: "終了", value: "exit" },
600
600
  ],
601
601
  });
602
602
  if (choice === "ok") {
@@ -709,7 +709,7 @@ async function runTestCommand({ log, tool }) {
709
709
  message: "次の操作を選んでください",
710
710
  choices: [
711
711
  { title: "再試行", value: "retry" },
712
- { title: "終了(保存せず終了)", value: "exit" },
712
+ { title: "終了", value: "exit" },
713
713
  ],
714
714
  });
715
715
  if (next === "retry") continue;
@@ -727,7 +727,7 @@ async function stepLaunchd({ log }) {
727
727
  choices: [
728
728
  { title: "launchd に登録する(推奨)", value: "install" },
729
729
  { title: "スキップして次へ進む", value: "skip" },
730
- { title: "終了(保存せず終了)", value: "exit" },
730
+ { title: "終了", value: "exit" },
731
731
  ],
732
732
  initial: 0,
733
733
  });
@@ -763,7 +763,7 @@ async function stepLaunchd({ log }) {
763
763
  choices: [
764
764
  { title: "再試行", value: "retry" },
765
765
  { title: "スキップして次へ進む", value: "skip" },
766
- { title: "終了(保存せず終了)", value: "exit" },
766
+ { title: "終了", value: "exit" },
767
767
  ],
768
768
  });
769
769
  if (next === "retry") continue;
@@ -974,7 +974,7 @@ async function copyManifestToClipboard({ log }) {
974
974
  message: "次の操作を選んでください",
975
975
  choices: [
976
976
  { title: "再試行", value: "retry" },
977
- { title: "終了(保存せず終了)", value: "exit" },
977
+ { title: "終了", value: "exit" },
978
978
  ],
979
979
  });
980
980
  if (next === "retry") continue;
@@ -1116,7 +1116,7 @@ async function ensureGlobalInstall({ log }) {
1116
1116
  message: "次の操作を選んでください",
1117
1117
  choices: [
1118
1118
  { title: "再試行", value: "retry" },
1119
- { title: "終了(保存せず終了)", value: "exit" },
1119
+ { title: "終了", value: "exit" },
1120
1120
  ],
1121
1121
  });
1122
1122
  if (next === "retry") continue;
@@ -1195,7 +1195,7 @@ async function promptLaunchdReinstall({ log, recommended }) {
1195
1195
  choices: [
1196
1196
  { title, value: "install" },
1197
1197
  { title: "あとでやる", value: "skip" },
1198
- { title: "終了(保存せず終了)", value: "exit" },
1198
+ { title: "終了", value: "exit" },
1199
1199
  ],
1200
1200
  initial: 0,
1201
1201
  });
@@ -1230,7 +1230,7 @@ async function promptLaunchdReinstall({ log, recommended }) {
1230
1230
  choices: [
1231
1231
  { title: "再試行", value: "retry" },
1232
1232
  { title: "あとでやる", value: "skip" },
1233
- { title: "終了(保存せず終了)", value: "exit" },
1233
+ { title: "終了", value: "exit" },
1234
1234
  ],
1235
1235
  });
1236
1236
  if (next === "retry") continue;
@@ -2,6 +2,21 @@ const fs = require("fs");
2
2
  const os = require("os");
3
3
  const path = require("path");
4
4
 
5
+ const CODEX_INTERNAL_TITLE_PROMPT_PREFIX =
6
+ "You are a helpful assistant. You will be presented with a user prompt";
7
+ const CODEX_INTERNAL_TITLE_PROMPT_LINES = [
8
+ "Generate a concise UI title (18-36 characters) for this task.",
9
+ "Return only the title. No quotes or trailing punctuation.",
10
+ "Do not use markdown or formatting characters.",
11
+ "If the task includes a ticket reference (e.g. ABC-123), include it verbatim.",
12
+ "Generate a clear, informative task title based solely on the prompt provided. Follow the rules below to ensure consistency, readability, and usefulness.",
13
+ "How to write a good title:",
14
+ "Generate a single-line title that captures the question or core change requested. The title should be easy to scan and useful in changelogs or review queues.",
15
+ "By following these conventions, your titles will be readable, changelog-friendly, and helpful to both users and downstream tools.",
16
+ "Examples:",
17
+ ];
18
+ const CODEX_INTERNAL_TITLE_PROMPT_MIN_MATCHES = 3;
19
+
5
20
  function parseCodexNotify(rawJson) {
6
21
  const payload = JSON.parse(rawJson);
7
22
  if (payload?.type !== "agent-turn-complete") {
@@ -11,9 +26,33 @@ function parseCodexNotify(rawJson) {
11
26
  const turnId = payload["turn-id"] ? String(payload["turn-id"]) : undefined;
12
27
  const inputMessages = payload["input-messages"];
13
28
  const meta = buildCodexInputMeta(inputMessages);
14
- const rolloutResult = readCodexUserMessageFromRollout(sessionId);
29
+ const codexHomeInfo = resolveCodexHomeInfo();
30
+ Object.assign(meta, codexHomeInfo.meta || {});
31
+ if (codexHomeInfo.isDefault) {
32
+ return {
33
+ tool: "codex",
34
+ skip: true,
35
+ skip_reason: "codex_home_default",
36
+ meta,
37
+ };
38
+ }
39
+ const rolloutResult = readCodexUserMessageFromRollout(sessionId, codexHomeInfo.home);
15
40
  Object.assign(meta, rolloutResult.meta || {});
16
41
  const userText = rolloutResult.userText || "";
42
+ const internalPromptCheck = matchCodexInternalTitlePrompt(userText);
43
+ Object.assign(meta, {
44
+ codex_internal_title_prompt_match: internalPromptCheck.match,
45
+ codex_internal_title_prompt_hits: internalPromptCheck.hits,
46
+ codex_internal_title_prompt_first_line: internalPromptCheck.firstLine,
47
+ });
48
+ if (internalPromptCheck.match) {
49
+ return {
50
+ tool: "codex",
51
+ skip: true,
52
+ skip_reason: "codex_internal_title_prompt",
53
+ meta,
54
+ };
55
+ }
17
56
  const assistantText = extractAssistantText(payload["last-assistant-message"]);
18
57
  const cwd = payload?.cwd ? String(payload.cwd) : "";
19
58
 
@@ -68,7 +107,35 @@ function extractAssistantText(content) {
68
107
  return normalizeContent(content);
69
108
  }
70
109
 
71
- function readCodexUserMessageFromRollout(sessionId) {
110
+ function matchCodexInternalTitlePrompt(text) {
111
+ const firstLine = firstNonEmptyLine(text);
112
+ if (!firstLine.startsWith(CODEX_INTERNAL_TITLE_PROMPT_PREFIX)) {
113
+ return { match: false, hits: 0, firstLine };
114
+ }
115
+ let hits = 0;
116
+ for (const line of CODEX_INTERNAL_TITLE_PROMPT_LINES) {
117
+ if (text.includes(line)) {
118
+ hits += 1;
119
+ }
120
+ }
121
+ return {
122
+ match: hits >= CODEX_INTERNAL_TITLE_PROMPT_MIN_MATCHES,
123
+ hits,
124
+ firstLine,
125
+ };
126
+ }
127
+
128
+ function firstNonEmptyLine(text) {
129
+ if (!text) return "";
130
+ const lines = String(text).split(/\r?\n/);
131
+ for (const line of lines) {
132
+ const trimmed = line.trim();
133
+ if (trimmed) return trimmed;
134
+ }
135
+ return "";
136
+ }
137
+
138
+ function readCodexUserMessageFromRollout(sessionId, codexHome) {
72
139
  const meta = {
73
140
  codex_rollout_found: false,
74
141
  codex_rollout_source: "",
@@ -79,7 +146,7 @@ function readCodexUserMessageFromRollout(sessionId) {
79
146
  codex_rollout_line_count: 0,
80
147
  codex_rollout_error: "",
81
148
  };
82
- const rolloutPath = findCodexRolloutPath(sessionId, meta);
149
+ const rolloutPath = findCodexRolloutPath(sessionId, meta, codexHome);
83
150
  if (!rolloutPath) {
84
151
  return { userText: "", meta };
85
152
  }
@@ -129,12 +196,13 @@ function extractCodexUserMessage(payload) {
129
196
  return normalizeContent(content);
130
197
  }
131
198
 
132
- function findCodexRolloutPath(sessionId, meta) {
199
+ function findCodexRolloutPath(sessionId, meta, codexHome) {
133
200
  if (!sessionId) {
134
201
  meta.codex_rollout_error = "session_id_missing";
135
202
  return "";
136
203
  }
137
- const sessionsDir = path.join(os.homedir(), ".codex", "sessions");
204
+ const baseHome = codexHome || path.join(os.homedir(), ".codex");
205
+ const sessionsDir = path.join(baseHome, "sessions");
138
206
  meta.codex_sessions_dir = sessionsDir;
139
207
  if (!fs.existsSync(sessionsDir)) {
140
208
  meta.codex_rollout_error = "sessions_dir_missing";
@@ -239,6 +307,38 @@ function findRolloutByContent(files, sessionId, meta) {
239
307
  return null;
240
308
  }
241
309
 
310
+ function resolveCodexHomeInfo() {
311
+ const defaultHome = path.join(os.homedir(), ".codex");
312
+ const defaultResolved = path.resolve(defaultHome);
313
+ const envHome = process.env.CODEX_HOME;
314
+ if (!envHome) {
315
+ return {
316
+ home: defaultResolved,
317
+ isDefault: true,
318
+ isSet: false,
319
+ meta: {
320
+ codex_home: defaultResolved,
321
+ codex_home_default: defaultResolved,
322
+ codex_home_is_default: true,
323
+ codex_home_set: false,
324
+ },
325
+ };
326
+ }
327
+ const resolved = path.resolve(envHome);
328
+ const isDefault = resolved === defaultResolved;
329
+ return {
330
+ home: resolved,
331
+ isDefault,
332
+ isSet: true,
333
+ meta: {
334
+ codex_home: resolved,
335
+ codex_home_default: defaultResolved,
336
+ codex_home_is_default: isDefault,
337
+ codex_home_set: true,
338
+ },
339
+ };
340
+ }
341
+
242
342
  function extractTextDeep(value, depth = 0) {
243
343
  if (depth > 6) return "";
244
344
  if (value === null || value === undefined) return "";