--- install scripts ---
### postinstall
node ./scripts/npm-postinstall.js
--- package/package.json (excerpt) ---
{
"name": "code-notify",
"version": "1.9.0",
"description": "Desktop notifications for Claude Code, OpenAI Codex, and Gemini CLI",
"license": "MIT",
"homepage": "https://github.com/mylee04/code-notify#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/mylee04/code-notify.git"
},
"bugs": {
"url": "https://github.com/mylee04/code-notify/issues"
},
"keywords": [
"ai",
"claude",
"codex",
"gemini",
"notifications",
"cli"
],
"engines": {
"node": ">=18"
},
"bin": {
"code-notify": "bin/npm-code-notify.js",
"cn": "bin/npm-cn.js",
"cnp": "bin/npm-cnp.js"
},
"files": [
"LICENSE",
"README.md",
"assets/audio/",
"bin/",
"lib/",
"scripts/install-windows.ps1",
"scripts/npm-postinstall.js"
],
"scripts": {
"postinstall": "node ./scripts/npm-postinstall.js"
}
}
--- package/lib/npm/launcher.js (excerpt) ---
#!/usr/bin/env node
'use strict';
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const repoRoot = path.resolve(__dirname, '..', '..');
const windowsShellCandidates = ['powershell.exe', 'powershell', 'pwsh.exe', 'pwsh'];
function exitWithSpawnResult(result, fallbackMessage) {
if (result.error) {
if (result.error.code === 'ENOENT') {
console.error(fallbackMessage);
} else {
console.error(result.error.message);
}
process.exit(1);
}
if (typeof result.status === 'number') {
process.exit(result.status);
}
process.exit(1);
}
function spawnWithCandidates(candidates, args, options, missingMessage) {
let lastResult = null;
for (const candidate of candidates) {
const result = spawnSync(candidate, args, options);
if (result.error && result.error.code === 'ENOENT') {
lastResult = result;
continue;
}
return result;
}
return lastResult || {
error: new Error(missingMessage)
};
}
function ensureFile(filePath, message) {
if (!fs.existsSync(filePath)) {
console.error(message);
process.exit(1);
}
}
function runUnixCli(commandName, args) {
const shellScript = path.join(repoRoot, 'bin', 'code-notify');
ensureFile(shellScript, `code-notify runtime not found at ${shellScript}`);
const result = spawnSync(
'bash',
[shellScript, ...args],
{
stdio: 'inherit',
env: {
--- package/lib/code-notify/utils/channels.sh (excerpt) ---
#!/bin/bash
CHANNELS_DIR="${CODE_NOTIFY_CONFIG_DIR:-$HOME/.config/code-notify}"
CHANNELS_FILE="$CHANNELS_DIR/channels.json"
channels_has_python3() {
command -v python3 >/dev/null 2>&1
}
ensure_channels_dir() {
mkdir -p "$CHANNELS_DIR"
chmod 700 "$CHANNELS_DIR" 2>/dev/null || true
}
channels_write_json() {
local json="$1"
local tmp_file
ensure_channels_dir
tmp_file=$(mktemp "$CHANNELS_DIR/.channels.XXXXXX") || return 1
printf '%s\n' "$json" > "$tmp_file" || {
rm -f "$tmp_file"
return 1
}
chmod 600 "$tmp_file" 2>/dev/null || true
mv "$tmp_file" "$CHANNELS_FILE"
}
channels_default_json() {
printf '%s\n' '{"enabled":true,"channels":[]}'
}
channels_read_json() {
if [[ -f "$CHANNELS_FILE" ]]; then
cat "$CHANNELS_FILE"
else
channels_default_json
fi
}
channels_validate_provider_url() {
local provider="$1"
local url="$2"
case "$provider:$url" in
slack:https://hooks.slack.com/*|slack:https://hooks.slack-gov.com/*)
return 0
;;
discord:https://discord.com/api/webhooks/*|discord:https://discordapp.com/api/webhooks/*)
return 0
;;
esac
return 1
}
channels_add() {
local provider="$1"
local url="$2"
local name="$3"
local current updated
[[ -n "$name" ]] || name="$provider"
if [[ "$provider" != "slack" && "$provider" != "discord" ]]; then
error "Unsupported channel provider: ${
"name": "code-notify",
"version": "1.9.0",
"description": "Desktop notifications for Claude Code, OpenAI Codex, and Gemini CLI",
"license": "MIT",
"homepage": "https://github.com/mylee04/code-notify#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/mylee04/code-notify.git"
},
"bugs": {
"url": "https://github.com/mylee04/code-notify/issues"
},
"keywords": [
"ai",
"claude",
"codex",
"gemini",
"notifications",
"cli"
],
"engines": {
"node": ">=18"
},
"bin": {
"code-notify": "bin/npm-code-notify.js",
"cn": "bin/npm-cn.js",
"cnp": "bin/npm-cnp.js"
},
"files": [
"LICENSE",
"README.md",
"assets/audio/",
"bin/",
"lib/",
"scripts/install-windows.ps1",
"scripts/npm-postinstall.js"
],
"scripts": {
"postinstall": "node ./scripts/npm-postinstall.js"
}
}
#!/usr/bin/env node
'use strict';
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const repoRoot = path.resolve(__dirname, '..', '..');
const windowsShellCandidates = ['powershell.exe', 'powershell', 'pwsh.exe', 'pwsh'];
function exitWithSpawnResult(result, fallbackMessage) {
if (result.error) {
if (result.error.code === 'ENOENT') {
console.error(fallbackMessage);
} else {
console.error(result.error.message);
}
process.exit(1);
}
if (typeof result.status === 'number') {
process.exit(result.status);
}
process.exit(1);
}
function spawnWithCandidates(candidates, args, options, missingMessage) {
let lastResult = null;
for (const candidate of candidates) {
const result = spawnSync(candidate, args, options);
if (result.error && result.error.code === 'ENOENT') {
lastResult = result;
continue;
}
return result;
}
return lastResult || {
error: new Error(missingMessage)
};
}
function ensureFile(filePath, message) {
if (!fs.existsSync(filePath)) {
console.error(message);
process.exit(1);
}
}
function runUnixCli(commandName, args) {
const shellScript = path.join(repoRoot, 'bin', 'code-notify');
ensureFile(shellScript, `code-notify runtime not found at ${shellScript}`);
const result = spawnSync(
'bash',
[shellScript, ...args],
{
stdio: 'inherit',
env: {