@pachca/cli 2026.10.1 → 2026.10.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +24 -24
  3. package/README.template.md +14 -23
  4. package/dist/base-command.js +1 -1
  5. package/dist/client.js +56 -32
  6. package/dist/commands/api.js +1 -1
  7. package/dist/commands/auth/logout.js +23 -12
  8. package/dist/commands/bots/create-token.js +0 -3
  9. package/dist/commands/bots/create.js +0 -4
  10. package/dist/commands/bots/delete.js +0 -3
  11. package/dist/commands/bots/get-stream.js +23 -0
  12. package/dist/commands/bots/list-company.js +0 -3
  13. package/dist/commands/bots/list-events.js +0 -3
  14. package/dist/commands/bots/list-scopes.js +0 -3
  15. package/dist/commands/bots/list.js +0 -3
  16. package/dist/commands/bots/recreate-token-self.js +0 -3
  17. package/dist/commands/bots/recreate-token.js +0 -3
  18. package/dist/commands/bots/reissue-token.js +0 -3
  19. package/dist/commands/bots/remove-event.js +0 -3
  20. package/dist/commands/bots/update-webhook.js +0 -3
  21. package/dist/commands/bots/update.js +0 -5
  22. package/dist/commands/chats/archive.js +0 -4
  23. package/dist/commands/chats/create.js +3 -6
  24. package/dist/commands/chats/download-export.js +0 -3
  25. package/dist/commands/chats/list-company.js +0 -3
  26. package/dist/commands/chats/list.js +1 -6
  27. package/dist/commands/chats/remove-avatar.js +0 -3
  28. package/dist/commands/chats/request-export.js +0 -3
  29. package/dist/commands/chats/unarchive.js +0 -3
  30. package/dist/commands/chats/update-avatar.js +0 -3
  31. package/dist/commands/chats/update.js +0 -3
  32. package/dist/commands/custom-properties/list.js +0 -3
  33. package/dist/commands/drafts/create.js +1 -4
  34. package/dist/commands/drafts/delete.js +0 -3
  35. package/dist/commands/drafts/update.js +1 -1
  36. package/dist/commands/files/uploads.js +0 -3
  37. package/dist/commands/group-tags/create.js +0 -3
  38. package/dist/commands/group-tags/list-users.js +0 -3
  39. package/dist/commands/group-tags/list.js +0 -3
  40. package/dist/commands/important-messages/create.js +64 -0
  41. package/dist/commands/important-messages/delete.js +42 -0
  42. package/dist/commands/important-messages/list.js +89 -0
  43. package/dist/commands/members/add.js +0 -5
  44. package/dist/commands/members/leave.js +0 -3
  45. package/dist/commands/members/list.js +0 -4
  46. package/dist/commands/members/remove.js +0 -3
  47. package/dist/commands/members/update.js +0 -3
  48. package/dist/commands/messages/create.js +3 -8
  49. package/dist/commands/messages/delete.js +0 -3
  50. package/dist/commands/messages/get.js +0 -5
  51. package/dist/commands/messages/list.js +1 -5
  52. package/dist/commands/messages/pin.js +0 -3
  53. package/dist/commands/messages/unfurl.js +0 -3
  54. package/dist/commands/messages/unpin.js +0 -3
  55. package/dist/commands/messages/update.js +2 -7
  56. package/dist/commands/oauth/token-info.js +0 -3
  57. package/dist/commands/profile/delete-avatar.js +0 -3
  58. package/dist/commands/profile/delete-status.js +0 -3
  59. package/dist/commands/profile/get.js +0 -4
  60. package/dist/commands/profile/update-avatar.js +0 -3
  61. package/dist/commands/profile/update-status.js +1 -4
  62. package/dist/commands/reactions/add.js +3 -6
  63. package/dist/commands/reactions/remove.js +3 -6
  64. package/dist/commands/read-member/list-readers.js +0 -3
  65. package/dist/commands/search/list-chats.js +0 -5
  66. package/dist/commands/search/list-messages.js +0 -3
  67. package/dist/commands/search/list-users.js +0 -5
  68. package/dist/commands/security/list.js +1 -6
  69. package/dist/commands/tasks/create.js +1 -6
  70. package/dist/commands/tasks/delete.js +0 -3
  71. package/dist/commands/tasks/get.js +0 -3
  72. package/dist/commands/tasks/list.js +0 -5
  73. package/dist/commands/tasks/update.js +2 -6
  74. package/dist/commands/threads/add.js +0 -4
  75. package/dist/commands/threads/list.js +0 -4
  76. package/dist/commands/users/create.js +0 -5
  77. package/dist/commands/users/delete.js +0 -3
  78. package/dist/commands/users/get-status.js +0 -3
  79. package/dist/commands/users/get.js +0 -3
  80. package/dist/commands/users/list.js +0 -5
  81. package/dist/commands/users/remove-avatar.js +0 -3
  82. package/dist/commands/users/remove-status.js +0 -3
  83. package/dist/commands/users/update-avatar.js +0 -3
  84. package/dist/commands/users/update-status.js +1 -4
  85. package/dist/commands/users/update.js +4 -4
  86. package/dist/commands/views/open.js +0 -5
  87. package/dist/commands/views/submit-response.js +0 -3
  88. package/dist/data/alternatives.json +5 -9
  89. package/dist/data/changelog.json +77 -0
  90. package/dist/data/endpoints.json +1 -1
  91. package/dist/help.js +0 -1
  92. package/dist/hooks/command-not-found.js +0 -1
  93. package/dist/hooks/init.js +5 -1
  94. package/dist/oauth.js +9 -4
  95. package/dist/token-refresh.js +61 -14
  96. package/oclif.manifest.json +1 -1
  97. package/package.json +1 -1
  98. package/dist/commands/guide.js +0 -90
  99. package/dist/data/workflows.json +0 -1058
package/dist/help.js CHANGED
@@ -15,7 +15,6 @@ export default class PachcaHelp extends Help {
15
15
  await super.showRootHelp();
16
16
  this.log('ДОКУМЕНТАЦИЯ');
17
17
  this.log(' Руководства и справочник API https://dev.pachca.com');
18
- this.log(' Сценарии в терминале pachca guide');
19
18
  this.log('');
20
19
  }
21
20
  }
@@ -43,7 +43,6 @@ const hook = async function (opts) {
43
43
  }
44
44
  }
45
45
  process.stderr.write(`\n Все команды: pachca commands\n`);
46
- process.stderr.write(` Помощь по сценариям: pachca guide\n`);
47
46
  }
48
47
  process.exit(2);
49
48
  };
@@ -8,6 +8,7 @@ import { getDefaults, resolveToken, TokenNotFoundError, ProfileNotFoundError, }
8
8
  import { ensureFreshToken, isSessionExpired } from '../token-refresh.js';
9
9
  import { outputError } from '../output.js';
10
10
  import { defaultOutputFormat, isInteractive } from '../utils.js';
11
+ import { detectIntrospectMode } from '../api-introspect.js';
11
12
  /**
12
13
  * Resolve output format from argv before flags are parsed by oclif.
13
14
  */
@@ -40,7 +41,6 @@ const SKIP_AUTH = new Set([
40
41
  'doctor',
41
42
  'autocomplete',
42
43
  'commands',
43
- 'guide',
44
44
  'introspect',
45
45
  'changelog',
46
46
  'upgrade',
@@ -102,6 +102,10 @@ const hook = async function (opts) {
102
102
  // Skip auth for exempt commands
103
103
  if (SKIP_AUTH.has(commandId))
104
104
  return;
105
+ // `api ls` and `api <METHOD> <path> --describe|--spec|--docs` read the bundled
106
+ // spec and never call the API, so an agent can study the API before signing in.
107
+ if (commandId === 'api' && (opts.argv[0] === 'ls' || detectIntrospectMode(opts.argv)))
108
+ return;
105
109
  // Skip auth when --help, --version, or --dry-run flag is passed
106
110
  if (process.argv.includes('--help') || process.argv.includes('-h'))
107
111
  return;
package/dist/oauth.js CHANGED
@@ -162,10 +162,15 @@ export async function pollForToken(grant) {
162
162
  /**
163
163
  * Put out an access token server-side (RFC 7009).
164
164
  *
165
- * The endpoint only accepts self-revocation: the same token must arrive both in
166
- * the body and as the bearer, and it must belong to the CLI's public client.
167
- * Returns false when the server declined, so the caller can say plainly that the
168
- * token is gone from this machine but still alive.
165
+ * The endpoint only accepts self-revocation: the same access token must arrive
166
+ * both in the body and as the bearer, and it must belong to the CLI's public
167
+ * client. A refresh token is refused here: the CLI does not know the client's
168
+ * real `client_id`, and self-revocation compares the body with the bearer.
169
+ *
170
+ * `true` proves the revocation only for a live token. The server answers `200`
171
+ * to an expired one too, yet leaves its record and refresh token untouched.
172
+ * Pass a token known to be live — `revokeSession` in `token-refresh.ts` does.
173
+ * Returns false when the server declined or could not be reached.
169
174
  */
170
175
  export async function revokeToken(token) {
171
176
  try {
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { getConfigDirPath, getProfile, isExpiring, isRefreshable, updateProfileTokens, expiresAtFromNow, } from './profiles.js';
4
- import { OAuthError, refreshAccessToken } from './oauth.js';
4
+ import { OAuthError, refreshAccessToken, revokeToken } from './oauth.js';
5
5
  const LOCK_FILENAME = 'refresh.lock';
6
6
  /** A lock older than this belonged to a process that died mid-refresh. */
7
7
  const LOCK_STALE_MS = 30_000;
@@ -65,46 +65,46 @@ function releaseLock(fd) {
65
65
  * Returns the profile to use. Throws `OAuthError` only when the session is truly
66
66
  * dead and the user has to log in again.
67
67
  */
68
- export async function ensureFreshToken(profileName, profile) {
69
- if (!isRefreshable(profile) || !isExpiring(profile))
68
+ export async function ensureFreshToken(profileName, profile, needsRefresh = isExpiring) {
69
+ if (!isRefreshable(profile) || !needsRefresh(profile))
70
70
  return profile;
71
71
  const fd = acquireLock();
72
72
  if (fd === null) {
73
- const waited = await waitForOtherProcess(profileName);
73
+ const waited = await waitForOtherProcess(profileName, needsRefresh);
74
74
  if (waited)
75
75
  return waited;
76
76
  // The holder gave up or is stuck — refresh ourselves rather than fail.
77
- return refreshAndStore(profileName, profile);
77
+ return refreshAndStore(profileName, profile, needsRefresh);
78
78
  }
79
79
  try {
80
80
  // Re-read: the previous holder may have refreshed while we queued for the lock.
81
81
  const current = getProfile(profileName) ?? profile;
82
- if (!isExpiring(current))
82
+ if (!needsRefresh(current))
83
83
  return current;
84
- return await refreshAndStore(profileName, current);
84
+ return await refreshAndStore(profileName, current, needsRefresh);
85
85
  }
86
86
  finally {
87
87
  releaseLock(fd);
88
88
  }
89
89
  }
90
- async function waitForOtherProcess(profileName) {
90
+ async function waitForOtherProcess(profileName, needsRefresh) {
91
91
  const deadline = Date.now() + LOCK_WAIT_MS;
92
92
  while (Date.now() < deadline) {
93
93
  await sleep(LOCK_POLL_MS);
94
94
  const current = getProfile(profileName);
95
- if (current && !isExpiring(current))
95
+ if (current && !needsRefresh(current))
96
96
  return current;
97
97
  if (!fs.existsSync(lockPath())) {
98
98
  // The holder finished between our read of the profile and this check. Look
99
99
  // once more before refreshing ourselves: their new token is already on disk,
100
100
  // and ours has just been spent.
101
101
  const after = getProfile(profileName);
102
- return after && !isExpiring(after) ? after : undefined;
102
+ return after && !needsRefresh(after) ? after : undefined;
103
103
  }
104
104
  }
105
105
  return undefined;
106
106
  }
107
- async function refreshAndStore(profileName, profile) {
107
+ async function refreshAndStore(profileName, profile, needsRefresh) {
108
108
  let grant;
109
109
  try {
110
110
  grant = await refreshAccessToken(profile.refresh_token);
@@ -116,21 +116,68 @@ async function refreshAndStore(profileName, profile) {
116
116
  // Without this check a lost race logs the user out for no reason.
117
117
  if (error instanceof OAuthError && error.code === 'invalid_grant') {
118
118
  const current = getProfile(profileName);
119
- if (current?.token && !isExpiring(current))
119
+ if (current?.token && !needsRefresh(current))
120
120
  return current;
121
121
  }
122
122
  throw error;
123
123
  }
124
- const updated = updateProfileTokens(profileName, {
124
+ const tokens = {
125
125
  token: grant.access_token,
126
126
  refresh_token: grant.refresh_token,
127
127
  expires_at: grant.expires_in ? expiresAtFromNow(grant.expires_in) : undefined,
128
128
  scopes: grant.scope ? grant.scope.split(' ').filter(Boolean) : undefined,
129
+ };
130
+ const updated = updateProfileTokens(profileName, tokens);
131
+ // The profile can vanish from the config mid-refresh. The new token is still
132
+ // the one in force: hand it back with its expiry so a caller can revoke it.
133
+ return (updated ?? {
134
+ ...profile,
135
+ token: tokens.token,
136
+ refresh_token: tokens.refresh_token ?? profile.refresh_token,
137
+ expires_at: tokens.expires_at ?? profile.expires_at,
129
138
  });
130
- return updated ?? { ...profile, token: grant.access_token };
131
139
  }
132
140
  /** True when the failure means the session is gone for good, not a hiccup. */
133
141
  export function isSessionExpired(error) {
134
142
  return (error instanceof OAuthError &&
135
143
  ['invalid_grant', 'invalid_request', 'invalid_client', 'unauthorized_client'].includes(error.code));
136
144
  }
145
+ /**
146
+ * True only when the access token will outlive the request about to use it.
147
+ * Unknown expiry counts as not live: revoking is only proof when the token is.
148
+ */
149
+ function hasLiveToken(profile) {
150
+ if (!profile.token || !profile.expires_at)
151
+ return false;
152
+ if (Number.isNaN(Date.parse(profile.expires_at)))
153
+ return false;
154
+ return !isExpiring(profile);
155
+ }
156
+ /**
157
+ * Close the profile's OAuth login on the server.
158
+ *
159
+ * The revoke endpoint answers `200` even when it skipped the token: an expired
160
+ * access token is not revocable, so the record stays as it was, refresh token
161
+ * included, and that refresh token can still mint new access tokens. Revoking
162
+ * by the refresh token itself is not open to the CLI: self-revocation takes only
163
+ * the access token that also came as the bearer. So the token sent is always a
164
+ * live one — refreshed first when the saved one has run out. The refresh retires
165
+ * the old record on the server, and the revoke then closes the new one.
166
+ */
167
+ export async function revokeSession(profileName, profile) {
168
+ let current;
169
+ try {
170
+ current = await ensureFreshToken(profileName, profile, (p) => !hasLiveToken(p));
171
+ }
172
+ catch (error) {
173
+ // `invalid_grant` on refresh: the token is revoked or gone on the server.
174
+ if (error instanceof OAuthError && error.code === 'invalid_grant')
175
+ return 'closed';
176
+ return 'open';
177
+ }
178
+ // No refresh token and the access token has run out, or it never came back
179
+ // from the OS store: there is nothing to revoke with that would prove anything.
180
+ if (!hasLiveToken(current))
181
+ return 'open';
182
+ return (await revokeToken(current.token)) ? 'revoked' : 'open';
183
+ }