@pachca/cli 2026.3.7 → 2026.3.9

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/base-command.js +1 -43
  3. package/dist/commands/auth/list.js +0 -1
  4. package/dist/commands/auth/login.js +0 -9
  5. package/dist/commands/auth/status.js +0 -10
  6. package/dist/commands/bots/list-events.js +0 -1
  7. package/dist/commands/bots/remove-event.js +0 -1
  8. package/dist/commands/bots/update.js +0 -1
  9. package/dist/commands/chats/archive.js +0 -1
  10. package/dist/commands/chats/create.js +0 -1
  11. package/dist/commands/chats/get.js +0 -1
  12. package/dist/commands/chats/list.js +0 -1
  13. package/dist/commands/chats/unarchive.js +0 -1
  14. package/dist/commands/chats/update.js +0 -1
  15. package/dist/commands/commands.js +5 -12
  16. package/dist/commands/common/custom-properties.js +0 -1
  17. package/dist/commands/common/get-exports.js +0 -1
  18. package/dist/commands/common/request-export.js +0 -1
  19. package/dist/commands/common/uploads.js +0 -1
  20. package/dist/commands/doctor.js +1 -1
  21. package/dist/commands/group-tags/create.js +0 -1
  22. package/dist/commands/group-tags/delete.js +0 -1
  23. package/dist/commands/group-tags/get.js +0 -1
  24. package/dist/commands/group-tags/list-users.js +0 -1
  25. package/dist/commands/group-tags/list.js +0 -1
  26. package/dist/commands/group-tags/update.js +0 -1
  27. package/dist/commands/link-previews/add.js +0 -1
  28. package/dist/commands/members/add-group-tags.js +0 -1
  29. package/dist/commands/members/add.js +0 -1
  30. package/dist/commands/members/leave.js +0 -1
  31. package/dist/commands/members/list.js +0 -1
  32. package/dist/commands/members/remove-group-tag.js +0 -1
  33. package/dist/commands/members/remove.js +0 -1
  34. package/dist/commands/members/update.js +0 -1
  35. package/dist/commands/messages/create.js +0 -1
  36. package/dist/commands/messages/delete.js +0 -1
  37. package/dist/commands/messages/get.js +0 -1
  38. package/dist/commands/messages/list.js +0 -1
  39. package/dist/commands/messages/pin.js +0 -1
  40. package/dist/commands/messages/unpin.js +0 -1
  41. package/dist/commands/messages/update.js +0 -1
  42. package/dist/commands/profile/delete-status.js +0 -1
  43. package/dist/commands/profile/get-status.js +0 -1
  44. package/dist/commands/profile/get.js +0 -1
  45. package/dist/commands/profile/update-status.js +0 -1
  46. package/dist/commands/reactions/add.js +0 -1
  47. package/dist/commands/reactions/list.js +0 -1
  48. package/dist/commands/reactions/remove.js +0 -1
  49. package/dist/commands/read-member/list-readers.js +0 -1
  50. package/dist/commands/search/list-chats.js +0 -1
  51. package/dist/commands/search/list-messages.js +0 -1
  52. package/dist/commands/search/list-users.js +0 -1
  53. package/dist/commands/security/list.js +0 -1
  54. package/dist/commands/tasks/create.js +0 -1
  55. package/dist/commands/tasks/delete.js +0 -1
  56. package/dist/commands/tasks/get.js +0 -1
  57. package/dist/commands/tasks/list.js +0 -1
  58. package/dist/commands/tasks/update.js +0 -1
  59. package/dist/commands/threads/add.js +0 -1
  60. package/dist/commands/threads/get.js +0 -1
  61. package/dist/commands/upgrade.js +111 -0
  62. package/dist/commands/upload.js +0 -1
  63. package/dist/commands/users/create.js +0 -1
  64. package/dist/commands/users/delete.js +0 -1
  65. package/dist/commands/users/get-status.js +0 -1
  66. package/dist/commands/users/get.js +0 -1
  67. package/dist/commands/users/list.js +0 -1
  68. package/dist/commands/users/remove-status.js +0 -1
  69. package/dist/commands/users/update-status.js +0 -1
  70. package/dist/commands/users/update.js +0 -1
  71. package/dist/commands/views/open.js +0 -1
  72. package/dist/data/changelog.json +42 -0
  73. package/dist/hooks/init.js +77 -23
  74. package/dist/hooks/postrun.js +3 -66
  75. package/dist/profiles.js +0 -8
  76. package/oclif.manifest.json +248 -256
  77. package/package.json +1 -1
  78. package/dist/commands/auth/refresh.js +0 -67
@@ -38,7 +38,6 @@ export default class TasksDelete extends BaseCommand {
38
38
  this.exit(0);
39
39
  }
40
40
  }
41
- this.checkScope("tasks:delete");
42
41
  const { data } = await this.apiRequest({
43
42
  method: 'DELETE',
44
43
  path: `/tasks/${args.id}`,
@@ -24,7 +24,6 @@ export default class TasksGet extends BaseCommand {
24
24
  async run() {
25
25
  const { args, flags } = await this.parse(TasksGet);
26
26
  this.parsedFlags = flags;
27
- this.checkScope("tasks:read");
28
27
  const { data } = await this.apiRequest({
29
28
  method: 'GET',
30
29
  path: `/tasks/${args.id}`,
@@ -29,7 +29,6 @@ export default class TasksList extends BaseCommand {
29
29
  async run() {
30
30
  const { args, flags } = await this.parse(TasksList);
31
31
  this.parsedFlags = flags;
32
- this.checkScope("tasks:read");
33
32
  if (flags.all) {
34
33
  // Auto-pagination
35
34
  const allData = [];
@@ -60,7 +60,6 @@ export default class TasksUpdate extends BaseCommand {
60
60
  }
61
61
  flags['content'] = Buffer.concat(chunks).toString('utf-8').trimEnd();
62
62
  }
63
- this.checkScope("tasks:update");
64
63
  const body = { task: {
65
64
  kind: flags['kind'],
66
65
  content: flags['content'],
@@ -23,7 +23,6 @@ export default class ThreadsAdd extends BaseCommand {
23
23
  async run() {
24
24
  const { args, flags } = await this.parse(ThreadsAdd);
25
25
  this.parsedFlags = flags;
26
- this.checkScope("threads:create");
27
26
  const { data } = await this.apiRequest({
28
27
  method: 'POST',
29
28
  path: `/messages/${args.id}/thread`,
@@ -19,7 +19,6 @@ export default class ThreadsGet extends BaseCommand {
19
19
  async run() {
20
20
  const { args, flags } = await this.parse(ThreadsGet);
21
21
  this.parsedFlags = flags;
22
- this.checkScope("threads:read");
23
22
  const { data } = await this.apiRequest({
24
23
  method: 'GET',
25
24
  path: `/threads/${args.id}`,
@@ -0,0 +1,111 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import semver from 'semver';
5
+ import { BaseCommand } from '../base-command.js';
6
+ /**
7
+ * Detect how CLI was installed by examining the executable path.
8
+ */
9
+ function detectPackageManager(binPath) {
10
+ const resolved = fs.realpathSync(binPath);
11
+ // Homebrew: /opt/homebrew/... or /usr/local/Cellar/...
12
+ if (resolved.includes('/homebrew/') || resolved.includes('/Cellar/')) {
13
+ return { name: 'homebrew', cmd: 'brew', args: ['upgrade', '@pachca/cli'] };
14
+ }
15
+ // Bun global: ~/.bun/install/global/ or contains /bun/
16
+ if (resolved.includes('/bun/') || resolved.includes('.bun/')) {
17
+ return { name: 'bun', cmd: 'bun', args: ['install', '-g', '@pachca/cli@latest'] };
18
+ }
19
+ // pnpm global: contains /pnpm/
20
+ if (resolved.includes('/pnpm/')) {
21
+ return { name: 'pnpm', cmd: 'pnpm', args: ['install', '-g', '@pachca/cli@latest'] };
22
+ }
23
+ // yarn global: contains /yarn/
24
+ if (resolved.includes('/yarn/')) {
25
+ return { name: 'yarn', cmd: 'yarn', args: ['global', 'add', '@pachca/cli@latest'] };
26
+ }
27
+ // Default: npm
28
+ return { name: 'npm', cmd: 'npm', args: ['install', '-g', '@pachca/cli@latest'] };
29
+ }
30
+ export default class Upgrade extends BaseCommand {
31
+ static description = 'Обновить CLI до последней версии';
32
+ static examples = [
33
+ '<%= config.bin %> upgrade',
34
+ ];
35
+ static flags = {
36
+ ...BaseCommand.baseFlags,
37
+ };
38
+ async run() {
39
+ const { flags } = await this.parse(Upgrade);
40
+ this.parsedFlags = flags;
41
+ const currentVersion = this.config.version;
42
+ const format = this.getOutputFormat();
43
+ // Fetch latest version from npm registry
44
+ let latest;
45
+ try {
46
+ const response = await fetch('https://registry.npmjs.org/@pachca/cli');
47
+ if (response.ok) {
48
+ const data = (await response.json());
49
+ latest = data['dist-tags']?.latest;
50
+ }
51
+ }
52
+ catch {
53
+ // Fallback: try cached version
54
+ }
55
+ if (!latest) {
56
+ try {
57
+ const versionFile = path.join(this.config.cacheDir, 'version');
58
+ if (fs.existsSync(versionFile)) {
59
+ const cached = JSON.parse(fs.readFileSync(versionFile, 'utf-8'));
60
+ latest = cached.latest;
61
+ }
62
+ }
63
+ catch {
64
+ // ignore
65
+ }
66
+ }
67
+ if (!latest) {
68
+ if (format === 'json') {
69
+ this.output({ upgraded: false, current: currentVersion, error: 'Cannot determine latest version' });
70
+ }
71
+ else {
72
+ process.stderr.write('✗ Не удалось определить последнюю версию\n');
73
+ }
74
+ this.exit(1);
75
+ }
76
+ if (semver.valid(currentVersion) && semver.valid(latest) && !semver.gt(latest, currentVersion)) {
77
+ if (format === 'json') {
78
+ this.output({ upgraded: false, current: currentVersion, latest });
79
+ }
80
+ else {
81
+ process.stderr.write(`✔ CLI уже обновлён (${currentVersion})\n`);
82
+ }
83
+ return;
84
+ }
85
+ const pm = detectPackageManager(process.argv[1] || this.config.binPath || '');
86
+ if (format !== 'json' && process.stderr.isTTY) {
87
+ process.stderr.write(`Обновление ${currentVersion} → ${latest} (${pm.name})...\n`);
88
+ }
89
+ try {
90
+ execFileSync(pm.cmd, pm.args, {
91
+ stdio: format === 'json' ? 'ignore' : 'inherit',
92
+ });
93
+ if (format === 'json') {
94
+ this.output({ upgraded: true, from: currentVersion, to: latest, package_manager: pm.name });
95
+ }
96
+ else {
97
+ process.stderr.write(`\n✔ Обновлено до ${latest}\n`);
98
+ }
99
+ }
100
+ catch {
101
+ if (format === 'json') {
102
+ this.output({ upgraded: false, current: currentVersion, latest, package_manager: pm.name, error: `${pm.cmd} failed` });
103
+ }
104
+ else {
105
+ process.stderr.write(`\n✗ Не удалось обновить через ${pm.name}. Попробуйте вручную:\n`);
106
+ process.stderr.write(` ${pm.cmd} ${pm.args.join(' ')}\n`);
107
+ }
108
+ this.exit(1);
109
+ }
110
+ }
111
+ }
@@ -21,7 +21,6 @@ export default class Upload extends BaseCommand {
21
21
  async run() {
22
22
  const { args, flags } = await this.parse(Upload);
23
23
  this.parsedFlags = flags;
24
- this.checkScope('uploads:write');
25
24
  // Step 1: Get upload params
26
25
  const { data: uploadData } = await this.apiRequest({
27
26
  method: 'POST',
@@ -85,7 +85,6 @@ export default class UsersCreate extends BaseCommand {
85
85
  this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --email <string>. pachca introspect users create" });
86
86
  }
87
87
  }
88
- this.checkScope("users:create");
89
88
  const body = {
90
89
  user: {
91
90
  first_name: flags['first-name'],
@@ -38,7 +38,6 @@ export default class UsersDelete extends BaseCommand {
38
38
  this.exit(0);
39
39
  }
40
40
  }
41
- this.checkScope("users:delete");
42
41
  const { data } = await this.apiRequest({
43
42
  method: 'DELETE',
44
43
  path: `/users/${args.id}`,
@@ -24,7 +24,6 @@ export default class UsersGetStatus extends BaseCommand {
24
24
  async run() {
25
25
  const { args, flags } = await this.parse(UsersGetStatus);
26
26
  this.parsedFlags = flags;
27
- this.checkScope("user_status:read");
28
27
  const { data } = await this.apiRequest({
29
28
  method: 'GET',
30
29
  path: `/users/${args.user_id}/status`,
@@ -24,7 +24,6 @@ export default class UsersGet extends BaseCommand {
24
24
  async run() {
25
25
  const { args, flags } = await this.parse(UsersGet);
26
26
  this.parsedFlags = flags;
27
- this.checkScope("users:read");
28
27
  const { data } = await this.apiRequest({
29
28
  method: 'GET',
30
29
  path: `/users/${args.id}`,
@@ -32,7 +32,6 @@ export default class UsersList extends BaseCommand {
32
32
  async run() {
33
33
  const { args, flags } = await this.parse(UsersList);
34
34
  this.parsedFlags = flags;
35
- this.checkScope("users:read");
36
35
  if (flags.all) {
37
36
  // Auto-pagination
38
37
  const allData = [];
@@ -38,7 +38,6 @@ export default class UsersRemoveStatus extends BaseCommand {
38
38
  this.exit(0);
39
39
  }
40
40
  }
41
- this.checkScope("user_status:write");
42
41
  const { data } = await this.apiRequest({
43
42
  method: 'DELETE',
44
43
  path: `/users/${args.user_id}/status`,
@@ -76,7 +76,6 @@ export default class UsersUpdateStatus extends BaseCommand {
76
76
  if (validationErrors.length > 0) {
77
77
  this.validationError(validationErrors);
78
78
  }
79
- this.checkScope("user_status:write");
80
79
  const body = { status: {
81
80
  emoji: flags['emoji'],
82
81
  title: flags['title'],
@@ -58,7 +58,6 @@ export default class UsersUpdate extends BaseCommand {
58
58
  async run() {
59
59
  const { args, flags } = await this.parse(UsersUpdate);
60
60
  this.parsedFlags = flags;
61
- this.checkScope("users:update");
62
61
  const body = { user: {
63
62
  first_name: flags['first-name'],
64
63
  last_name: flags['last-name'],
@@ -96,7 +96,6 @@ export default class ViewsOpen extends BaseCommand {
96
96
  if (validationErrors.length > 0) {
97
97
  this.validationError(validationErrors);
98
98
  }
99
- this.checkScope("views:write");
100
99
  const body = {
101
100
  view: {
102
101
  title: flags['title'],
@@ -1,4 +1,46 @@
1
1
  [
2
+ {
3
+ "version": "2026.3.9",
4
+ "date": "19 марта 2026",
5
+ "changes": [
6
+ {
7
+ "type": "~",
8
+ "command": "all",
9
+ "description": "Убрана клиентская валидация скоупов — проверка разрешений теперь только на стороне API (403)"
10
+ },
11
+ {
12
+ "type": "~",
13
+ "command": "auth login",
14
+ "description": "Профиль больше не сохраняет список скоупов — только тип, имя и email"
15
+ },
16
+ {
17
+ "type": "-",
18
+ "command": "auth refresh",
19
+ "description": "Команда удалена — кеширование скоупов больше не используется"
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "version": "2026.3.8",
25
+ "date": "17 марта 2026",
26
+ "changes": [
27
+ {
28
+ "type": "+",
29
+ "command": "upgrade",
30
+ "description": "Команда pachca upgrade — обновление CLI до последней версии"
31
+ },
32
+ {
33
+ "type": "-",
34
+ "command": "init hook",
35
+ "description": "Ошибки авторизации теперь выводятся в JSON при -o json (раньше формат игнорировался)"
36
+ },
37
+ {
38
+ "type": "-",
39
+ "command": "update banner",
40
+ "description": "Баннер обновления теперь показывается даже при ошибках команд"
41
+ }
42
+ ]
43
+ },
2
44
  {
3
45
  "version": "2026.3.7",
4
46
  "date": "16 марта 2026",
@@ -1,7 +1,29 @@
1
- import { setProfile, resolveToken, TokenNotFoundError, ProfileNotFoundError, } from '../profiles.js';
2
- import { request } from '../client.js';
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { spawn } from 'node:child_process';
5
+ import semver from 'semver';
6
+ import ansis from 'ansis';
7
+ import { getDefaults, resolveToken, TokenNotFoundError, ProfileNotFoundError, } from '../profiles.js';
3
8
  import { outputError } from '../output.js';
4
- import { defaultOutputFormat } from '../utils.js';
9
+ import { defaultOutputFormat, isInteractive } from '../utils.js';
10
+ /**
11
+ * Resolve output format from argv before flags are parsed by oclif.
12
+ */
13
+ function resolveOutputFromArgv() {
14
+ if (process.argv.includes('--json'))
15
+ return 'json';
16
+ const outputIdx = Math.max(process.argv.indexOf('-o'), process.argv.indexOf('--output'));
17
+ if (outputIdx !== -1) {
18
+ const val = process.argv[outputIdx + 1];
19
+ if (val && ['json', 'yaml', 'csv', 'table'].includes(val))
20
+ return val;
21
+ }
22
+ const defaults = getDefaults();
23
+ if (defaults.output)
24
+ return defaults.output;
25
+ return defaultOutputFormat();
26
+ }
5
27
  // Commands that don't require auth
6
28
  const SKIP_AUTH = new Set([
7
29
  'auth:login',
@@ -20,12 +42,62 @@ const SKIP_AUTH = new Set([
20
42
  'guide',
21
43
  'introspect',
22
44
  'changelog',
45
+ 'upgrade',
23
46
  'help',
24
47
  ]);
25
48
  const hook = async function (opts) {
26
49
  const commandId = opts.id;
27
50
  if (!commandId)
28
51
  return;
52
+ // Register update banner on process exit (fires even when command fails)
53
+ if (isInteractive() && !process.env.PACHCA_SKIP_NEW_VERSION_CHECK) {
54
+ const cacheDir = opts.config.cacheDir;
55
+ const versionFile = path.join(cacheDir, 'version');
56
+ const currentVersion = opts.config.version;
57
+ process.on('exit', () => {
58
+ try {
59
+ if (fs.existsSync(versionFile)) {
60
+ const cached = JSON.parse(fs.readFileSync(versionFile, 'utf-8'));
61
+ const latest = cached.latest;
62
+ if (latest && semver.valid(latest) && semver.valid(currentVersion) && semver.gt(latest, currentVersion)) {
63
+ const box = [
64
+ '',
65
+ ansis.yellow(`╭${'─'.repeat(37)}╮`),
66
+ ansis.yellow(`│ Доступна новая версия: ${latest.padEnd(12)}│`),
67
+ ansis.yellow(`│ pachca upgrade${' '.repeat(21)}│`),
68
+ ansis.yellow(`╰${'─'.repeat(37)}╯`),
69
+ '',
70
+ ].join('\n');
71
+ process.stderr.write(box);
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // ignore
77
+ }
78
+ });
79
+ // Background refresh if cache is stale (>1h)
80
+ try {
81
+ let shouldRefresh = true;
82
+ if (fs.existsSync(versionFile)) {
83
+ const stat = fs.statSync(versionFile);
84
+ shouldRefresh = Date.now() - stat.mtimeMs > 60 * 60 * 1000;
85
+ }
86
+ if (shouldRefresh) {
87
+ if (!fs.existsSync(cacheDir)) {
88
+ fs.mkdirSync(cacheDir, { recursive: true });
89
+ }
90
+ const scriptPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'get-version.js');
91
+ if (fs.existsSync(scriptPath)) {
92
+ const child = spawn(process.execPath, [scriptPath, versionFile], { detached: true, stdio: 'ignore' });
93
+ child.unref();
94
+ }
95
+ }
96
+ }
97
+ catch {
98
+ // ignore
99
+ }
100
+ }
29
101
  // Skip auth for exempt commands
30
102
  if (SKIP_AUTH.has(commandId))
31
103
  return;
@@ -58,28 +130,10 @@ const hook = async function (opts) {
58
130
  // Verify auth exists
59
131
  try {
60
132
  const { profileName, profile } = resolveToken({ token: argvToken, profile: argvProfile });
61
- // Refresh bot scopes if stale (>24h)
62
- if (profileName && profile?.type === 'bot' && profile.scopes_refreshed_at) {
63
- const refreshedAt = new Date(profile.scopes_refreshed_at).getTime();
64
- const now = Date.now();
65
- const twentyFourHours = 24 * 60 * 60 * 1000;
66
- if (now - refreshedAt > twentyFourHours) {
67
- try {
68
- const response = await request({ method: 'GET', path: '/oauth/token/info', token: profile.token }, { quiet: true });
69
- const wrapper = response.data;
70
- profile.scopes = wrapper.data.scopes || [];
71
- profile.scopes_refreshed_at = new Date().toISOString();
72
- setProfile(profileName, profile);
73
- }
74
- catch {
75
- // Silently ignore refresh errors — use cached scopes
76
- }
77
- }
78
- }
79
133
  }
80
134
  catch (error) {
81
135
  if (error instanceof TokenNotFoundError) {
82
- const format = defaultOutputFormat();
136
+ const format = resolveOutputFromArgv();
83
137
  if (format === 'json' || !process.stderr.isTTY) {
84
138
  outputError({ error: 'Token not found', type: 'PACHCA_AUTH_ERROR', code: null, hint: 'pachca auth login --token <your-token>' }, format);
85
139
  }
@@ -94,7 +148,7 @@ const hook = async function (opts) {
94
148
  process.exit(3);
95
149
  }
96
150
  if (error instanceof ProfileNotFoundError) {
97
- const format = defaultOutputFormat();
151
+ const format = resolveOutputFromArgv();
98
152
  outputError({ error: `Profile "${error.profileName}" not found`, type: 'PACHCA_USAGE_ERROR', code: null, hint: 'pachca auth list' }, format);
99
153
  process.exit(2);
100
154
  }
@@ -1,67 +1,4 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { spawn } from 'node:child_process';
5
- import semver from 'semver';
6
- import ansis from 'ansis';
7
- import { isInteractive } from '../utils.js';
8
- const hook = async function (opts) {
9
- // Don't show update banner in non-interactive mode
10
- if (!isInteractive())
11
- return;
12
- if (process.env.PACHCA_SKIP_NEW_VERSION_CHECK)
13
- return;
14
- const cacheDir = opts.config.cacheDir;
15
- const versionFile = path.join(cacheDir, 'version');
16
- // Check if version file exists and show banner
17
- try {
18
- if (fs.existsSync(versionFile)) {
19
- const cached = JSON.parse(fs.readFileSync(versionFile, 'utf-8'));
20
- const latest = cached.latest;
21
- const currentVersion = opts.config.version;
22
- if (latest && semver.valid(latest) && semver.valid(currentVersion)) {
23
- if (semver.gt(latest, currentVersion)) {
24
- const box = [
25
- '',
26
- ansis.yellow(`╭${'─'.repeat(37)}╮`),
27
- ansis.yellow(`│ Доступна новая версия: ${latest.padEnd(12)}│`),
28
- ansis.yellow(`│ npm install -g @pachca/cli${' '.repeat(9)}│`),
29
- ansis.yellow(`╰${'─'.repeat(37)}╯`),
30
- '',
31
- ].join('\n');
32
- process.stderr.write(box);
33
- }
34
- }
35
- }
36
- }
37
- catch {
38
- // Ignore cache read errors
39
- }
40
- // Check if we should refresh version cache (mtime > 24h)
41
- try {
42
- let shouldRefresh = true;
43
- if (fs.existsSync(versionFile)) {
44
- const stat = fs.statSync(versionFile);
45
- const ageMs = Date.now() - stat.mtimeMs;
46
- shouldRefresh = ageMs > 60 * 60 * 1000;
47
- }
48
- if (shouldRefresh) {
49
- if (!fs.existsSync(cacheDir)) {
50
- fs.mkdirSync(cacheDir, { recursive: true });
51
- }
52
- // Spawn detached background process to fetch latest version
53
- const scriptPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'get-version.js');
54
- if (fs.existsSync(scriptPath)) {
55
- const child = spawn(process.execPath, [scriptPath, versionFile], {
56
- detached: true,
57
- stdio: 'ignore',
58
- });
59
- child.unref();
60
- }
61
- }
62
- }
63
- catch {
64
- // Ignore errors in update check
65
- }
66
- };
1
+ // Banner and version refresh moved to init hook (process.on('exit'))
2
+ // so they fire even when commands fail.
3
+ const hook = async function () { };
67
4
  export default hook;
package/dist/profiles.js CHANGED
@@ -171,14 +171,6 @@ export function getConfigFilePermissions() {
171
171
  const stat = fs.statSync(configPath);
172
172
  return (stat.mode & 0o777).toString(8);
173
173
  }
174
- export function invalidateScopes(profileName) {
175
- const config = readConfig();
176
- if (config.profiles?.[profileName]) {
177
- config.profiles[profileName].scopes = [];
178
- config.profiles[profileName].scopes_refreshed_at = undefined;
179
- writeConfig(config);
180
- }
181
- }
182
174
  export class TokenNotFoundError extends Error {
183
175
  constructor() {
184
176
  super('Token not found');