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.
@@ -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 linking, `nnt` is available globally.
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 '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',
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('add', {
11
- description: 'Add a notification profile',
12
- outputPolicy: 'agent-only',
10
+ profileCli.command("add", {
11
+ description: "Add a notification profile",
12
+ outputPolicy: "agent-only",
13
13
  args: z.object({
14
- provider: z.enum(['telegram']).default('telegram').describe('Profile provider'),
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 !== 'telegram') {
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('Profile name: ');
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('Telegram bot token: ');
28
+ const botToken = await askRequired("Telegram bot token: ");
26
29
  if (shouldRenderPretty(c.agent)) {
27
- process.stdout.write('\nSend any message to your bot in Telegram.\n');
28
- process.stdout.write('Waiting for message to detect chat_id (up to 120s)...\n');
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('Connected Telegram user has no username set.\n');
41
+ process.stdout.write("Connected Telegram user has no username set.\n");
39
42
  }
40
43
  }
41
44
  config.profiles[profileName] = {
42
- type: 'telegram',
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 = error instanceof Error ? error.message : 'Unknown error while sending confirmation';
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('Profile added', [
63
- { key: 'profile', value: profileName },
64
- { key: 'provider', value: 'telegram' },
65
- { key: 'chat_id', value: chatId },
66
- { key: 'username', value: connection.username ? `@${connection.username}` : '(none)' },
67
- { key: 'default', value: config.defaultProfile ?? '(none)' },
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: 'telegram',
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('list', {
84
- description: 'List configured profiles',
85
- outputPolicy: 'agent-only',
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('default', {
105
- description: 'Get or set default profile',
106
- outputPolicy: 'agent-only',
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('Profile name to set as default'),
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('Default profile', [
115
- { key: 'default', value: config.defaultProfile ?? '(not set)' },
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('Default profile updated', [
129
- { key: 'default', value: config.defaultProfile ?? '(not set)' },
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('delete', {
139
- description: 'Delete a profile',
140
- outputPolicy: 'agent-only',
147
+ profileCli.command("delete", {
148
+ description: "Delete a profile",
149
+ outputPolicy: "agent-only",
141
150
  args: z.object({
142
- profile: z.string().describe('Profile name to delete'),
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('Profile deleted', [
159
- { key: 'profile', value: targetName },
160
- { key: 'provider', value: profile.type },
161
- { key: 'default', value: config.defaultProfile ?? '(not set)' },
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('edit', {
173
- description: 'Edit profile data',
174
- outputPolicy: 'agent-only',
181
+ profileCli.command("edit", {
182
+ description: "Edit profile data",
183
+ outputPolicy: "agent-only",
175
184
  args: z.object({
176
- profile: z.string().optional().describe('Existing profile name'),
185
+ profile: z.string().optional().describe("Existing profile name"),
177
186
  }),
178
187
  options: z.object({
179
- newName: z.string().optional().describe('Rename profile to a new name'),
180
- botToken: z.string().optional().describe('Replace Telegram bot token'),
181
- chatId: z.string().optional().describe('Replace Telegram chat id'),
182
- reconnect: z.boolean().optional().describe('Re-detect chat id from next Telegram message'),
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: 'n',
186
- botToken: 't',
187
- chatId: 'c',
188
- reconnect: 'r',
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('No profiles found. Run `nnt profile add` first.');
206
+ throw new Error("No profiles found. Run `nnt profile add` first.");
195
207
  }
196
- const hasDirectEditOptions = Boolean(c.options.newName || c.options.botToken || c.options.chatId || c.options.reconnect);
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('Profile name', sourceName);
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('Telegram bot token', sourceProfile.botToken);
217
- const shouldReconnect = await askConfirm('Reconnect and detect chat_id from a new message?', false);
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('\nSend any message to your bot in Telegram.\n');
221
- process.stdout.write('Waiting for message to detect chat_id (up to 120s)...\n');
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('Connected Telegram user has no username set.\n');
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('Telegram chat_id', sourceProfile.chatId);
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('\nSend any message to your bot in Telegram.\n');
243
- process.stdout.write('Waiting for message to detect chat_id (up to 120s)...\n');
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('Connected Telegram user has no username set.\n');
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
- || nextBotToken !== sourceProfile.botToken
273
- || chatId !== sourceProfile.chatId;
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('Profile updated', [
286
- { key: 'profile', value: nextName },
287
- { key: 'provider', value: updatedProfile.type },
288
- { key: 'chat_id', value: chatId },
289
- { key: 'username', value: connectedUsername ? `@${connectedUsername}` : '(unchanged/none)' },
290
- { key: 'default', value: config.defaultProfile ?? '(not set)' },
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('nnt', {
304
- description: 'Send Telegram notifications from terminal and agents',
323
+ const cli = Cli.create("nnt", {
324
+ description: "Send Telegram notifications from terminal and agents",
305
325
  })
306
- .command('send', {
307
- description: 'Send a message via a saved profile',
326
+ .command("send", {
327
+ description: "Send a message via a saved profile",
308
328
  args: z.object({
309
- message: z.string().describe('Message text to send'),
329
+ message: z.string().describe("Message text to send"),
310
330
  }),
311
331
  options: z.object({
312
- profile: z.string().optional().describe('Profile name from config'),
332
+ profile: z.string().optional().describe("Profile name from config"),
313
333
  }),
314
334
  alias: {
315
- profile: 'p',
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('No default profile found. Run `nnt profile add` first.');
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(['profile', 'send', 'skills', 'mcp']);
341
- const bareGlobalFlags = new Set(['--help', '-h', '--version', '--llms', '--mcp', '--json', '--verbose']);
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 === '--format') {
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), 'send', ...argv.slice(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('--format=')) {
367
- normalized.push('--format', token.slice('--format='.length));
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 === '--json' || token === '--verbose' || token === '--format') {
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 ['--format', 'toon', ...argv];
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('Profile name is required in non-interactive mode.');
433
+ throw new Error("Profile name is required in non-interactive mode.");
406
434
  }
407
- return askSelect('Select profile to edit', profileNames.map(name => ({
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 'node:os';
2
- import { join, resolve } from 'node:path';
3
- import { mkdir, readFile, writeFile, chmod } from 'node:fs/promises';
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(), '.nnt');
13
+ return join(homedir(), ".nnt");
14
14
  }
15
15
  export function getConfigPath() {
16
- return join(getConfigDir(), 'config');
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, 'utf8');
21
+ const raw = await readFile(path, "utf8");
22
22
  const parsed = JSON.parse(raw);
23
23
  return {
24
- defaultProfile: typeof parsed.defaultProfile === 'string' ? parsed.defaultProfile : null,
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 === 'ENOENT') {
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`, { mode: 0o600 });
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 === 'object' && error !== null && 'code' in 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 'cli-table3';
1
+ import Table from "cli-table3";
2
2
  export function printProfilesTable(rows) {
3
3
  if (rows.length === 0) {
4
- process.stdout.write('No profiles configured. Run `nnt profile add`.\n');
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: ['Profile', 'Provider', 'Default'],
8
+ head: ["Profile", "Provider", "Default"],
9
9
  });
10
10
  for (const row of rows) {
11
- table.push([row.name, row.provider, row.isDefault ? 'yes' : '']);
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: ['Field', 'Value'],
18
+ head: ["Field", "Value"],
19
19
  });
20
20
  for (const row of rows) {
21
21
  table.push([row.key, row.value]);