ispbills-icli 5.0.0 → 5.3.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/README.md CHANGED
File without changes
package/bin/icli.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createInterface, emitKeypressEvents } from 'readline';
3
- import { stdin as input, stdout as output, argv, exit } from 'process';
3
+ import { stdin as input, stdout as output, argv, exit, platform } from 'process';
4
+ import { spawnSync } from 'child_process';
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
4
8
  import chalk from 'chalk';
5
- import { loadConfig, configPath } from '../src/config.js';
9
+ import { loadConfig, configPath, clearConfig } from '../src/config.js';
6
10
  import { loginFlow } from '../src/auth.js';
7
11
  import { inner, onResize, boxTop, boxBot, boxMid, boxRow, bottomPalette } from '../src/layout.js';
8
12
  import {
@@ -10,6 +14,45 @@ import {
10
14
  SLASH_COMMANDS, slashCompleter,
11
15
  } from '../src/chat.js';
12
16
 
17
+ const PKG_NAME = 'ispbills-icli';
18
+
19
+ /** Read this CLI's installed version from its package.json. */
20
+ function pkgVersion() {
21
+ try {
22
+ const p = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
23
+ return JSON.parse(readFileSync(p, 'utf8')).version ?? 'unknown';
24
+ } catch {
25
+ return 'unknown';
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Self-update: install the latest published version globally via npm.
31
+ * Returns true on success. The running process keeps the old code until the
32
+ * next launch, so callers should tell the user to restart.
33
+ */
34
+ function selfUpdate(P) {
35
+ const cur = pkgVersion();
36
+ process.stdout.write('\n ' + P.dim('Current version: ') + chalk.white(cur) + '\n');
37
+ process.stdout.write(' ' + P.muted('Updating ' + PKG_NAME + ' to the latest version…') + '\n\n');
38
+
39
+ const npm = platform === 'win32' ? 'npm.cmd' : 'npm';
40
+ const res = spawnSync(npm, ['install', '-g', `${PKG_NAME}@latest`], {
41
+ stdio: 'inherit',
42
+ shell: platform === 'win32',
43
+ });
44
+
45
+ if (res.status === 0) {
46
+ process.stdout.write('\n ' + P.ok('✓') + ' iCli updated. ' +
47
+ P.dim('Restart iCli to use the new version.') + '\n\n');
48
+ return true;
49
+ }
50
+ process.stdout.write('\n ' + P.err('✖') + ' Update failed. Try manually:\n');
51
+ process.stdout.write(' ' + P.accent(`npm install -g ${PKG_NAME}@latest`) + '\n');
52
+ process.stdout.write(' ' + P.dim('On some systems this needs elevated privileges (sudo / Administrator).') + '\n\n');
53
+ return false;
54
+ }
55
+
13
56
  const P = {
14
57
  border: chalk.hex('#303134'),
15
58
  dim: chalk.hex('#9AA0A6'),
@@ -44,6 +87,8 @@ function printHelp() {
44
87
  ['icli', 'Interactive REPL with slash commands'],
45
88
  ['icli "question"', 'Single-shot query then exit'],
46
89
  ['icli login', 'Authenticate to your IspBills instance'],
90
+ ['icli logout', 'Log out and clear saved credentials'],
91
+ ['icli update', 'Update iCli to the latest version'],
47
92
  ['icli whoami', 'Show current session info'],
48
93
  ['icli --help', 'Show this help'],
49
94
  ].forEach(([c, d]) =>
@@ -163,7 +208,7 @@ async function interactiveRepl(cfg) {
163
208
  turns.forEach((m, i) =>
164
209
  process.stdout.write(
165
210
  boxRow(P.dim(String(i + 1).padStart(2) + '.') + ' ' +
166
- chalk.white(m.content.replace(/\s+/g, ' ').slice(0, inner() - 5)), P.border) + '\n',
211
+ chalk.white(m.content.replace(/\s+/g, ' ').slice(0, inner() - 5)), P.border) + '\n')
167
212
  );
168
213
  process.stdout.write(boxBot(P.border) + '\n\n');
169
214
  }
@@ -221,6 +266,21 @@ async function interactiveRepl(cfg) {
221
266
  rl.prompt();
222
267
  return;
223
268
  }
269
+ if (q === '/update' || q === 'update') {
270
+ rl.pause();
271
+ selfUpdate(P);
272
+ rl.resume();
273
+ rl.prompt();
274
+ return;
275
+ }
276
+ if (q === '/logout' || q === 'logout') {
277
+ const cleared = clearConfig();
278
+ process.stdout.write('\n ' + (cleared ? P.ok('✓') + ' Logged out — credentials cleared.'
279
+ : P.warn('Already logged out.')) +
280
+ '\n ' + P.dim('Run ') + P.accent('icli login') + P.dim(' to sign in again.') + '\n\n');
281
+ rl.close();
282
+ exit(0);
283
+ }
224
284
  if (q === '/help' || q === 'help') { printHelp(); rl.prompt(); return; }
225
285
  if (q === '/history' || q === 'history') {
226
286
  const turns = history.filter(m => m.role === 'user');
@@ -278,6 +338,22 @@ async function main() {
278
338
  return;
279
339
  }
280
340
 
341
+ if (cmd === 'update') {
342
+ const ok = selfUpdate(P);
343
+ exit(ok ? 0 : 1);
344
+ }
345
+
346
+ if (cmd === 'logout') {
347
+ const cleared = clearConfig();
348
+ if (cleared) {
349
+ process.stdout.write('\n ' + P.ok('✓') + ' Logged out — credentials cleared.\n');
350
+ process.stdout.write(' ' + P.dim('Run ') + P.accent('icli login') + P.dim(' to sign in again.') + '\n\n');
351
+ } else {
352
+ process.stdout.write('\n ' + P.warn('Not logged in.') + ' Nothing to clear.\n\n');
353
+ }
354
+ return;
355
+ }
356
+
281
357
  if (cmd === 'whoami') {
282
358
  const cfg = loadConfig();
283
359
  if (!cfg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ispbills-icli",
3
- "version": "5.0.0",
3
+ "version": "5.3.0",
4
4
  "description": "iCli — IspBills AI network engineer in your terminal",
5
5
  "keywords": [
6
6
  "ispbills",
package/src/auth.js CHANGED
File without changes
package/src/chat.js CHANGED
@@ -119,6 +119,8 @@ export const SLASH_COMMANDS = [
119
119
  { cmd: '/online', hint: '', desc: 'List online sessions' },
120
120
  { cmd: '/history', hint: '', desc: 'Show conversation history' },
121
121
  { cmd: '/clear', hint: '', desc: 'Clear the screen' },
122
+ { cmd: '/update', hint: '', desc: 'Update iCli to the latest version'},
123
+ { cmd: '/logout', hint: '', desc: 'Log out and clear credentials'},
122
124
  { cmd: '/help', hint: '', desc: 'Show help' },
123
125
  { cmd: '/exit', hint: '', desc: 'Exit iCli' },
124
126
  ];
package/src/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, mkdirSync } from 'fs';
1
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
4
 
@@ -21,3 +21,13 @@ export function saveConfig(cfg) {
21
21
  export function configPath() {
22
22
  return CONFIG_FILE;
23
23
  }
24
+
25
+ /**
26
+ * Remove the saved credentials (log out). Returns true if a config file was
27
+ * present and deleted, false if there was nothing to clear.
28
+ */
29
+ export function clearConfig() {
30
+ if (!existsSync(CONFIG_FILE)) return false;
31
+ rmSync(CONFIG_FILE, { force: true });
32
+ return true;
33
+ }
package/src/layout.js CHANGED
File without changes