nonotify 0.1.0 → 0.1.3
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/.github/workflows/release.yml +113 -0
- package/README.md +2 -4
- package/dist/cli.js +125 -97
- package/dist/config.js +15 -11
- package/dist/display.js +5 -5
- package/dist/prompt.js +7 -7
- package/dist/telegram.js +12 -10
- package/nonotify-0.1.2.tgz +0 -0
- package/package.json +8 -2
- package/src/cli.ts +308 -223
- package/src/config.ts +39 -36
- package/src/display.ts +20 -17
- package/src/prompt.ts +36 -27
- package/src/telegram.ts +77 -53
- package/tsconfig.json +1 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
name: Release & Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 20
|
|
24
|
+
registry-url: https://registry.npmjs.org/
|
|
25
|
+
|
|
26
|
+
- name: Decide whether to release
|
|
27
|
+
id: guard
|
|
28
|
+
shell: bash
|
|
29
|
+
run: |
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
VERSION="$(node -p "require('./package.json').version")"
|
|
33
|
+
NAME="$(node -p "require('./package.json').name")"
|
|
34
|
+
|
|
35
|
+
git fetch --tags --force
|
|
36
|
+
|
|
37
|
+
# Latest tag (empty if no tags yet)
|
|
38
|
+
LATEST_TAG="$(git tag --sort=-v:refname | head -n 1 || true)"
|
|
39
|
+
|
|
40
|
+
# Does tag for this version already exist?
|
|
41
|
+
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
|
|
42
|
+
TAG_EXISTS="true"
|
|
43
|
+
else
|
|
44
|
+
TAG_EXISTS="false"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Is that version already on npm?
|
|
48
|
+
if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
|
|
49
|
+
NPM_EXISTS="true"
|
|
50
|
+
else
|
|
51
|
+
NPM_EXISTS="false"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Decide
|
|
55
|
+
SHOULD_RELEASE="true"
|
|
56
|
+
REASON="ok"
|
|
57
|
+
|
|
58
|
+
if [[ "$TAG_EXISTS" == "true" ]]; then
|
|
59
|
+
SHOULD_RELEASE="false"
|
|
60
|
+
REASON="git tag already exists"
|
|
61
|
+
elif [[ -n "$LATEST_TAG" && "$VERSION" == "$LATEST_TAG" ]]; then
|
|
62
|
+
SHOULD_RELEASE="false"
|
|
63
|
+
REASON="package.json version equals latest tag"
|
|
64
|
+
elif [[ "$NPM_EXISTS" == "true" ]]; then
|
|
65
|
+
SHOULD_RELEASE="false"
|
|
66
|
+
REASON="version already published on npm"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
70
|
+
echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
|
|
71
|
+
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
|
72
|
+
echo "npm_exists=$NPM_EXISTS" >> "$GITHUB_OUTPUT"
|
|
73
|
+
echo "should_release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
|
|
74
|
+
echo "reason=$REASON" >> "$GITHUB_OUTPUT"
|
|
75
|
+
|
|
76
|
+
echo "Decision: should_release=$SHOULD_RELEASE ($REASON)"
|
|
77
|
+
|
|
78
|
+
- name: Stop summary
|
|
79
|
+
if: steps.guard.outputs.should_release != 'true'
|
|
80
|
+
run: |
|
|
81
|
+
echo "Skipping release: ${{ steps.guard.outputs.reason }}"
|
|
82
|
+
echo "Version: ${{ steps.guard.outputs.version }}"
|
|
83
|
+
echo "Latest tag: ${{ steps.guard.outputs.latest_tag }}"
|
|
84
|
+
|
|
85
|
+
- name: Install dependencies
|
|
86
|
+
if: steps.guard.outputs.should_release == 'true'
|
|
87
|
+
run: npm ci
|
|
88
|
+
|
|
89
|
+
- name: Run tests
|
|
90
|
+
if: steps.guard.outputs.should_release == 'true'
|
|
91
|
+
run: npm test
|
|
92
|
+
|
|
93
|
+
- name: Create git tag
|
|
94
|
+
if: steps.guard.outputs.should_release == 'true'
|
|
95
|
+
run: |
|
|
96
|
+
git config user.name "github-actions"
|
|
97
|
+
git config user.email "github-actions@github.com"
|
|
98
|
+
git tag "${{ steps.guard.outputs.version }}"
|
|
99
|
+
git push origin "${{ steps.guard.outputs.version }}"
|
|
100
|
+
|
|
101
|
+
- name: Create GitHub Release
|
|
102
|
+
if: steps.guard.outputs.should_release == 'true'
|
|
103
|
+
uses: softprops/action-gh-release@v2
|
|
104
|
+
with:
|
|
105
|
+
tag_name: ${{ steps.guard.outputs.version }}
|
|
106
|
+
name: Release ${{ steps.guard.outputs.version }}
|
|
107
|
+
generate_release_notes: true
|
|
108
|
+
|
|
109
|
+
- name: Publish to npm
|
|
110
|
+
if: steps.guard.outputs.should_release == 'true'
|
|
111
|
+
run: npm publish
|
|
112
|
+
env:
|
|
113
|
+
NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
|
package/README.md
CHANGED
|
@@ -7,12 +7,10 @@ Use it to send yourself a message when tasks are done, including from coding age
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run build
|
|
12
|
-
npm link
|
|
10
|
+
npm install -g nonotify
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
After
|
|
13
|
+
After install, `nnt` is available globally.
|
|
16
14
|
|
|
17
15
|
## Config location
|
|
18
16
|
|
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Cli, z } from
|
|
3
|
-
import { askConfirm, askRequired, askRequiredWithInitial, askSelect } from
|
|
4
|
-
import { getConfigPath, loadConfig, saveConfig } from
|
|
5
|
-
import { printKeyValueTable, printProfilesTable } from
|
|
6
|
-
import { getLatestUpdateOffset, sendTelegramMessage, waitForChatId } from
|
|
7
|
-
const profileCli = Cli.create(
|
|
8
|
-
description:
|
|
2
|
+
import { Cli, z } from "incur";
|
|
3
|
+
import { askConfirm, askRequired, askRequiredWithInitial, askSelect, } from "./prompt.js";
|
|
4
|
+
import { getConfigPath, loadConfig, saveConfig } from "./config.js";
|
|
5
|
+
import { printKeyValueTable, printProfilesTable } from "./display.js";
|
|
6
|
+
import { getLatestUpdateOffset, sendTelegramMessage, waitForChatId, } from "./telegram.js";
|
|
7
|
+
const profileCli = Cli.create("profile", {
|
|
8
|
+
description: "Manage notification profiles",
|
|
9
9
|
});
|
|
10
|
-
profileCli.command(
|
|
11
|
-
description:
|
|
12
|
-
outputPolicy:
|
|
10
|
+
profileCli.command("add", {
|
|
11
|
+
description: "Add a notification profile",
|
|
12
|
+
outputPolicy: "agent-only",
|
|
13
13
|
args: z.object({
|
|
14
|
-
provider: z
|
|
14
|
+
provider: z
|
|
15
|
+
.enum(["telegram"])
|
|
16
|
+
.default("telegram")
|
|
17
|
+
.describe("Profile provider"),
|
|
15
18
|
}),
|
|
16
19
|
async run(c) {
|
|
17
|
-
if (c.args.provider !==
|
|
20
|
+
if (c.args.provider !== "telegram") {
|
|
18
21
|
throw new Error(`Unsupported provider: ${c.args.provider}`);
|
|
19
22
|
}
|
|
20
23
|
const config = await loadConfig();
|
|
21
|
-
const profileName = await askRequired(
|
|
24
|
+
const profileName = await askRequired("Profile name: ");
|
|
22
25
|
if (config.profiles[profileName]) {
|
|
23
26
|
throw new Error(`Profile "${profileName}" already exists.`);
|
|
24
27
|
}
|
|
25
|
-
const botToken = await askRequired(
|
|
28
|
+
const botToken = await askRequired("Telegram bot token: ");
|
|
26
29
|
if (shouldRenderPretty(c.agent)) {
|
|
27
|
-
process.stdout.write(
|
|
28
|
-
process.stdout.write(
|
|
30
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
31
|
+
process.stdout.write("Waiting for message to detect chat_id (up to 120s)...\n");
|
|
29
32
|
}
|
|
30
33
|
const offset = await getLatestUpdateOffset(botToken);
|
|
31
34
|
const connection = await waitForChatId(botToken, offset, 120);
|
|
@@ -35,11 +38,11 @@ profileCli.command('add', {
|
|
|
35
38
|
process.stdout.write(`Connected Telegram username: @${connection.username}\n`);
|
|
36
39
|
}
|
|
37
40
|
else {
|
|
38
|
-
process.stdout.write(
|
|
41
|
+
process.stdout.write("Connected Telegram user has no username set.\n");
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
config.profiles[profileName] = {
|
|
42
|
-
type:
|
|
45
|
+
type: "telegram",
|
|
43
46
|
name: profileName,
|
|
44
47
|
botToken,
|
|
45
48
|
chatId,
|
|
@@ -56,21 +59,27 @@ profileCli.command('add', {
|
|
|
56
59
|
confirmationSent = true;
|
|
57
60
|
}
|
|
58
61
|
catch (error) {
|
|
59
|
-
confirmationWarning =
|
|
62
|
+
confirmationWarning =
|
|
63
|
+
error instanceof Error
|
|
64
|
+
? error.message
|
|
65
|
+
: "Unknown error while sending confirmation";
|
|
60
66
|
}
|
|
61
67
|
if (shouldRenderPretty(c.agent)) {
|
|
62
|
-
printKeyValueTable(
|
|
63
|
-
{ key:
|
|
64
|
-
{ key:
|
|
65
|
-
{ key:
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
+
printKeyValueTable("Profile added", [
|
|
69
|
+
{ key: "profile", value: profileName },
|
|
70
|
+
{ key: "provider", value: "telegram" },
|
|
71
|
+
{ key: "chat_id", value: chatId },
|
|
72
|
+
{
|
|
73
|
+
key: "username",
|
|
74
|
+
value: connection.username ? `@${connection.username}` : "(none)",
|
|
75
|
+
},
|
|
76
|
+
{ key: "default", value: config.defaultProfile ?? "(none)" },
|
|
68
77
|
]);
|
|
69
78
|
}
|
|
70
79
|
return {
|
|
71
80
|
added: true,
|
|
72
81
|
profile: profileName,
|
|
73
|
-
provider:
|
|
82
|
+
provider: "telegram",
|
|
74
83
|
chatId,
|
|
75
84
|
username: connection.username,
|
|
76
85
|
defaultProfile: config.defaultProfile,
|
|
@@ -80,13 +89,13 @@ profileCli.command('add', {
|
|
|
80
89
|
};
|
|
81
90
|
},
|
|
82
91
|
});
|
|
83
|
-
profileCli.command(
|
|
84
|
-
description:
|
|
85
|
-
outputPolicy:
|
|
92
|
+
profileCli.command("list", {
|
|
93
|
+
description: "List configured profiles",
|
|
94
|
+
outputPolicy: "agent-only",
|
|
86
95
|
async run(c) {
|
|
87
96
|
const config = await loadConfig();
|
|
88
97
|
const names = Object.keys(config.profiles).sort((a, b) => a.localeCompare(b));
|
|
89
|
-
const profiles = names.map(name => ({
|
|
98
|
+
const profiles = names.map((name) => ({
|
|
90
99
|
name,
|
|
91
100
|
provider: config.profiles[name].type,
|
|
92
101
|
isDefault: name === config.defaultProfile,
|
|
@@ -101,18 +110,18 @@ profileCli.command('list', {
|
|
|
101
110
|
};
|
|
102
111
|
},
|
|
103
112
|
});
|
|
104
|
-
profileCli.command(
|
|
105
|
-
description:
|
|
106
|
-
outputPolicy:
|
|
113
|
+
profileCli.command("default", {
|
|
114
|
+
description: "Get or set default profile",
|
|
115
|
+
outputPolicy: "agent-only",
|
|
107
116
|
args: z.object({
|
|
108
|
-
profile: z.string().optional().describe(
|
|
117
|
+
profile: z.string().optional().describe("Profile name to set as default"),
|
|
109
118
|
}),
|
|
110
119
|
async run(c) {
|
|
111
120
|
const config = await loadConfig();
|
|
112
121
|
if (!c.args.profile) {
|
|
113
122
|
if (shouldRenderPretty(c.agent)) {
|
|
114
|
-
printKeyValueTable(
|
|
115
|
-
{ key:
|
|
123
|
+
printKeyValueTable("Default profile", [
|
|
124
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
116
125
|
]);
|
|
117
126
|
}
|
|
118
127
|
return {
|
|
@@ -125,8 +134,8 @@ profileCli.command('default', {
|
|
|
125
134
|
config.defaultProfile = c.args.profile;
|
|
126
135
|
await saveConfig(config);
|
|
127
136
|
if (shouldRenderPretty(c.agent)) {
|
|
128
|
-
printKeyValueTable(
|
|
129
|
-
{ key:
|
|
137
|
+
printKeyValueTable("Default profile updated", [
|
|
138
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
130
139
|
]);
|
|
131
140
|
}
|
|
132
141
|
return {
|
|
@@ -135,11 +144,11 @@ profileCli.command('default', {
|
|
|
135
144
|
};
|
|
136
145
|
},
|
|
137
146
|
});
|
|
138
|
-
profileCli.command(
|
|
139
|
-
description:
|
|
140
|
-
outputPolicy:
|
|
147
|
+
profileCli.command("delete", {
|
|
148
|
+
description: "Delete a profile",
|
|
149
|
+
outputPolicy: "agent-only",
|
|
141
150
|
args: z.object({
|
|
142
|
-
profile: z.string().describe(
|
|
151
|
+
profile: z.string().describe("Profile name to delete"),
|
|
143
152
|
}),
|
|
144
153
|
async run(c) {
|
|
145
154
|
const config = await loadConfig();
|
|
@@ -155,10 +164,10 @@ profileCli.command('delete', {
|
|
|
155
164
|
}
|
|
156
165
|
await saveConfig(config);
|
|
157
166
|
if (shouldRenderPretty(c.agent)) {
|
|
158
|
-
printKeyValueTable(
|
|
159
|
-
{ key:
|
|
160
|
-
{ key:
|
|
161
|
-
{ key:
|
|
167
|
+
printKeyValueTable("Profile deleted", [
|
|
168
|
+
{ key: "profile", value: targetName },
|
|
169
|
+
{ key: "provider", value: profile.type },
|
|
170
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
162
171
|
]);
|
|
163
172
|
}
|
|
164
173
|
return {
|
|
@@ -169,31 +178,37 @@ profileCli.command('delete', {
|
|
|
169
178
|
};
|
|
170
179
|
},
|
|
171
180
|
});
|
|
172
|
-
profileCli.command(
|
|
173
|
-
description:
|
|
174
|
-
outputPolicy:
|
|
181
|
+
profileCli.command("edit", {
|
|
182
|
+
description: "Edit profile data",
|
|
183
|
+
outputPolicy: "agent-only",
|
|
175
184
|
args: z.object({
|
|
176
|
-
profile: z.string().optional().describe(
|
|
185
|
+
profile: z.string().optional().describe("Existing profile name"),
|
|
177
186
|
}),
|
|
178
187
|
options: z.object({
|
|
179
|
-
newName: z.string().optional().describe(
|
|
180
|
-
botToken: z.string().optional().describe(
|
|
181
|
-
chatId: z.string().optional().describe(
|
|
182
|
-
reconnect: z
|
|
188
|
+
newName: z.string().optional().describe("Rename profile to a new name"),
|
|
189
|
+
botToken: z.string().optional().describe("Replace Telegram bot token"),
|
|
190
|
+
chatId: z.string().optional().describe("Replace Telegram chat id"),
|
|
191
|
+
reconnect: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.optional()
|
|
194
|
+
.describe("Re-detect chat id from next Telegram message"),
|
|
183
195
|
}),
|
|
184
196
|
alias: {
|
|
185
|
-
newName:
|
|
186
|
-
botToken:
|
|
187
|
-
chatId:
|
|
188
|
-
reconnect:
|
|
197
|
+
newName: "n",
|
|
198
|
+
botToken: "t",
|
|
199
|
+
chatId: "c",
|
|
200
|
+
reconnect: "r",
|
|
189
201
|
},
|
|
190
202
|
async run(c) {
|
|
191
203
|
const config = await loadConfig();
|
|
192
204
|
const profileNames = Object.keys(config.profiles).sort((a, b) => a.localeCompare(b));
|
|
193
205
|
if (profileNames.length === 0) {
|
|
194
|
-
throw new Error(
|
|
206
|
+
throw new Error("No profiles found. Run `nnt profile add` first.");
|
|
195
207
|
}
|
|
196
|
-
const hasDirectEditOptions = Boolean(c.options.newName ||
|
|
208
|
+
const hasDirectEditOptions = Boolean(c.options.newName ||
|
|
209
|
+
c.options.botToken ||
|
|
210
|
+
c.options.chatId ||
|
|
211
|
+
c.options.reconnect);
|
|
197
212
|
const sourceName = await resolveProfileForEdit(c.args.profile, profileNames, hasDirectEditOptions);
|
|
198
213
|
const sourceProfile = config.profiles[sourceName];
|
|
199
214
|
if (!sourceProfile) {
|
|
@@ -209,16 +224,16 @@ profileCli.command('edit', {
|
|
|
209
224
|
let chatId = c.options.chatId ?? sourceProfile.chatId;
|
|
210
225
|
let connectedUsername = null;
|
|
211
226
|
if (!hasDirectEditOptions && canPromptInteractively()) {
|
|
212
|
-
nextName = await askRequiredWithInitial(
|
|
227
|
+
nextName = await askRequiredWithInitial("Profile name", sourceName);
|
|
213
228
|
if (nextName !== sourceName && config.profiles[nextName]) {
|
|
214
229
|
throw new Error(`Profile "${nextName}" already exists.`);
|
|
215
230
|
}
|
|
216
|
-
nextBotToken = await askRequiredWithInitial(
|
|
217
|
-
const shouldReconnect = await askConfirm(
|
|
231
|
+
nextBotToken = await askRequiredWithInitial("Telegram bot token", sourceProfile.botToken);
|
|
232
|
+
const shouldReconnect = await askConfirm("Reconnect and detect chat_id from a new message?", false);
|
|
218
233
|
if (shouldReconnect) {
|
|
219
234
|
if (shouldRenderPretty(c.agent)) {
|
|
220
|
-
process.stdout.write(
|
|
221
|
-
process.stdout.write(
|
|
235
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
236
|
+
process.stdout.write("Waiting for message to detect chat_id (up to 120s)...\n");
|
|
222
237
|
}
|
|
223
238
|
const offset = await getLatestUpdateOffset(nextBotToken);
|
|
224
239
|
const connection = await waitForChatId(nextBotToken, offset, 120);
|
|
@@ -229,18 +244,18 @@ profileCli.command('edit', {
|
|
|
229
244
|
process.stdout.write(`Connected Telegram username: @${connection.username}\n`);
|
|
230
245
|
}
|
|
231
246
|
else {
|
|
232
|
-
process.stdout.write(
|
|
247
|
+
process.stdout.write("Connected Telegram user has no username set.\n");
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
}
|
|
236
251
|
else {
|
|
237
|
-
chatId = await askRequiredWithInitial(
|
|
252
|
+
chatId = await askRequiredWithInitial("Telegram chat_id", sourceProfile.chatId);
|
|
238
253
|
}
|
|
239
254
|
}
|
|
240
255
|
if (c.options.reconnect) {
|
|
241
256
|
if (shouldRenderPretty(c.agent)) {
|
|
242
|
-
process.stdout.write(
|
|
243
|
-
process.stdout.write(
|
|
257
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
258
|
+
process.stdout.write("Waiting for message to detect chat_id (up to 120s)...\n");
|
|
244
259
|
}
|
|
245
260
|
const offset = await getLatestUpdateOffset(nextBotToken);
|
|
246
261
|
const connection = await waitForChatId(nextBotToken, offset, 120);
|
|
@@ -251,7 +266,7 @@ profileCli.command('edit', {
|
|
|
251
266
|
process.stdout.write(`Connected Telegram username: @${connection.username}\n`);
|
|
252
267
|
}
|
|
253
268
|
else {
|
|
254
|
-
process.stdout.write(
|
|
269
|
+
process.stdout.write("Connected Telegram user has no username set.\n");
|
|
255
270
|
}
|
|
256
271
|
}
|
|
257
272
|
}
|
|
@@ -268,9 +283,9 @@ profileCli.command('edit', {
|
|
|
268
283
|
if (config.defaultProfile === sourceName) {
|
|
269
284
|
config.defaultProfile = nextName;
|
|
270
285
|
}
|
|
271
|
-
const hasChanges = nextName !== sourceName
|
|
272
|
-
|
|
273
|
-
|
|
286
|
+
const hasChanges = nextName !== sourceName ||
|
|
287
|
+
nextBotToken !== sourceProfile.botToken ||
|
|
288
|
+
chatId !== sourceProfile.chatId;
|
|
274
289
|
if (!hasChanges) {
|
|
275
290
|
return {
|
|
276
291
|
updated: false,
|
|
@@ -282,12 +297,17 @@ profileCli.command('edit', {
|
|
|
282
297
|
}
|
|
283
298
|
await saveConfig(config);
|
|
284
299
|
if (shouldRenderPretty(c.agent)) {
|
|
285
|
-
printKeyValueTable(
|
|
286
|
-
{ key:
|
|
287
|
-
{ key:
|
|
288
|
-
{ key:
|
|
289
|
-
{
|
|
290
|
-
|
|
300
|
+
printKeyValueTable("Profile updated", [
|
|
301
|
+
{ key: "profile", value: nextName },
|
|
302
|
+
{ key: "provider", value: updatedProfile.type },
|
|
303
|
+
{ key: "chat_id", value: chatId },
|
|
304
|
+
{
|
|
305
|
+
key: "username",
|
|
306
|
+
value: connectedUsername
|
|
307
|
+
? `@${connectedUsername}`
|
|
308
|
+
: "(unchanged/none)",
|
|
309
|
+
},
|
|
310
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
291
311
|
]);
|
|
292
312
|
}
|
|
293
313
|
return {
|
|
@@ -300,25 +320,25 @@ profileCli.command('edit', {
|
|
|
300
320
|
};
|
|
301
321
|
},
|
|
302
322
|
});
|
|
303
|
-
const cli = Cli.create(
|
|
304
|
-
description:
|
|
323
|
+
const cli = Cli.create("nnt", {
|
|
324
|
+
description: "Send Telegram notifications from terminal and agents",
|
|
305
325
|
})
|
|
306
|
-
.command(
|
|
307
|
-
description:
|
|
326
|
+
.command("send", {
|
|
327
|
+
description: "Send a message via a saved profile",
|
|
308
328
|
args: z.object({
|
|
309
|
-
message: z.string().describe(
|
|
329
|
+
message: z.string().describe("Message text to send"),
|
|
310
330
|
}),
|
|
311
331
|
options: z.object({
|
|
312
|
-
profile: z.string().optional().describe(
|
|
332
|
+
profile: z.string().optional().describe("Profile name from config"),
|
|
313
333
|
}),
|
|
314
334
|
alias: {
|
|
315
|
-
profile:
|
|
335
|
+
profile: "p",
|
|
316
336
|
},
|
|
317
337
|
async run(c) {
|
|
318
338
|
const config = await loadConfig();
|
|
319
339
|
const profileName = c.options.profile ?? config.defaultProfile;
|
|
320
340
|
if (!profileName) {
|
|
321
|
-
throw new Error(
|
|
341
|
+
throw new Error("No default profile found. Run `nnt profile add` first.");
|
|
322
342
|
}
|
|
323
343
|
const profile = config.profiles[profileName];
|
|
324
344
|
if (!profile) {
|
|
@@ -337,12 +357,20 @@ function routeDefaultCommand(argv) {
|
|
|
337
357
|
if (argv.length === 0) {
|
|
338
358
|
return argv;
|
|
339
359
|
}
|
|
340
|
-
const topLevelCommands = new Set([
|
|
341
|
-
const bareGlobalFlags = new Set([
|
|
360
|
+
const topLevelCommands = new Set(["profile", "send", "skills", "mcp"]);
|
|
361
|
+
const bareGlobalFlags = new Set([
|
|
362
|
+
"--help",
|
|
363
|
+
"-h",
|
|
364
|
+
"--version",
|
|
365
|
+
"--llms",
|
|
366
|
+
"--mcp",
|
|
367
|
+
"--json",
|
|
368
|
+
"--verbose",
|
|
369
|
+
]);
|
|
342
370
|
let index = 0;
|
|
343
371
|
while (index < argv.length) {
|
|
344
372
|
const token = argv[index];
|
|
345
|
-
if (token ===
|
|
373
|
+
if (token === "--format") {
|
|
346
374
|
index += 2;
|
|
347
375
|
continue;
|
|
348
376
|
}
|
|
@@ -358,13 +386,13 @@ function routeDefaultCommand(argv) {
|
|
|
358
386
|
if (topLevelCommands.has(argv[index])) {
|
|
359
387
|
return argv;
|
|
360
388
|
}
|
|
361
|
-
return [...argv.slice(0, index),
|
|
389
|
+
return [...argv.slice(0, index), "send", ...argv.slice(index)];
|
|
362
390
|
}
|
|
363
391
|
function normalizeFormatFlag(argv) {
|
|
364
392
|
const normalized = [];
|
|
365
393
|
for (const token of argv) {
|
|
366
|
-
if (token.startsWith(
|
|
367
|
-
normalized.push(
|
|
394
|
+
if (token.startsWith("--format=")) {
|
|
395
|
+
normalized.push("--format", token.slice("--format=".length));
|
|
368
396
|
continue;
|
|
369
397
|
}
|
|
370
398
|
normalized.push(token);
|
|
@@ -373,7 +401,7 @@ function normalizeFormatFlag(argv) {
|
|
|
373
401
|
}
|
|
374
402
|
function isStrictOutputRequested(argv) {
|
|
375
403
|
for (const token of argv) {
|
|
376
|
-
if (token ===
|
|
404
|
+
if (token === "--json" || token === "--verbose" || token === "--format") {
|
|
377
405
|
return true;
|
|
378
406
|
}
|
|
379
407
|
}
|
|
@@ -392,7 +420,7 @@ function withAgentDefaultFormat(argv, strictOutputRequested) {
|
|
|
392
420
|
if (strictOutputRequested || !isAgentEnvironment()) {
|
|
393
421
|
return argv;
|
|
394
422
|
}
|
|
395
|
-
return [
|
|
423
|
+
return ["--format", "toon", ...argv];
|
|
396
424
|
}
|
|
397
425
|
function canPromptInteractively() {
|
|
398
426
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -402,9 +430,9 @@ async function resolveProfileForEdit(profileFromArgs, profileNames, hasDirectEdi
|
|
|
402
430
|
return profileFromArgs;
|
|
403
431
|
}
|
|
404
432
|
if (hasDirectEditOptions || !canPromptInteractively()) {
|
|
405
|
-
throw new Error(
|
|
433
|
+
throw new Error("Profile name is required in non-interactive mode.");
|
|
406
434
|
}
|
|
407
|
-
return askSelect(
|
|
435
|
+
return askSelect("Select profile to edit", profileNames.map((name) => ({
|
|
408
436
|
value: name,
|
|
409
437
|
label: name,
|
|
410
438
|
})));
|
package/dist/config.js
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
import { homedir } from
|
|
2
|
-
import { join, resolve } from
|
|
3
|
-
import { mkdir, readFile, writeFile, chmod } from
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
4
4
|
const DEFAULT_CONFIG = {
|
|
5
5
|
defaultProfile: null,
|
|
6
6
|
profiles: {},
|
|
7
7
|
};
|
|
8
8
|
export function getConfigDir() {
|
|
9
9
|
const dir = process.env.NNT_CONFIG_DIR;
|
|
10
|
-
if (dir && dir.trim() !==
|
|
10
|
+
if (dir && dir.trim() !== "") {
|
|
11
11
|
return resolve(dir);
|
|
12
12
|
}
|
|
13
|
-
return join(homedir(),
|
|
13
|
+
return join(homedir(), ".nnt");
|
|
14
14
|
}
|
|
15
15
|
export function getConfigPath() {
|
|
16
|
-
return join(getConfigDir(),
|
|
16
|
+
return join(getConfigDir(), "config");
|
|
17
17
|
}
|
|
18
18
|
export async function loadConfig() {
|
|
19
19
|
const path = getConfigPath();
|
|
20
20
|
try {
|
|
21
|
-
const raw = await readFile(path,
|
|
21
|
+
const raw = await readFile(path, "utf8");
|
|
22
22
|
const parsed = JSON.parse(raw);
|
|
23
23
|
return {
|
|
24
|
-
defaultProfile: typeof parsed.defaultProfile ===
|
|
24
|
+
defaultProfile: typeof parsed.defaultProfile === "string"
|
|
25
|
+
? parsed.defaultProfile
|
|
26
|
+
: null,
|
|
25
27
|
profiles: parsed.profiles ?? {},
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
catch (error) {
|
|
29
|
-
if (isNodeError(error) && error.code ===
|
|
31
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
30
32
|
return { ...DEFAULT_CONFIG };
|
|
31
33
|
}
|
|
32
34
|
throw error;
|
|
@@ -36,7 +38,9 @@ export async function saveConfig(config) {
|
|
|
36
38
|
const dir = getConfigDir();
|
|
37
39
|
const path = getConfigPath();
|
|
38
40
|
await mkdir(dir, { recursive: true });
|
|
39
|
-
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, {
|
|
41
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, {
|
|
42
|
+
mode: 0o600,
|
|
43
|
+
});
|
|
40
44
|
try {
|
|
41
45
|
await chmod(path, 0o600);
|
|
42
46
|
}
|
|
@@ -45,5 +49,5 @@ export async function saveConfig(config) {
|
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
function isNodeError(error) {
|
|
48
|
-
return typeof error ===
|
|
52
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
49
53
|
}
|
package/dist/display.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import Table from
|
|
1
|
+
import Table from "cli-table3";
|
|
2
2
|
export function printProfilesTable(rows) {
|
|
3
3
|
if (rows.length === 0) {
|
|
4
|
-
process.stdout.write(
|
|
4
|
+
process.stdout.write("No profiles configured. Run `nnt profile add`.\n");
|
|
5
5
|
return;
|
|
6
6
|
}
|
|
7
7
|
const table = new Table({
|
|
8
|
-
head: [
|
|
8
|
+
head: ["Profile", "Provider", "Default"],
|
|
9
9
|
});
|
|
10
10
|
for (const row of rows) {
|
|
11
|
-
table.push([row.name, row.provider, row.isDefault ?
|
|
11
|
+
table.push([row.name, row.provider, row.isDefault ? "yes" : ""]);
|
|
12
12
|
}
|
|
13
13
|
process.stdout.write(`${table.toString()}\n`);
|
|
14
14
|
}
|
|
15
15
|
export function printKeyValueTable(title, rows) {
|
|
16
16
|
process.stdout.write(`${title}\n`);
|
|
17
17
|
const table = new Table({
|
|
18
|
-
head: [
|
|
18
|
+
head: ["Field", "Value"],
|
|
19
19
|
});
|
|
20
20
|
for (const row of rows) {
|
|
21
21
|
table.push([row.key, row.value]);
|