@k-msg/cli 0.7.1 → 0.8.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/CHANGELOG.md +71 -0
- package/README.md +32 -7
- package/bin/k-msg.js +181 -4
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# @k-msg/cli
|
|
2
2
|
|
|
3
|
+
## 0.8.0 — 2026-02-21
|
|
4
|
+
|
|
5
|
+
### Minor changes
|
|
6
|
+
|
|
7
|
+
- [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.
|
|
8
|
+
|
|
9
|
+
## `@k-msg/channel` (minor)
|
|
10
|
+
|
|
11
|
+
- split exports into runtime-first root API and toolkit-only subpath (`@k-msg/channel/toolkit`)
|
|
12
|
+
- add runtime services:
|
|
13
|
+
- `KakaoChannelCapabilityService`
|
|
14
|
+
- `KakaoChannelBindingResolver`
|
|
15
|
+
- `KakaoChannelLifecycleService`
|
|
16
|
+
- add runtime channel binding/capability types:
|
|
17
|
+
- `KakaoChannelCapabilityMode`
|
|
18
|
+
- `KakaoChannelBinding`
|
|
19
|
+
- `ResolvedKakaoChannelBinding`
|
|
20
|
+
- `KakaoChannelListItem`
|
|
21
|
+
- add provider adapter flow for mode-specific handling (`aligo`, `iwinv`, `solapi`, `mock`)
|
|
22
|
+
|
|
23
|
+
## `@k-msg/cli` (minor)
|
|
24
|
+
|
|
25
|
+
- replace legacy `kakao channel` direct provider flow with channel runtime services
|
|
26
|
+
- add `kakao channel binding` command group:
|
|
27
|
+
- `list`, `resolve`, `set`, `delete`
|
|
28
|
+
- add `kakao channel api` command group:
|
|
29
|
+
- `categories`, `list`, `auth`, `add`
|
|
30
|
+
- remove legacy `kakao channel categories|list|auth|add` behavior and return guided migration errors
|
|
31
|
+
- unify senderKey/plusId resolution with channel binding resolver (including provider config hints such as `solapi.kakaoPfId`) — Thanks @imjlk!
|
|
32
|
+
- [adb3997](https://github.com/k-otp/k-msg/commit/adb3997754705ad24f7865e73e4bdff0f5a69360) Refactor template handling around `@k-msg/template` as the single runtime source of truth.
|
|
33
|
+
|
|
34
|
+
## `@k-msg/template` (minor)
|
|
35
|
+
|
|
36
|
+
- introduce runtime-first API surface:
|
|
37
|
+
- `TemplateLifecycleService`
|
|
38
|
+
- `TemplatePersonalizer`, `defaultTemplatePersonalizer`, `TemplateVariableUtils`
|
|
39
|
+
- `validateTemplatePayload`, `parseTemplateButtons`
|
|
40
|
+
- split builder/registry/testing helpers to a dedicated subpath: `@k-msg/template/toolkit`
|
|
41
|
+
- remove legacy root exports that overlapped service semantics (`TemplateService`, `MockTemplateService`, root-level builder/registry exports)
|
|
42
|
+
- move personalization implementation from messaging into template package
|
|
43
|
+
|
|
44
|
+
## `@k-msg/messaging` (minor)
|
|
45
|
+
|
|
46
|
+
- remove root personalization exports:
|
|
47
|
+
- `VariableReplacer`
|
|
48
|
+
- `VariableUtils`
|
|
49
|
+
- `defaultVariableReplacer`
|
|
50
|
+
- migration path: import the renamed equivalents from `@k-msg/template`
|
|
51
|
+
- `TemplatePersonalizer`
|
|
52
|
+
- `TemplateVariableUtils`
|
|
53
|
+
- `defaultTemplatePersonalizer`
|
|
54
|
+
|
|
55
|
+
## `@k-msg/cli` (minor)
|
|
56
|
+
|
|
57
|
+
- route `kakao template *` commands through `TemplateLifecycleService` instead of direct provider template method calls
|
|
58
|
+
- apply template runtime validation (`validateTemplatePayload`, `parseTemplateButtons`) before provider requests for create/update flows
|
|
59
|
+
|
|
60
|
+
## `@k-msg/provider` (patch)
|
|
61
|
+
|
|
62
|
+
- remove duplicate template interpolation path in Aligo send by reusing template runtime interpolation
|
|
63
|
+
- apply shared template payload/button validation to Aligo and IWINV template create/update flows
|
|
64
|
+
- normalize Aligo template button serialization through the shared template button parser/serializer — Thanks @imjlk!
|
|
65
|
+
|
|
66
|
+
### Patch changes
|
|
67
|
+
|
|
68
|
+
- [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.
|
|
69
|
+
|
|
70
|
+
- `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.
|
|
71
|
+
- 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!
|
|
72
|
+
- 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
|
|
73
|
+
|
|
3
74
|
## 0.7.1 — 2026-02-21
|
|
4
75
|
|
|
5
76
|
### 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
|
|
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
|
|
338
|
+
## Kakao Channel
|
|
324
339
|
|
|
325
340
|
```bash
|
|
326
|
-
|
|
327
|
-
k-msg kakao channel list
|
|
328
|
-
k-msg kakao channel
|
|
329
|
-
k-msg kakao channel
|
|
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))
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.8.0",
|
|
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/
|
|
37
|
-
"@k-msg/
|
|
38
|
-
"@k-msg/
|
|
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.
|
|
47
|
+
"k-msg": "0.19.1",
|
|
46
48
|
"solapi": "^5.5.4",
|
|
47
49
|
"typescript": "^5.7.2",
|
|
48
50
|
"zod": "^4.0.14"
|