nonotify 0.1.2 → 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 +3 -3
- package/dist/cli.js +448 -0
- package/dist/config.js +53 -0
- package/dist/display.js +24 -0
- package/dist/prompt.js +47 -0
- package/dist/telegram.js +59 -0
- 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
package/src/cli.ts
CHANGED
|
@@ -1,200 +1,225 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
-
|
|
8
|
-
const profileCli = Cli.create('profile', {
|
|
9
|
-
description: 'Manage notification profiles',
|
|
10
|
-
})
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { Cli, z } from "incur";
|
|
4
|
+
import {
|
|
5
|
+
askConfirm,
|
|
6
|
+
askRequired,
|
|
7
|
+
askRequiredWithInitial,
|
|
8
|
+
askSelect,
|
|
9
|
+
} from "./prompt.js";
|
|
10
|
+
import { getConfigPath, loadConfig, saveConfig } from "./config.js";
|
|
11
|
+
import { printKeyValueTable, printProfilesTable } from "./display.js";
|
|
12
|
+
import {
|
|
13
|
+
getLatestUpdateOffset,
|
|
14
|
+
sendTelegramMessage,
|
|
15
|
+
waitForChatId,
|
|
16
|
+
} from "./telegram.js";
|
|
17
|
+
|
|
18
|
+
const profileCli = Cli.create("profile", {
|
|
19
|
+
description: "Manage notification profiles",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
profileCli.command("add", {
|
|
23
|
+
description: "Add a notification profile",
|
|
24
|
+
outputPolicy: "agent-only",
|
|
15
25
|
args: z.object({
|
|
16
|
-
provider: z
|
|
26
|
+
provider: z
|
|
27
|
+
.enum(["telegram"])
|
|
28
|
+
.default("telegram")
|
|
29
|
+
.describe("Profile provider"),
|
|
17
30
|
}),
|
|
18
31
|
async run(c) {
|
|
19
|
-
if (c.args.provider !==
|
|
20
|
-
throw new Error(`Unsupported provider: ${c.args.provider}`)
|
|
32
|
+
if (c.args.provider !== "telegram") {
|
|
33
|
+
throw new Error(`Unsupported provider: ${c.args.provider}`);
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
const config = await loadConfig()
|
|
24
|
-
const profileName = await askRequired(
|
|
36
|
+
const config = await loadConfig();
|
|
37
|
+
const profileName = await askRequired("Profile name: ");
|
|
25
38
|
|
|
26
39
|
if (config.profiles[profileName]) {
|
|
27
|
-
throw new Error(`Profile "${profileName}" already exists.`)
|
|
40
|
+
throw new Error(`Profile "${profileName}" already exists.`);
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
const botToken = await askRequired(
|
|
43
|
+
const botToken = await askRequired("Telegram bot token: ");
|
|
31
44
|
|
|
32
45
|
if (shouldRenderPretty(c.agent)) {
|
|
33
|
-
process.stdout.write(
|
|
34
|
-
process.stdout.write(
|
|
46
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
47
|
+
process.stdout.write(
|
|
48
|
+
"Waiting for message to detect chat_id (up to 120s)...\n",
|
|
49
|
+
);
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
const offset = await getLatestUpdateOffset(botToken)
|
|
38
|
-
const connection = await waitForChatId(botToken, offset, 120)
|
|
39
|
-
const chatId = connection.chatId
|
|
52
|
+
const offset = await getLatestUpdateOffset(botToken);
|
|
53
|
+
const connection = await waitForChatId(botToken, offset, 120);
|
|
54
|
+
const chatId = connection.chatId;
|
|
40
55
|
|
|
41
56
|
if (shouldRenderPretty(c.agent)) {
|
|
42
57
|
if (connection.username) {
|
|
43
|
-
process.stdout.write(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
`Connected Telegram username: @${connection.username}\n`,
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
process.stdout.write("Connected Telegram user has no username set.\n");
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
config.profiles[profileName] = {
|
|
51
|
-
type:
|
|
67
|
+
type: "telegram",
|
|
52
68
|
name: profileName,
|
|
53
69
|
botToken,
|
|
54
70
|
chatId,
|
|
55
71
|
createdAt: new Date().toISOString(),
|
|
56
|
-
}
|
|
72
|
+
};
|
|
57
73
|
|
|
58
74
|
if (!config.defaultProfile) {
|
|
59
|
-
config.defaultProfile = profileName
|
|
75
|
+
config.defaultProfile = profileName;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
await saveConfig(config)
|
|
78
|
+
await saveConfig(config);
|
|
63
79
|
|
|
64
|
-
let confirmationSent = false
|
|
65
|
-
let confirmationWarning: string | null = null
|
|
80
|
+
let confirmationSent = false;
|
|
81
|
+
let confirmationWarning: string | null = null;
|
|
66
82
|
|
|
67
83
|
try {
|
|
68
84
|
await sendTelegramMessage(
|
|
69
85
|
botToken,
|
|
70
86
|
chatId,
|
|
71
87
|
`nnt: profile "${profileName}" connected successfully. You can now send notifications from CLI.`,
|
|
72
|
-
)
|
|
73
|
-
confirmationSent = true
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
);
|
|
89
|
+
confirmationSent = true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
confirmationWarning =
|
|
92
|
+
error instanceof Error
|
|
93
|
+
? error.message
|
|
94
|
+
: "Unknown error while sending confirmation";
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
if (shouldRenderPretty(c.agent)) {
|
|
80
|
-
printKeyValueTable(
|
|
81
|
-
{ key:
|
|
82
|
-
{ key:
|
|
83
|
-
{ key:
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
printKeyValueTable("Profile added", [
|
|
99
|
+
{ key: "profile", value: profileName },
|
|
100
|
+
{ key: "provider", value: "telegram" },
|
|
101
|
+
{ key: "chat_id", value: chatId },
|
|
102
|
+
{
|
|
103
|
+
key: "username",
|
|
104
|
+
value: connection.username ? `@${connection.username}` : "(none)",
|
|
105
|
+
},
|
|
106
|
+
{ key: "default", value: config.defaultProfile ?? "(none)" },
|
|
107
|
+
]);
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
return {
|
|
90
111
|
added: true,
|
|
91
112
|
profile: profileName,
|
|
92
|
-
provider:
|
|
113
|
+
provider: "telegram",
|
|
93
114
|
chatId,
|
|
94
115
|
username: connection.username,
|
|
95
116
|
defaultProfile: config.defaultProfile,
|
|
96
117
|
configPath: getConfigPath(),
|
|
97
118
|
confirmationSent,
|
|
98
119
|
confirmationWarning,
|
|
99
|
-
}
|
|
120
|
+
};
|
|
100
121
|
},
|
|
101
|
-
})
|
|
122
|
+
});
|
|
102
123
|
|
|
103
|
-
profileCli.command(
|
|
104
|
-
description:
|
|
105
|
-
outputPolicy:
|
|
124
|
+
profileCli.command("list", {
|
|
125
|
+
description: "List configured profiles",
|
|
126
|
+
outputPolicy: "agent-only",
|
|
106
127
|
async run(c) {
|
|
107
|
-
const config = await loadConfig()
|
|
108
|
-
const names = Object.keys(config.profiles).sort((a, b) =>
|
|
109
|
-
|
|
128
|
+
const config = await loadConfig();
|
|
129
|
+
const names = Object.keys(config.profiles).sort((a, b) =>
|
|
130
|
+
a.localeCompare(b),
|
|
131
|
+
);
|
|
132
|
+
const profiles = names.map((name) => ({
|
|
110
133
|
name,
|
|
111
134
|
provider: config.profiles[name].type,
|
|
112
135
|
isDefault: name === config.defaultProfile,
|
|
113
|
-
}))
|
|
136
|
+
}));
|
|
114
137
|
|
|
115
138
|
if (shouldRenderPretty(c.agent)) {
|
|
116
|
-
printProfilesTable(profiles)
|
|
139
|
+
printProfilesTable(profiles);
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
return {
|
|
120
143
|
defaultProfile: config.defaultProfile,
|
|
121
144
|
totalProfiles: names.length,
|
|
122
145
|
profiles,
|
|
123
|
-
}
|
|
146
|
+
};
|
|
124
147
|
},
|
|
125
|
-
})
|
|
148
|
+
});
|
|
126
149
|
|
|
127
|
-
profileCli.command(
|
|
128
|
-
description:
|
|
129
|
-
outputPolicy:
|
|
150
|
+
profileCli.command("default", {
|
|
151
|
+
description: "Get or set default profile",
|
|
152
|
+
outputPolicy: "agent-only",
|
|
130
153
|
args: z.object({
|
|
131
|
-
profile: z.string().optional().describe(
|
|
154
|
+
profile: z.string().optional().describe("Profile name to set as default"),
|
|
132
155
|
}),
|
|
133
156
|
async run(c) {
|
|
134
|
-
const config = await loadConfig()
|
|
157
|
+
const config = await loadConfig();
|
|
135
158
|
|
|
136
159
|
if (!c.args.profile) {
|
|
137
160
|
if (shouldRenderPretty(c.agent)) {
|
|
138
|
-
printKeyValueTable(
|
|
139
|
-
{ key:
|
|
140
|
-
])
|
|
161
|
+
printKeyValueTable("Default profile", [
|
|
162
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
163
|
+
]);
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
return {
|
|
144
167
|
defaultProfile: config.defaultProfile,
|
|
145
|
-
}
|
|
168
|
+
};
|
|
146
169
|
}
|
|
147
170
|
|
|
148
171
|
if (!config.profiles[c.args.profile]) {
|
|
149
|
-
throw new Error(`Profile "${c.args.profile}" not found.`)
|
|
172
|
+
throw new Error(`Profile "${c.args.profile}" not found.`);
|
|
150
173
|
}
|
|
151
174
|
|
|
152
|
-
config.defaultProfile = c.args.profile
|
|
153
|
-
await saveConfig(config)
|
|
175
|
+
config.defaultProfile = c.args.profile;
|
|
176
|
+
await saveConfig(config);
|
|
154
177
|
|
|
155
178
|
if (shouldRenderPretty(c.agent)) {
|
|
156
|
-
printKeyValueTable(
|
|
157
|
-
{ key:
|
|
158
|
-
])
|
|
179
|
+
printKeyValueTable("Default profile updated", [
|
|
180
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
181
|
+
]);
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
return {
|
|
162
185
|
updated: true,
|
|
163
186
|
defaultProfile: config.defaultProfile,
|
|
164
|
-
}
|
|
187
|
+
};
|
|
165
188
|
},
|
|
166
|
-
})
|
|
189
|
+
});
|
|
167
190
|
|
|
168
|
-
profileCli.command(
|
|
169
|
-
description:
|
|
170
|
-
outputPolicy:
|
|
191
|
+
profileCli.command("delete", {
|
|
192
|
+
description: "Delete a profile",
|
|
193
|
+
outputPolicy: "agent-only",
|
|
171
194
|
args: z.object({
|
|
172
|
-
profile: z.string().describe(
|
|
195
|
+
profile: z.string().describe("Profile name to delete"),
|
|
173
196
|
}),
|
|
174
197
|
async run(c) {
|
|
175
|
-
const config = await loadConfig()
|
|
176
|
-
const targetName = c.args.profile
|
|
177
|
-
const profile = config.profiles[targetName]
|
|
198
|
+
const config = await loadConfig();
|
|
199
|
+
const targetName = c.args.profile;
|
|
200
|
+
const profile = config.profiles[targetName];
|
|
178
201
|
|
|
179
202
|
if (!profile) {
|
|
180
|
-
throw new Error(`Profile "${targetName}" not found.`)
|
|
203
|
+
throw new Error(`Profile "${targetName}" not found.`);
|
|
181
204
|
}
|
|
182
205
|
|
|
183
|
-
delete config.profiles[targetName]
|
|
206
|
+
delete config.profiles[targetName];
|
|
184
207
|
|
|
185
208
|
if (config.defaultProfile === targetName) {
|
|
186
|
-
const remaining = Object.keys(config.profiles).sort((a, b) =>
|
|
187
|
-
|
|
209
|
+
const remaining = Object.keys(config.profiles).sort((a, b) =>
|
|
210
|
+
a.localeCompare(b),
|
|
211
|
+
);
|
|
212
|
+
config.defaultProfile = remaining[0] ?? null;
|
|
188
213
|
}
|
|
189
214
|
|
|
190
|
-
await saveConfig(config)
|
|
215
|
+
await saveConfig(config);
|
|
191
216
|
|
|
192
217
|
if (shouldRenderPretty(c.agent)) {
|
|
193
|
-
printKeyValueTable(
|
|
194
|
-
{ key:
|
|
195
|
-
{ key:
|
|
196
|
-
{ key:
|
|
197
|
-
])
|
|
218
|
+
printKeyValueTable("Profile deleted", [
|
|
219
|
+
{ key: "profile", value: targetName },
|
|
220
|
+
{ key: "provider", value: profile.type },
|
|
221
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
222
|
+
]);
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
return {
|
|
@@ -202,110 +227,142 @@ profileCli.command('delete', {
|
|
|
202
227
|
profile: targetName,
|
|
203
228
|
provider: profile.type,
|
|
204
229
|
defaultProfile: config.defaultProfile,
|
|
205
|
-
}
|
|
230
|
+
};
|
|
206
231
|
},
|
|
207
|
-
})
|
|
232
|
+
});
|
|
208
233
|
|
|
209
|
-
profileCli.command(
|
|
210
|
-
description:
|
|
211
|
-
outputPolicy:
|
|
234
|
+
profileCli.command("edit", {
|
|
235
|
+
description: "Edit profile data",
|
|
236
|
+
outputPolicy: "agent-only",
|
|
212
237
|
args: z.object({
|
|
213
|
-
profile: z.string().optional().describe(
|
|
238
|
+
profile: z.string().optional().describe("Existing profile name"),
|
|
214
239
|
}),
|
|
215
240
|
options: z.object({
|
|
216
|
-
newName: z.string().optional().describe(
|
|
217
|
-
botToken: z.string().optional().describe(
|
|
218
|
-
chatId: z.string().optional().describe(
|
|
219
|
-
reconnect: z
|
|
241
|
+
newName: z.string().optional().describe("Rename profile to a new name"),
|
|
242
|
+
botToken: z.string().optional().describe("Replace Telegram bot token"),
|
|
243
|
+
chatId: z.string().optional().describe("Replace Telegram chat id"),
|
|
244
|
+
reconnect: z
|
|
245
|
+
.boolean()
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Re-detect chat id from next Telegram message"),
|
|
220
248
|
}),
|
|
221
249
|
alias: {
|
|
222
|
-
newName:
|
|
223
|
-
botToken:
|
|
224
|
-
chatId:
|
|
225
|
-
reconnect:
|
|
250
|
+
newName: "n",
|
|
251
|
+
botToken: "t",
|
|
252
|
+
chatId: "c",
|
|
253
|
+
reconnect: "r",
|
|
226
254
|
},
|
|
227
255
|
async run(c) {
|
|
228
|
-
const config = await loadConfig()
|
|
229
|
-
const profileNames = Object.keys(config.profiles).sort((a, b) =>
|
|
256
|
+
const config = await loadConfig();
|
|
257
|
+
const profileNames = Object.keys(config.profiles).sort((a, b) =>
|
|
258
|
+
a.localeCompare(b),
|
|
259
|
+
);
|
|
230
260
|
|
|
231
261
|
if (profileNames.length === 0) {
|
|
232
|
-
throw new Error(
|
|
262
|
+
throw new Error("No profiles found. Run `nnt profile add` first.");
|
|
233
263
|
}
|
|
234
264
|
|
|
235
|
-
const hasDirectEditOptions = Boolean(
|
|
265
|
+
const hasDirectEditOptions = Boolean(
|
|
266
|
+
c.options.newName ||
|
|
267
|
+
c.options.botToken ||
|
|
268
|
+
c.options.chatId ||
|
|
269
|
+
c.options.reconnect,
|
|
270
|
+
);
|
|
236
271
|
|
|
237
|
-
const sourceName = await resolveProfileForEdit(
|
|
238
|
-
|
|
272
|
+
const sourceName = await resolveProfileForEdit(
|
|
273
|
+
c.args.profile,
|
|
274
|
+
profileNames,
|
|
275
|
+
hasDirectEditOptions,
|
|
276
|
+
);
|
|
277
|
+
const sourceProfile = config.profiles[sourceName];
|
|
239
278
|
|
|
240
279
|
if (!sourceProfile) {
|
|
241
|
-
throw new Error(`Profile "${sourceName}" not found.`)
|
|
280
|
+
throw new Error(`Profile "${sourceName}" not found.`);
|
|
242
281
|
}
|
|
243
282
|
|
|
244
|
-
const targetName = c.options.newName ?? sourceName
|
|
283
|
+
const targetName = c.options.newName ?? sourceName;
|
|
245
284
|
|
|
246
285
|
if (targetName !== sourceName && config.profiles[targetName]) {
|
|
247
|
-
throw new Error(`Profile "${targetName}" already exists.`)
|
|
286
|
+
throw new Error(`Profile "${targetName}" already exists.`);
|
|
248
287
|
}
|
|
249
288
|
|
|
250
|
-
const botToken = c.options.botToken ?? sourceProfile.botToken
|
|
251
|
-
let nextName = targetName
|
|
252
|
-
let nextBotToken = botToken
|
|
253
|
-
let chatId = c.options.chatId ?? sourceProfile.chatId
|
|
254
|
-
let connectedUsername: string | null = null
|
|
289
|
+
const botToken = c.options.botToken ?? sourceProfile.botToken;
|
|
290
|
+
let nextName = targetName;
|
|
291
|
+
let nextBotToken = botToken;
|
|
292
|
+
let chatId = c.options.chatId ?? sourceProfile.chatId;
|
|
293
|
+
let connectedUsername: string | null = null;
|
|
255
294
|
|
|
256
295
|
if (!hasDirectEditOptions && canPromptInteractively()) {
|
|
257
|
-
nextName = await askRequiredWithInitial(
|
|
296
|
+
nextName = await askRequiredWithInitial("Profile name", sourceName);
|
|
258
297
|
|
|
259
298
|
if (nextName !== sourceName && config.profiles[nextName]) {
|
|
260
|
-
throw new Error(`Profile "${nextName}" already exists.`)
|
|
299
|
+
throw new Error(`Profile "${nextName}" already exists.`);
|
|
261
300
|
}
|
|
262
301
|
|
|
263
|
-
nextBotToken = await askRequiredWithInitial(
|
|
302
|
+
nextBotToken = await askRequiredWithInitial(
|
|
303
|
+
"Telegram bot token",
|
|
304
|
+
sourceProfile.botToken,
|
|
305
|
+
);
|
|
264
306
|
|
|
265
|
-
const shouldReconnect = await askConfirm(
|
|
307
|
+
const shouldReconnect = await askConfirm(
|
|
308
|
+
"Reconnect and detect chat_id from a new message?",
|
|
309
|
+
false,
|
|
310
|
+
);
|
|
266
311
|
|
|
267
312
|
if (shouldReconnect) {
|
|
268
313
|
if (shouldRenderPretty(c.agent)) {
|
|
269
|
-
process.stdout.write(
|
|
270
|
-
process.stdout.write(
|
|
314
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
315
|
+
process.stdout.write(
|
|
316
|
+
"Waiting for message to detect chat_id (up to 120s)...\n",
|
|
317
|
+
);
|
|
271
318
|
}
|
|
272
319
|
|
|
273
|
-
const offset = await getLatestUpdateOffset(nextBotToken)
|
|
274
|
-
const connection = await waitForChatId(nextBotToken, offset, 120)
|
|
275
|
-
chatId = connection.chatId
|
|
276
|
-
connectedUsername = connection.username
|
|
320
|
+
const offset = await getLatestUpdateOffset(nextBotToken);
|
|
321
|
+
const connection = await waitForChatId(nextBotToken, offset, 120);
|
|
322
|
+
chatId = connection.chatId;
|
|
323
|
+
connectedUsername = connection.username;
|
|
277
324
|
|
|
278
325
|
if (shouldRenderPretty(c.agent)) {
|
|
279
326
|
if (connection.username) {
|
|
280
|
-
process.stdout.write(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
327
|
+
process.stdout.write(
|
|
328
|
+
`Connected Telegram username: @${connection.username}\n`,
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
process.stdout.write(
|
|
332
|
+
"Connected Telegram user has no username set.\n",
|
|
333
|
+
);
|
|
284
334
|
}
|
|
285
335
|
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
336
|
+
} else {
|
|
337
|
+
chatId = await askRequiredWithInitial(
|
|
338
|
+
"Telegram chat_id",
|
|
339
|
+
sourceProfile.chatId,
|
|
340
|
+
);
|
|
289
341
|
}
|
|
290
342
|
}
|
|
291
343
|
|
|
292
344
|
if (c.options.reconnect) {
|
|
293
345
|
if (shouldRenderPretty(c.agent)) {
|
|
294
|
-
process.stdout.write(
|
|
295
|
-
process.stdout.write(
|
|
346
|
+
process.stdout.write("\nSend any message to your bot in Telegram.\n");
|
|
347
|
+
process.stdout.write(
|
|
348
|
+
"Waiting for message to detect chat_id (up to 120s)...\n",
|
|
349
|
+
);
|
|
296
350
|
}
|
|
297
351
|
|
|
298
|
-
const offset = await getLatestUpdateOffset(nextBotToken)
|
|
299
|
-
const connection = await waitForChatId(nextBotToken, offset, 120)
|
|
300
|
-
chatId = connection.chatId
|
|
301
|
-
connectedUsername = connection.username
|
|
352
|
+
const offset = await getLatestUpdateOffset(nextBotToken);
|
|
353
|
+
const connection = await waitForChatId(nextBotToken, offset, 120);
|
|
354
|
+
chatId = connection.chatId;
|
|
355
|
+
connectedUsername = connection.username;
|
|
302
356
|
|
|
303
357
|
if (shouldRenderPretty(c.agent)) {
|
|
304
358
|
if (connection.username) {
|
|
305
|
-
process.stdout.write(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
359
|
+
process.stdout.write(
|
|
360
|
+
`Connected Telegram username: @${connection.username}\n`,
|
|
361
|
+
);
|
|
362
|
+
} else {
|
|
363
|
+
process.stdout.write(
|
|
364
|
+
"Connected Telegram user has no username set.\n",
|
|
365
|
+
);
|
|
309
366
|
}
|
|
310
367
|
}
|
|
311
368
|
}
|
|
@@ -315,22 +372,22 @@ profileCli.command('edit', {
|
|
|
315
372
|
name: nextName,
|
|
316
373
|
botToken: nextBotToken,
|
|
317
374
|
chatId,
|
|
318
|
-
}
|
|
375
|
+
};
|
|
319
376
|
|
|
320
377
|
if (nextName !== sourceName) {
|
|
321
|
-
delete config.profiles[sourceName]
|
|
378
|
+
delete config.profiles[sourceName];
|
|
322
379
|
}
|
|
323
380
|
|
|
324
|
-
config.profiles[nextName] = updatedProfile
|
|
381
|
+
config.profiles[nextName] = updatedProfile;
|
|
325
382
|
|
|
326
383
|
if (config.defaultProfile === sourceName) {
|
|
327
|
-
config.defaultProfile = nextName
|
|
384
|
+
config.defaultProfile = nextName;
|
|
328
385
|
}
|
|
329
386
|
|
|
330
387
|
const hasChanges =
|
|
331
|
-
nextName !== sourceName
|
|
332
|
-
|
|
333
|
-
|
|
388
|
+
nextName !== sourceName ||
|
|
389
|
+
nextBotToken !== sourceProfile.botToken ||
|
|
390
|
+
chatId !== sourceProfile.chatId;
|
|
334
391
|
|
|
335
392
|
if (!hasChanges) {
|
|
336
393
|
return {
|
|
@@ -339,19 +396,24 @@ profileCli.command('edit', {
|
|
|
339
396
|
provider: sourceProfile.type,
|
|
340
397
|
defaultProfile: config.defaultProfile,
|
|
341
398
|
connectedUsername,
|
|
342
|
-
}
|
|
399
|
+
};
|
|
343
400
|
}
|
|
344
401
|
|
|
345
|
-
await saveConfig(config)
|
|
402
|
+
await saveConfig(config);
|
|
346
403
|
|
|
347
404
|
if (shouldRenderPretty(c.agent)) {
|
|
348
|
-
printKeyValueTable(
|
|
349
|
-
{ key:
|
|
350
|
-
{ key:
|
|
351
|
-
{ key:
|
|
352
|
-
{
|
|
353
|
-
|
|
354
|
-
|
|
405
|
+
printKeyValueTable("Profile updated", [
|
|
406
|
+
{ key: "profile", value: nextName },
|
|
407
|
+
{ key: "provider", value: updatedProfile.type },
|
|
408
|
+
{ key: "chat_id", value: chatId },
|
|
409
|
+
{
|
|
410
|
+
key: "username",
|
|
411
|
+
value: connectedUsername
|
|
412
|
+
? `@${connectedUsername}`
|
|
413
|
+
: "(unchanged/none)",
|
|
414
|
+
},
|
|
415
|
+
{ key: "default", value: config.defaultProfile ?? "(not set)" },
|
|
416
|
+
]);
|
|
355
417
|
}
|
|
356
418
|
|
|
357
419
|
return {
|
|
@@ -361,108 +423,122 @@ profileCli.command('edit', {
|
|
|
361
423
|
provider: updatedProfile.type,
|
|
362
424
|
defaultProfile: config.defaultProfile,
|
|
363
425
|
connectedUsername,
|
|
364
|
-
}
|
|
426
|
+
};
|
|
365
427
|
},
|
|
366
|
-
})
|
|
428
|
+
});
|
|
367
429
|
|
|
368
|
-
const cli = Cli.create(
|
|
369
|
-
description:
|
|
430
|
+
const cli = Cli.create("nnt", {
|
|
431
|
+
description: "Send Telegram notifications from terminal and agents",
|
|
370
432
|
})
|
|
371
|
-
.command(
|
|
372
|
-
description:
|
|
433
|
+
.command("send", {
|
|
434
|
+
description: "Send a message via a saved profile",
|
|
373
435
|
args: z.object({
|
|
374
|
-
message: z.string().describe(
|
|
436
|
+
message: z.string().describe("Message text to send"),
|
|
375
437
|
}),
|
|
376
438
|
options: z.object({
|
|
377
|
-
profile: z.string().optional().describe(
|
|
439
|
+
profile: z.string().optional().describe("Profile name from config"),
|
|
378
440
|
}),
|
|
379
441
|
alias: {
|
|
380
|
-
profile:
|
|
442
|
+
profile: "p",
|
|
381
443
|
},
|
|
382
444
|
async run(c) {
|
|
383
|
-
const config = await loadConfig()
|
|
384
|
-
const profileName = c.options.profile ?? config.defaultProfile
|
|
445
|
+
const config = await loadConfig();
|
|
446
|
+
const profileName = c.options.profile ?? config.defaultProfile;
|
|
385
447
|
|
|
386
448
|
if (!profileName) {
|
|
387
|
-
throw new Error(
|
|
449
|
+
throw new Error(
|
|
450
|
+
"No default profile found. Run `nnt profile add` first.",
|
|
451
|
+
);
|
|
388
452
|
}
|
|
389
453
|
|
|
390
|
-
const profile = config.profiles[profileName]
|
|
454
|
+
const profile = config.profiles[profileName];
|
|
391
455
|
|
|
392
456
|
if (!profile) {
|
|
393
|
-
throw new Error(`Profile "${profileName}" not found.`)
|
|
457
|
+
throw new Error(`Profile "${profileName}" not found.`);
|
|
394
458
|
}
|
|
395
459
|
|
|
396
|
-
await sendTelegramMessage(
|
|
460
|
+
await sendTelegramMessage(
|
|
461
|
+
profile.botToken,
|
|
462
|
+
profile.chatId,
|
|
463
|
+
c.args.message,
|
|
464
|
+
);
|
|
397
465
|
|
|
398
466
|
return {
|
|
399
467
|
sent: true,
|
|
400
468
|
profile: profileName,
|
|
401
469
|
provider: profile.type,
|
|
402
|
-
}
|
|
470
|
+
};
|
|
403
471
|
},
|
|
404
472
|
})
|
|
405
|
-
.command(profileCli)
|
|
473
|
+
.command(profileCli);
|
|
406
474
|
|
|
407
475
|
function routeDefaultCommand(argv: string[]): string[] {
|
|
408
476
|
if (argv.length === 0) {
|
|
409
|
-
return argv
|
|
477
|
+
return argv;
|
|
410
478
|
}
|
|
411
479
|
|
|
412
|
-
const topLevelCommands = new Set([
|
|
413
|
-
const bareGlobalFlags = new Set([
|
|
414
|
-
|
|
415
|
-
|
|
480
|
+
const topLevelCommands = new Set(["profile", "send", "skills", "mcp"]);
|
|
481
|
+
const bareGlobalFlags = new Set([
|
|
482
|
+
"--help",
|
|
483
|
+
"-h",
|
|
484
|
+
"--version",
|
|
485
|
+
"--llms",
|
|
486
|
+
"--mcp",
|
|
487
|
+
"--json",
|
|
488
|
+
"--verbose",
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
let index = 0;
|
|
416
492
|
while (index < argv.length) {
|
|
417
|
-
const token = argv[index]
|
|
493
|
+
const token = argv[index];
|
|
418
494
|
|
|
419
|
-
if (token ===
|
|
420
|
-
index += 2
|
|
421
|
-
continue
|
|
495
|
+
if (token === "--format") {
|
|
496
|
+
index += 2;
|
|
497
|
+
continue;
|
|
422
498
|
}
|
|
423
499
|
|
|
424
500
|
if (bareGlobalFlags.has(token)) {
|
|
425
|
-
index += 1
|
|
426
|
-
continue
|
|
501
|
+
index += 1;
|
|
502
|
+
continue;
|
|
427
503
|
}
|
|
428
504
|
|
|
429
|
-
break
|
|
505
|
+
break;
|
|
430
506
|
}
|
|
431
507
|
|
|
432
508
|
if (index >= argv.length) {
|
|
433
|
-
return argv
|
|
509
|
+
return argv;
|
|
434
510
|
}
|
|
435
511
|
|
|
436
512
|
if (topLevelCommands.has(argv[index])) {
|
|
437
|
-
return argv
|
|
513
|
+
return argv;
|
|
438
514
|
}
|
|
439
515
|
|
|
440
|
-
return [...argv.slice(0, index),
|
|
516
|
+
return [...argv.slice(0, index), "send", ...argv.slice(index)];
|
|
441
517
|
}
|
|
442
518
|
|
|
443
519
|
function normalizeFormatFlag(argv: string[]): string[] {
|
|
444
|
-
const normalized: string[] = []
|
|
520
|
+
const normalized: string[] = [];
|
|
445
521
|
|
|
446
522
|
for (const token of argv) {
|
|
447
|
-
if (token.startsWith(
|
|
448
|
-
normalized.push(
|
|
449
|
-
continue
|
|
523
|
+
if (token.startsWith("--format=")) {
|
|
524
|
+
normalized.push("--format", token.slice("--format=".length));
|
|
525
|
+
continue;
|
|
450
526
|
}
|
|
451
527
|
|
|
452
|
-
normalized.push(token)
|
|
528
|
+
normalized.push(token);
|
|
453
529
|
}
|
|
454
530
|
|
|
455
|
-
return normalized
|
|
531
|
+
return normalized;
|
|
456
532
|
}
|
|
457
533
|
|
|
458
534
|
function isStrictOutputRequested(argv: string[]): boolean {
|
|
459
535
|
for (const token of argv) {
|
|
460
|
-
if (token ===
|
|
461
|
-
return true
|
|
536
|
+
if (token === "--json" || token === "--verbose" || token === "--format") {
|
|
537
|
+
return true;
|
|
462
538
|
}
|
|
463
539
|
}
|
|
464
540
|
|
|
465
|
-
return false
|
|
541
|
+
return false;
|
|
466
542
|
}
|
|
467
543
|
|
|
468
544
|
function isAgentEnvironment(): boolean {
|
|
@@ -472,19 +548,22 @@ function isAgentEnvironment(): boolean {
|
|
|
472
548
|
process.env.CURSOR_AGENT,
|
|
473
549
|
process.env.AIDER_SESSION,
|
|
474
550
|
process.env.NNT_AGENT_MODE,
|
|
475
|
-
].some(Boolean)
|
|
551
|
+
].some(Boolean);
|
|
476
552
|
}
|
|
477
553
|
|
|
478
|
-
function withAgentDefaultFormat(
|
|
554
|
+
function withAgentDefaultFormat(
|
|
555
|
+
argv: string[],
|
|
556
|
+
strictOutputRequested: boolean,
|
|
557
|
+
): string[] {
|
|
479
558
|
if (strictOutputRequested || !isAgentEnvironment()) {
|
|
480
|
-
return argv
|
|
559
|
+
return argv;
|
|
481
560
|
}
|
|
482
561
|
|
|
483
|
-
return [
|
|
562
|
+
return ["--format", "toon", ...argv];
|
|
484
563
|
}
|
|
485
564
|
|
|
486
565
|
function canPromptInteractively(): boolean {
|
|
487
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
566
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
488
567
|
}
|
|
489
568
|
|
|
490
569
|
async function resolveProfileForEdit(
|
|
@@ -493,28 +572,34 @@ async function resolveProfileForEdit(
|
|
|
493
572
|
hasDirectEditOptions: boolean,
|
|
494
573
|
): Promise<string> {
|
|
495
574
|
if (profileFromArgs) {
|
|
496
|
-
return profileFromArgs
|
|
575
|
+
return profileFromArgs;
|
|
497
576
|
}
|
|
498
577
|
|
|
499
578
|
if (hasDirectEditOptions || !canPromptInteractively()) {
|
|
500
|
-
throw new Error(
|
|
579
|
+
throw new Error("Profile name is required in non-interactive mode.");
|
|
501
580
|
}
|
|
502
581
|
|
|
503
|
-
return askSelect(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
582
|
+
return askSelect(
|
|
583
|
+
"Select profile to edit",
|
|
584
|
+
profileNames.map((name) => ({
|
|
585
|
+
value: name,
|
|
586
|
+
label: name,
|
|
587
|
+
})),
|
|
588
|
+
);
|
|
507
589
|
}
|
|
508
590
|
|
|
509
|
-
const normalizedArgv = normalizeFormatFlag(process.argv.slice(2))
|
|
510
|
-
const strictOutputRequested = isStrictOutputRequested(normalizedArgv)
|
|
511
|
-
const argvWithAgentDefaults = withAgentDefaultFormat(
|
|
591
|
+
const normalizedArgv = normalizeFormatFlag(process.argv.slice(2));
|
|
592
|
+
const strictOutputRequested = isStrictOutputRequested(normalizedArgv);
|
|
593
|
+
const argvWithAgentDefaults = withAgentDefaultFormat(
|
|
594
|
+
normalizedArgv,
|
|
595
|
+
strictOutputRequested,
|
|
596
|
+
);
|
|
512
597
|
|
|
513
598
|
function shouldRenderPretty(agent: boolean): boolean {
|
|
514
|
-
return !agent && !strictOutputRequested && !isAgentEnvironment()
|
|
599
|
+
return !agent && !strictOutputRequested && !isAgentEnvironment();
|
|
515
600
|
}
|
|
516
601
|
|
|
517
|
-
const routedArgv = routeDefaultCommand(argvWithAgentDefaults)
|
|
518
|
-
await cli.serve(routedArgv)
|
|
602
|
+
const routedArgv = routeDefaultCommand(argvWithAgentDefaults);
|
|
603
|
+
await cli.serve(routedArgv);
|
|
519
604
|
|
|
520
|
-
export default cli
|
|
605
|
+
export default cli;
|