@k-msg/cli 0.7.1 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,82 @@
1
1
  # @k-msg/cli
2
2
 
3
+ ## 0.8.1 — 2026-02-22
4
+
5
+ ### Patch changes
6
+
7
+ - Updated dependencies: channel@0.21.0, core@0.21.0, messaging@0.21.0, provider@0.21.0, template@0.21.0, k-msg@0.21.0
8
+
9
+ ## 0.8.0 — 2026-02-21
10
+
11
+ ### Minor changes
12
+
13
+ - [46c54c0](https://github.com/k-otp/k-msg/commit/46c54c059f004c34b0acf497a0d06343ff2b7d83) Refactor Kakao channel handling around `@k-msg/channel` runtime services and redesign CLI channel commands.
14
+
15
+ ## `@k-msg/channel` (minor)
16
+
17
+ - split exports into runtime-first root API and toolkit-only subpath (`@k-msg/channel/toolkit`)
18
+ - add runtime services:
19
+ - `KakaoChannelCapabilityService`
20
+ - `KakaoChannelBindingResolver`
21
+ - `KakaoChannelLifecycleService`
22
+ - add runtime channel binding/capability types:
23
+ - `KakaoChannelCapabilityMode`
24
+ - `KakaoChannelBinding`
25
+ - `ResolvedKakaoChannelBinding`
26
+ - `KakaoChannelListItem`
27
+ - add provider adapter flow for mode-specific handling (`aligo`, `iwinv`, `solapi`, `mock`)
28
+
29
+ ## `@k-msg/cli` (minor)
30
+
31
+ - replace legacy `kakao channel` direct provider flow with channel runtime services
32
+ - add `kakao channel binding` command group:
33
+ - `list`, `resolve`, `set`, `delete`
34
+ - add `kakao channel api` command group:
35
+ - `categories`, `list`, `auth`, `add`
36
+ - remove legacy `kakao channel categories|list|auth|add` behavior and return guided migration errors
37
+ - unify senderKey/plusId resolution with channel binding resolver (including provider config hints such as `solapi.kakaoPfId`) — Thanks @imjlk!
38
+ - [adb3997](https://github.com/k-otp/k-msg/commit/adb3997754705ad24f7865e73e4bdff0f5a69360) Refactor template handling around `@k-msg/template` as the single runtime source of truth.
39
+
40
+ ## `@k-msg/template` (minor)
41
+
42
+ - introduce runtime-first API surface:
43
+ - `TemplateLifecycleService`
44
+ - `TemplatePersonalizer`, `defaultTemplatePersonalizer`, `TemplateVariableUtils`
45
+ - `validateTemplatePayload`, `parseTemplateButtons`
46
+ - split builder/registry/testing helpers to a dedicated subpath: `@k-msg/template/toolkit`
47
+ - remove legacy root exports that overlapped service semantics (`TemplateService`, `MockTemplateService`, root-level builder/registry exports)
48
+ - move personalization implementation from messaging into template package
49
+
50
+ ## `@k-msg/messaging` (minor)
51
+
52
+ - remove root personalization exports:
53
+ - `VariableReplacer`
54
+ - `VariableUtils`
55
+ - `defaultVariableReplacer`
56
+ - migration path: import the renamed equivalents from `@k-msg/template`
57
+ - `TemplatePersonalizer`
58
+ - `TemplateVariableUtils`
59
+ - `defaultTemplatePersonalizer`
60
+
61
+ ## `@k-msg/cli` (minor)
62
+
63
+ - route `kakao template *` commands through `TemplateLifecycleService` instead of direct provider template method calls
64
+ - apply template runtime validation (`validateTemplatePayload`, `parseTemplateButtons`) before provider requests for create/update flows
65
+
66
+ ## `@k-msg/provider` (patch)
67
+
68
+ - remove duplicate template interpolation path in Aligo send by reusing template runtime interpolation
69
+ - apply shared template payload/button validation to Aligo and IWINV template create/update flows
70
+ - normalize Aligo template button serialization through the shared template button parser/serializer — Thanks @imjlk!
71
+
72
+ ### Patch changes
73
+
74
+ - [ce4bba0](https://github.com/k-otp/k-msg/commit/ce4bba07974365c681e29e6da2e007c968c84c74) Harden CLI installer and launcher path sync behavior to reduce unsafe overwrites across mixed install methods.
75
+
76
+ - `install.sh` now skips replacing active symlink/script launchers (for example package-manager shims) and falls back to `~/.local/bin` unless `K_MSG_CLI_INSTALL_DIR` is explicitly set.
77
+ - The npm/bun launcher sync logic now targets only active command entries and only replaces native executables, avoiding broad PATH scanning and accidental script replacement. — Thanks @imjlk!
78
+ - Updated dependencies: channel@0.20.0, core@0.20.0, messaging@0.20.0, provider@0.20.0, template@0.20.0, k-msg@0.20.0
79
+
3
80
  ## 0.7.1 — 2026-02-21
4
81
 
5
82
  ### Patch changes
package/README.md CHANGED
@@ -18,6 +18,10 @@ Note: the npm package downloads a native binary from GitHub Releases on first ru
18
18
  using `checksums.txt`, then extracts and caches it under your OS cache directory
19
19
  (`K_MSG_CLI_CACHE_DIR` to override).
20
20
 
21
+ When a new native binary is installed, the launcher also performs best-effort sync
22
+ for writable legacy `k-msg` binaries found on PATH (for example, older curl-based
23
+ installs). Set `K_MSG_CLI_SYNC_PATHS=0` to disable this behavior.
24
+
21
25
  Env overrides:
22
26
 
23
27
  - `K_MSG_CLI_BASE_URL`: override GitHub release base URL (default: `https://github.com/k-otp/k-msg/releases/download/cli-v<version>`)
@@ -33,9 +37,14 @@ curl -fsSL https://k-otp.github.io/k-msg/cli/install.sh | bash
33
37
  Installer environment variables:
34
38
 
35
39
  - `K_MSG_CLI_VERSION`: override target version (default: latest Pages script version)
36
- - `K_MSG_CLI_INSTALL_DIR`: target directory (default: `~/.local/bin`)
40
+ - `K_MSG_CLI_INSTALL_DIR`: target directory override (default: auto-detect active `k-msg` directory when writable, otherwise `~/.local/bin`)
37
41
  - `K_MSG_CLI_BASE_URL`: override release base URL (default: `https://github.com/k-otp/k-msg/releases/download/cli-v<version>`)
38
42
 
43
+ Path conflict note:
44
+
45
+ - The installer now prefers updating the currently active `k-msg` path when possible, and also refreshes that path if it differs from the selected install dir. This reduces stale-version issues when users previously installed via `bun`/`npm`/older `curl` flows.
46
+ - For safety, the installer does not overwrite active symlink/script launchers (for example package-manager shims). In that case it installs to `~/.local/bin` unless `K_MSG_CLI_INSTALL_DIR` is set.
47
+
39
48
  ### GitHub Releases (manual)
40
49
 
41
50
  The distribution workflow also publishes prebuilt binaries to GitHub Releases as:
@@ -178,9 +187,15 @@ Required values by provider/channel:
178
187
  - `k-msg alimtalk preflight|send`
179
188
  - `k-msg send --input <json> | --file <path> | --stdin` (advanced/raw JSON only)
180
189
  - `k-msg db schema print|generate`
181
- - `k-msg kakao channel categories|list|auth|add`
190
+ - `k-msg kakao channel binding list|resolve|set|delete`
191
+ - `k-msg kakao channel api categories|list|auth|add`
182
192
  - `k-msg kakao template list|get|create|update|delete|request`
183
193
 
194
+ Template command internals:
195
+
196
+ - `kakao template *` commands route through `TemplateLifecycleService` from `@k-msg/template`.
197
+ - `create/update` validate `name/content/buttons` using `validateTemplatePayload` + `parseTemplateButtons` before provider API calls.
198
+
184
199
  ## DB schema generator
185
200
 
186
201
  Generate canonical SQL DDL and/or Drizzle schema source from the same
@@ -320,13 +335,21 @@ Resolution precedence for overlapping values is:
320
335
 
321
336
  - `CLI flag > environment variable > config file > built-in default`
322
337
 
323
- ## Kakao Channel (Aligo capability)
338
+ ## Kakao Channel
324
339
 
325
340
  ```bash
326
- k-msg kakao channel categories
327
- k-msg kakao channel list
328
- k-msg kakao channel auth --plus-id @my_channel --phone 01012345678
329
- k-msg kakao channel add \
341
+ # config/provider-hint binding management (works for api/manual/none providers)
342
+ k-msg kakao channel binding list
343
+ k-msg kakao channel binding resolve --channel main
344
+ k-msg kakao channel binding set --alias main --provider aligo-main --sender-key SENDER_KEY --plus-id @my_channel
345
+ k-msg kakao channel binding delete --alias old-channel
346
+
347
+ # provider API operations (api-mode providers only, e.g. aligo/mock)
348
+ k-msg kakao channel api categories --provider aligo-main
349
+ k-msg kakao channel api list --provider aligo-main
350
+ k-msg kakao channel api auth --provider aligo-main --plus-id @my_channel --phone 01012345678
351
+ k-msg kakao channel api add \
352
+ --provider aligo-main \
330
353
  --plus-id @my_channel \
331
354
  --auth-num 123456 \
332
355
  --phone 01012345678 \
@@ -334,6 +357,8 @@ k-msg kakao channel add \
334
357
  --save main
335
358
  ```
336
359
 
360
+ Legacy notice: `k-msg kakao channel categories|list|auth|add` were removed. The CLI now prints guidance to the new `binding` / `api` command groups.
361
+
337
362
  ## Kakao Template (IWINV/Aligo)
338
363
 
339
364
  Channel scope (Aligo): use `--channel <alias>` or `--sender-key <value>`.
package/bin/k-msg.js CHANGED
@@ -139,6 +139,176 @@ function parseChecksums(text) {
139
139
  return out;
140
140
  }
141
141
 
142
+ function safeRealpath(filePath) {
143
+ try {
144
+ return fs.realpathSync(filePath);
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ function isWritableFile(filePath) {
151
+ try {
152
+ fs.accessSync(filePath, fs.constants.W_OK);
153
+ return true;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ function parseVersionFromOutput(output) {
160
+ const m = /\bk-msg v([0-9]+\.[0-9]+\.[0-9]+)\b/.exec(output);
161
+ return m ? m[1] : null;
162
+ }
163
+
164
+ function readCommandVersion(commandPath) {
165
+ const result = spawnSync(commandPath, ["--version"], {
166
+ encoding: "utf8",
167
+ stdio: ["ignore", "pipe", "pipe"],
168
+ timeout: 3000,
169
+ });
170
+ if (result.error || result.status !== 0) return null;
171
+ const output = `${result.stdout || ""}\n${result.stderr || ""}`;
172
+ return parseVersionFromOutput(output);
173
+ }
174
+
175
+ function resolveActiveCommand(commandName) {
176
+ const pathValue = process.env.PATH || "";
177
+ const dirs = pathValue.split(path.delimiter).filter(Boolean);
178
+ const suffixes =
179
+ process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
180
+
181
+ for (const dir of dirs) {
182
+ for (const suffix of suffixes) {
183
+ const candidate = path.join(dir, `${commandName}${suffix}`);
184
+ if (fs.existsSync(candidate)) {
185
+ return candidate;
186
+ }
187
+ }
188
+ }
189
+
190
+ return null;
191
+ }
192
+
193
+ function listActiveCommandCandidates() {
194
+ const seen = new Set();
195
+ const out = [];
196
+ for (const name of ["k-msg", "kmsg"]) {
197
+ const resolved = resolveActiveCommand(name);
198
+ if (!resolved || seen.has(resolved)) continue;
199
+ seen.add(resolved);
200
+ out.push(resolved);
201
+ }
202
+ return out;
203
+ }
204
+
205
+ function isLikelyNativeExecutable(filePath) {
206
+ let fd;
207
+ try {
208
+ fd = fs.openSync(filePath, "r");
209
+ const buffer = Buffer.alloc(4);
210
+ const read = fs.readSync(fd, buffer, 0, 4, 0);
211
+ if (read < 2) return false;
212
+
213
+ // ELF
214
+ if (
215
+ read >= 4 &&
216
+ buffer[0] === 0x7f &&
217
+ buffer[1] === 0x45 &&
218
+ buffer[2] === 0x4c &&
219
+ buffer[3] === 0x46
220
+ ) {
221
+ return true;
222
+ }
223
+
224
+ // Mach-O (32/64, little/big endian)
225
+ if (read >= 4) {
226
+ const magic = buffer.readUInt32BE(0);
227
+ if (
228
+ magic === 0xfeedface ||
229
+ magic === 0xfeedfacf ||
230
+ magic === 0xcefaedfe ||
231
+ magic === 0xcffaedfe
232
+ ) {
233
+ return true;
234
+ }
235
+ }
236
+
237
+ // PE/COFF (Windows)
238
+ if (buffer[0] === 0x4d && buffer[1] === 0x5a) {
239
+ return true;
240
+ }
241
+ } catch {
242
+ return false;
243
+ } finally {
244
+ if (typeof fd === "number") {
245
+ try {
246
+ fs.closeSync(fd);
247
+ } catch {
248
+ // ignore
249
+ }
250
+ }
251
+ }
252
+
253
+ return false;
254
+ }
255
+
256
+ function replaceBinary({ sourcePath, targetPath }) {
257
+ const tmpPath = `${targetPath}.kmsg-sync-${process.pid}.tmp`;
258
+ fs.copyFileSync(sourcePath, tmpPath);
259
+ if (process.platform !== "win32") {
260
+ fs.chmodSync(tmpPath, 0o755);
261
+ }
262
+ fs.renameSync(tmpPath, targetPath);
263
+ }
264
+
265
+ function syncLegacyCommandPaths({ binaryPath, version }) {
266
+ const syncOptOut = process.env.K_MSG_CLI_SYNC_PATHS;
267
+ if (
268
+ typeof syncOptOut === "string" &&
269
+ ["0", "false", "off"].includes(syncOptOut.trim().toLowerCase())
270
+ ) {
271
+ return;
272
+ }
273
+
274
+ const selfScript = safeRealpath(process.argv[1] || "");
275
+ const binaryReal = safeRealpath(binaryPath);
276
+ const updated = [];
277
+
278
+ for (const candidate of listActiveCommandCandidates()) {
279
+ let stat;
280
+ try {
281
+ stat = fs.lstatSync(candidate);
282
+ } catch {
283
+ continue;
284
+ }
285
+
286
+ if (!stat.isFile() || stat.isSymbolicLink()) continue;
287
+ if (!isWritableFile(candidate)) continue;
288
+ if (!isLikelyNativeExecutable(candidate)) continue;
289
+
290
+ const candidateReal = safeRealpath(candidate);
291
+ if (candidateReal && selfScript && candidateReal === selfScript) continue;
292
+ if (candidateReal && binaryReal && candidateReal === binaryReal) continue;
293
+
294
+ const currentVersion = readCommandVersion(candidate);
295
+ if (!currentVersion || currentVersion === version) continue;
296
+
297
+ try {
298
+ replaceBinary({ sourcePath: binaryPath, targetPath: candidate });
299
+ updated.push({ path: candidate, from: currentVersion });
300
+ } catch {
301
+ // Best-effort sync: ignore per-path failures.
302
+ }
303
+ }
304
+
305
+ for (const item of updated) {
306
+ console.error(
307
+ `[k-msg] Synced existing install: ${item.path} (${item.from} -> ${version})`,
308
+ );
309
+ }
310
+ }
311
+
142
312
  function tarReadString(header, start, len) {
143
313
  const slice = header.subarray(start, start + len);
144
314
  const zero = slice.indexOf(0);
@@ -194,7 +364,9 @@ async function ensureBinary() {
194
364
  const cacheDir = path.join(cacheBaseDir(), "k-msg", "cli", version, target);
195
365
  const dest = path.join(cacheDir, `k-msg${ext}`);
196
366
 
197
- if (fs.existsSync(dest)) return dest;
367
+ if (fs.existsSync(dest)) {
368
+ return { binaryPath: dest, version, freshlyInstalled: false };
369
+ }
198
370
 
199
371
  const local = process.env.K_MSG_CLI_LOCAL_BINARY;
200
372
  if (typeof local === "string" && local.trim().length > 0) {
@@ -203,7 +375,7 @@ async function ensureBinary() {
203
375
  if (process.platform !== "win32") {
204
376
  fs.chmodSync(dest, 0o755);
205
377
  }
206
- return dest;
378
+ return { binaryPath: dest, version, freshlyInstalled: true };
207
379
  }
208
380
 
209
381
  const baseUrl =
@@ -247,7 +419,7 @@ async function ensureBinary() {
247
419
  fs.chmodSync(dest, 0o755);
248
420
  }
249
421
  fs.rmSync(archiveTmp, { force: true });
250
- return dest;
422
+ return { binaryPath: dest, version, freshlyInstalled: true };
251
423
  } catch (err) {
252
424
  fs.rmSync(archiveTmp, { force: true });
253
425
  fs.rmSync(binTmp, { force: true });
@@ -256,7 +428,12 @@ async function ensureBinary() {
256
428
  }
257
429
 
258
430
  async function main() {
259
- const bin = await ensureBinary();
431
+ const resolved = await ensureBinary();
432
+ syncLegacyCommandPaths({
433
+ binaryPath: resolved.binaryPath,
434
+ version: resolved.version,
435
+ });
436
+ const bin = resolved.binaryPath;
260
437
  const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
261
438
  if (result.error) {
262
439
  console.error(result.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-msg/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "private": false,
5
5
  "description": "k-msg CLI (prebuilt binaries via GitHub Releases)",
6
6
  "type": "module",
@@ -33,16 +33,18 @@
33
33
  "clean": "rm -rf dist"
34
34
  },
35
35
  "devDependencies": {
36
- "@k-msg/core": "0.18.2",
37
- "@k-msg/messaging": "0.18.2",
38
- "@k-msg/provider": "0.18.2",
36
+ "@k-msg/channel": "0.19.1",
37
+ "@k-msg/core": "0.19.1",
38
+ "@k-msg/messaging": "0.19.1",
39
+ "@k-msg/provider": "0.19.1",
40
+ "@k-msg/template": "0.19.1",
39
41
  "@bunli/core": "^0.5.4",
40
42
  "@bunli/plugin-ai-detect": "^0.5.2",
41
43
  "@bunli/test": "^0.3.2",
42
44
  "@types/bun": "^1.3.9",
43
45
  "@types/node": "^22.0.0",
44
46
  "bunli": "^0.5.3",
45
- "k-msg": "0.18.2",
47
+ "k-msg": "0.19.1",
46
48
  "solapi": "^5.5.4",
47
49
  "typescript": "^5.7.2",
48
50
  "zod": "^4.0.14"