@foxden-app/foxclaw 0.4.6 → 0.4.7
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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/README_EN.md +1 -1
- package/dist/codex_app/client.d.ts +1 -1
- package/dist/codex_app/client.js +2 -2
- package/dist/controller/controller.d.ts +5 -0
- package/dist/controller/controller.js +185 -8
- package/dist/i18n.d.ts +16 -2
- package/dist/i18n.js +16 -2
- package/docs/user-manual.md +4 -3
- package/docs/zh/user-manual.md +4 -3
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +3 -0
- package/skills/npm-publish/SKILL.md +2 -0
- package/skills/npm-publish/agents/openai.yaml +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.4.7 - 2026-06-02
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- `/auth` 面板新增“刷新全部”按钮,并支持 `/auth refresh all`。
|
|
9
|
+
- 刷新全部仅在所有 Telegram runtime、微信 runtime、审批、待输入、登录流程和 auth 镜像写入都空闲时执行。
|
|
10
|
+
- 执行时逐个切换 ChatGPT 候选,调用 Codex `account/read refreshToken=true` 主动刷新,使用 usage 接口验证后再镜像同步,并在结束后恢复原当前 auth、刷新面板摘要。
|
|
11
|
+
- 更新 FoxClaw/npm 发布 skill:用户要求收尾或发布时,校验通过后自动 commit、push、publish,只在真实外部阻塞时再等待用户。
|
|
12
|
+
|
|
13
|
+
### English
|
|
14
|
+
- Added a Refresh all button to the `/auth` panel and the `/auth refresh all` command.
|
|
15
|
+
- Refresh all runs only when every Telegram runtime, the Weixin runtime, approvals, inputs, login flows, and auth mirror writes are idle.
|
|
16
|
+
- It visits each ChatGPT candidate, calls Codex `account/read refreshToken=true`, validates through the usage endpoint before mirroring, restores the original current auth, and refreshes the panel summary.
|
|
17
|
+
- Updated the FoxClaw/npm publish skills so release wrap-up proceeds through commit, push, and publish after validation, pausing only for real external blockers.
|
|
18
|
+
|
|
5
19
|
## 0.4.6 - 2026-06-01
|
|
6
20
|
|
|
7
21
|
### 中文
|
package/README.md
CHANGED
|
@@ -258,7 +258,7 @@ FoxClaw 会把 `codex app-server` 作为 detached 子进程启动,记录其 pi
|
|
|
258
258
|
- `/status`、`/account`、`/quota`、`/update`
|
|
259
259
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
260
260
|
- `/login_device`、`/login_cancel [id]`、`/logout confirm`
|
|
261
|
-
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
261
|
+
- `/auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]`
|
|
262
262
|
- `/threads [query]`、`/threads archived`、`/open <n>`
|
|
263
263
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
264
264
|
- `/history [limit]`、`/files <query>`、`/remote`
|
package/README_EN.md
CHANGED
|
@@ -258,7 +258,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
258
258
|
- `/status`, `/account`, `/quota`, `/update`
|
|
259
259
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
260
260
|
- `/login_device`, `/login_cancel [id]`, `/logout confirm`
|
|
261
|
-
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
261
|
+
- `/auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]`
|
|
262
262
|
- `/threads [query]`, `/threads archived`, `/open <n>`
|
|
263
263
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
264
264
|
- `/history [limit]`, `/files <query>`, `/remote`
|
|
@@ -125,7 +125,7 @@ export declare class CodexAppClient extends EventEmitter {
|
|
|
125
125
|
reviewThreadId: string;
|
|
126
126
|
}>;
|
|
127
127
|
listModels(): Promise<ModelInfo[]>;
|
|
128
|
-
readAccount(): Promise<CodexAccountInfo | null>;
|
|
128
|
+
readAccount(refreshToken?: boolean): Promise<CodexAccountInfo | null>;
|
|
129
129
|
startDeviceLogin(): Promise<CodexLoginDeviceCode>;
|
|
130
130
|
cancelLogin(loginId: string): Promise<void>;
|
|
131
131
|
logoutAccount(): Promise<void>;
|
package/dist/codex_app/client.js
CHANGED
|
@@ -277,8 +277,8 @@ export class CodexAppClient extends EventEmitter {
|
|
|
277
277
|
} while (cursor);
|
|
278
278
|
return models;
|
|
279
279
|
}
|
|
280
|
-
async readAccount() {
|
|
281
|
-
const result = await this.request('account/read', { refreshToken
|
|
280
|
+
async readAccount(refreshToken = false) {
|
|
281
|
+
const result = await this.request('account/read', { refreshToken });
|
|
282
282
|
const account = result?.account;
|
|
283
283
|
if (!account || typeof account !== 'object') {
|
|
284
284
|
return null;
|
|
@@ -45,6 +45,7 @@ export declare class BridgeSessionCore {
|
|
|
45
45
|
private pendingApprovalMessages;
|
|
46
46
|
private pendingAuthRotation;
|
|
47
47
|
private authRotationInProgress;
|
|
48
|
+
private authRefreshAllInProgress;
|
|
48
49
|
private authRotationFailedTargets;
|
|
49
50
|
private localUsageCache;
|
|
50
51
|
private localUsageCacheLoaded;
|
|
@@ -231,12 +232,14 @@ export declare class BridgeSessionCore {
|
|
|
231
232
|
private handleFastCommand;
|
|
232
233
|
private setCollaborationMode;
|
|
233
234
|
private handleAuthReloadCommand;
|
|
235
|
+
private canRunGlobalAuthRefresh;
|
|
234
236
|
private handleSelfUpdateCommand;
|
|
235
237
|
private scheduleSelfUpdateStatusPoll;
|
|
236
238
|
private clearSelfUpdateStatusPoll;
|
|
237
239
|
private pollSelfUpdateStatus;
|
|
238
240
|
private formatSelfUpdateResult;
|
|
239
241
|
private handleAuthCommand;
|
|
242
|
+
private handleAuthRefreshAllCommand;
|
|
240
243
|
private handleAuthUseCommand;
|
|
241
244
|
private handleAuthToggleCommand;
|
|
242
245
|
private findLatestAuthChoiceList;
|
|
@@ -288,6 +291,7 @@ export declare class BridgeSessionCore {
|
|
|
288
291
|
private switchCodexAuthAndRestart;
|
|
289
292
|
private recoverCodexAuthCandidate;
|
|
290
293
|
private syncCodexAuthCandidate;
|
|
294
|
+
private refreshAllCodexAuthCandidates;
|
|
291
295
|
private buildNativeCollaborationMode;
|
|
292
296
|
private buildCodexUsageStatusLines;
|
|
293
297
|
private buildCodexLocalUsageStatusLines;
|
|
@@ -299,6 +303,7 @@ export declare class BridgeSessionCore {
|
|
|
299
303
|
private codexLocalUsageSnapshotPath;
|
|
300
304
|
private refreshCurrentCodexAuthQuota;
|
|
301
305
|
private applySharedCodexAuthQuotaSnapshots;
|
|
306
|
+
private recordCodexAuthQuotaSnapshot;
|
|
302
307
|
private readCodexAuthCandidateAccountIds;
|
|
303
308
|
private codexAuthQuotaSnapshotMatchesAccount;
|
|
304
309
|
private readCodexAuthQuotaSnapshots;
|
|
@@ -113,6 +113,7 @@ export class BridgeSessionCore {
|
|
|
113
113
|
pendingApprovalMessages = new Map();
|
|
114
114
|
pendingAuthRotation = null;
|
|
115
115
|
authRotationInProgress = false;
|
|
116
|
+
authRefreshAllInProgress = false;
|
|
116
117
|
authRotationFailedTargets = new Set();
|
|
117
118
|
localUsageCache = null;
|
|
118
119
|
localUsageCacheLoaded = false;
|
|
@@ -232,6 +233,7 @@ export class BridgeSessionCore {
|
|
|
232
233
|
this.recentCommandUsageByScope.clear();
|
|
233
234
|
this.commandUsageSequence = 0;
|
|
234
235
|
this.pendingAuthRotation = null;
|
|
236
|
+
this.authRefreshAllInProgress = false;
|
|
235
237
|
this.clearObservedThreadWatchers();
|
|
236
238
|
this.releaseActiveTurnsForBridgeShutdown();
|
|
237
239
|
this.bot.stop();
|
|
@@ -1008,7 +1010,7 @@ export class BridgeSessionCore {
|
|
|
1008
1010
|
await this.handleAuthToggleCallback(event, authToggleMatch[1], Number.parseInt(authToggleMatch[2], 10), locale);
|
|
1009
1011
|
return;
|
|
1010
1012
|
}
|
|
1011
|
-
const authActionMatch = /^auth:([a-f0-9]+):(login_device|reload)$/.exec(event.data);
|
|
1013
|
+
const authActionMatch = /^auth:([a-f0-9]+):(login_device|reload|refresh_all)$/.exec(event.data);
|
|
1012
1014
|
if (authActionMatch) {
|
|
1013
1015
|
await this.handleAuthPanelActionCallback(event, authActionMatch[1], authActionMatch[2], locale);
|
|
1014
1016
|
return;
|
|
@@ -2604,7 +2606,8 @@ export class BridgeSessionCore {
|
|
|
2604
2606
|
&& this.pendingUserInputs.size === 0
|
|
2605
2607
|
&& this.pendingMcpElicitations.size === 0
|
|
2606
2608
|
&& this.pendingLoginsByScope.size === 0
|
|
2607
|
-
&& !this.authRotationInProgress
|
|
2609
|
+
&& !this.authRotationInProgress
|
|
2610
|
+
&& !this.authRefreshAllInProgress;
|
|
2608
2611
|
}
|
|
2609
2612
|
hasLocalBlockingActivity() {
|
|
2610
2613
|
return !this.isIdleForServiceUpdate();
|
|
@@ -3779,6 +3782,12 @@ export class BridgeSessionCore {
|
|
|
3779
3782
|
lines.push(...await this.buildCodexUsageStatusLines(locale));
|
|
3780
3783
|
await this.sendMessage(scopeId, lines.join('\n'));
|
|
3781
3784
|
}
|
|
3785
|
+
canRunGlobalAuthRefresh() {
|
|
3786
|
+
return this.isIdleForServiceUpdate()
|
|
3787
|
+
&& this.store.countPendingApprovals() === 0
|
|
3788
|
+
&& this.store.countPendingUserInputs() === 0
|
|
3789
|
+
&& (!this.coordinator?.canSelfUpdate || this.coordinator.canSelfUpdate());
|
|
3790
|
+
}
|
|
3782
3791
|
async handleSelfUpdateCommand(scopeId, locale) {
|
|
3783
3792
|
if (!this.selfUpdater) {
|
|
3784
3793
|
await this.sendMessage(scopeId, t(locale, 'update_unavailable'));
|
|
@@ -3860,6 +3869,18 @@ export class BridgeSessionCore {
|
|
|
3860
3869
|
await this.handleAuthReloadCommand(scopeId, locale);
|
|
3861
3870
|
return;
|
|
3862
3871
|
}
|
|
3872
|
+
if (action === 'refresh') {
|
|
3873
|
+
if (args[1]?.toLowerCase() === 'all') {
|
|
3874
|
+
await this.handleAuthRefreshAllCommand(scopeId, locale);
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
await this.sendMessage(scopeId, t(locale, 'usage_auth'));
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
if (action === 'refresh_all') {
|
|
3881
|
+
await this.handleAuthRefreshAllCommand(scopeId, locale);
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3863
3884
|
if (action === 'use') {
|
|
3864
3885
|
await this.handleAuthUseCommand(scopeId, locale, args.slice(1));
|
|
3865
3886
|
return;
|
|
@@ -3889,6 +3910,26 @@ export class BridgeSessionCore {
|
|
|
3889
3910
|
const messageId = await this.sendMessage(scopeId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null), authChoiceKeyboard(locale, record));
|
|
3890
3911
|
record.messageId = messageId;
|
|
3891
3912
|
}
|
|
3913
|
+
async handleAuthRefreshAllCommand(scopeId, locale) {
|
|
3914
|
+
if (!this.canRunGlobalAuthRefresh()) {
|
|
3915
|
+
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_blocked_active'));
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_starting'));
|
|
3919
|
+
const result = await this.refreshAllCodexAuthCandidates();
|
|
3920
|
+
const state = await this.listCodexAuthState();
|
|
3921
|
+
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
3922
|
+
const record = {
|
|
3923
|
+
localId: crypto.randomBytes(8).toString('hex'),
|
|
3924
|
+
chatId: scopeId,
|
|
3925
|
+
messageId: null,
|
|
3926
|
+
candidates: state.candidates,
|
|
3927
|
+
createdAt: Date.now(),
|
|
3928
|
+
};
|
|
3929
|
+
this.pendingAuthChoiceLists.set(record.localId, record);
|
|
3930
|
+
const messageId = await this.sendMessage(scopeId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null)}`, authChoiceKeyboard(locale, record));
|
|
3931
|
+
record.messageId = messageId;
|
|
3932
|
+
}
|
|
3892
3933
|
async handleAuthUseCommand(scopeId, locale, args) {
|
|
3893
3934
|
if (this.hasLocalBlockingActivity()) {
|
|
3894
3935
|
await this.sendMessage(scopeId, t(locale, 'auth_reload_blocked_active'));
|
|
@@ -4361,6 +4402,25 @@ export class BridgeSessionCore {
|
|
|
4361
4402
|
await this.handleLoginDeviceCommand(event.scopeId, locale);
|
|
4362
4403
|
return;
|
|
4363
4404
|
}
|
|
4405
|
+
if (action === 'refresh_all') {
|
|
4406
|
+
if (!this.canRunGlobalAuthRefresh()) {
|
|
4407
|
+
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_blocked_active'));
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4410
|
+
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_starting'));
|
|
4411
|
+
if (record.messageId !== null) {
|
|
4412
|
+
await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
|
|
4413
|
+
}
|
|
4414
|
+
const result = await this.refreshAllCodexAuthCandidates();
|
|
4415
|
+
const state = await this.listCodexAuthState();
|
|
4416
|
+
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
4417
|
+
record.candidates = state.candidates;
|
|
4418
|
+
record.createdAt = Date.now();
|
|
4419
|
+
if (record.messageId !== null) {
|
|
4420
|
+
await this.editMessage(event.scopeId, record.messageId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null)}`, authChoiceKeyboard(locale, record));
|
|
4421
|
+
}
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4364
4424
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_reload_restarting'));
|
|
4365
4425
|
await this.handleAuthReloadCommand(event.scopeId, locale);
|
|
4366
4426
|
}
|
|
@@ -4603,6 +4663,71 @@ export class BridgeSessionCore {
|
|
|
4603
4663
|
});
|
|
4604
4664
|
}
|
|
4605
4665
|
}
|
|
4666
|
+
async refreshAllCodexAuthCandidates() {
|
|
4667
|
+
if (this.authRefreshAllInProgress) {
|
|
4668
|
+
throw new UserFacingError('Auth refresh all is already running.');
|
|
4669
|
+
}
|
|
4670
|
+
const initialState = await this.listCodexAuthState();
|
|
4671
|
+
const result = { refreshed: [], skipped: [], failed: [] };
|
|
4672
|
+
if (initialState.candidates.length === 0) {
|
|
4673
|
+
return result;
|
|
4674
|
+
}
|
|
4675
|
+
const authStat = await fs.lstat(initialState.authPath).catch(() => null);
|
|
4676
|
+
const originalRegularAuth = authStat?.isFile()
|
|
4677
|
+
? await fs.readFile(initialState.authPath, 'utf8').catch(() => null)
|
|
4678
|
+
: null;
|
|
4679
|
+
let changedAuthTarget = false;
|
|
4680
|
+
this.authRefreshAllInProgress = true;
|
|
4681
|
+
try {
|
|
4682
|
+
for (const candidate of initialState.candidates) {
|
|
4683
|
+
const before = await readChatGptAuthMetadata(candidate.path);
|
|
4684
|
+
if (!before) {
|
|
4685
|
+
result.skipped.push(candidate.name);
|
|
4686
|
+
continue;
|
|
4687
|
+
}
|
|
4688
|
+
try {
|
|
4689
|
+
await pointCodexAuthAtTarget(initialState.authDir, initialState.authPath, candidate.path);
|
|
4690
|
+
changedAuthTarget = true;
|
|
4691
|
+
this.pendingTurnErrors.clear();
|
|
4692
|
+
this.attachedThreads.clear();
|
|
4693
|
+
await this.app.restart();
|
|
4694
|
+
await this.app.readAccount(true);
|
|
4695
|
+
const rateLimits = await this.app.readAccountRateLimits();
|
|
4696
|
+
const snapshot = selectCodexRateLimitSnapshot(rateLimits);
|
|
4697
|
+
if (!snapshot) {
|
|
4698
|
+
throw new Error('Codex did not return ChatGPT rate limits after refresh');
|
|
4699
|
+
}
|
|
4700
|
+
const after = await readChatGptAuthMetadata(candidate.path);
|
|
4701
|
+
if (!after || after.accountId !== before.accountId) {
|
|
4702
|
+
throw new Error('refreshed auth account id did not match the original candidate');
|
|
4703
|
+
}
|
|
4704
|
+
if (after.lastRefreshMs <= before.lastRefreshMs) {
|
|
4705
|
+
throw new Error('Codex did not advance last_refresh');
|
|
4706
|
+
}
|
|
4707
|
+
await this.recordCodexAuthQuotaSnapshot(candidate.name, after.accountId, snapshot);
|
|
4708
|
+
await this.syncCodexAuthCandidate(candidate.name);
|
|
4709
|
+
result.refreshed.push(candidate.name);
|
|
4710
|
+
}
|
|
4711
|
+
catch (error) {
|
|
4712
|
+
result.failed.push({ name: candidate.name, error: formatUserError(error) });
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
finally {
|
|
4717
|
+
try {
|
|
4718
|
+
if (changedAuthTarget) {
|
|
4719
|
+
await restoreCodexAuthTarget(initialState.authDir, initialState.authPath, initialState.currentTargetPath, originalRegularAuth);
|
|
4720
|
+
this.pendingTurnErrors.clear();
|
|
4721
|
+
this.attachedThreads.clear();
|
|
4722
|
+
await this.app.restart();
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
finally {
|
|
4726
|
+
this.authRefreshAllInProgress = false;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
return result;
|
|
4730
|
+
}
|
|
4606
4731
|
async buildNativeCollaborationMode(settings, cwd, modeOverride) {
|
|
4607
4732
|
const mode = resolveCollaborationMode(modeOverride === undefined ? settings?.collaborationMode ?? null : modeOverride);
|
|
4608
4733
|
try {
|
|
@@ -4807,11 +4932,7 @@ export class BridgeSessionCore {
|
|
|
4807
4932
|
}
|
|
4808
4933
|
const quota = authQuotaSnapshotFromRateLimit(snapshot, metadata?.accountId ?? null);
|
|
4809
4934
|
candidate.quota = quota;
|
|
4810
|
-
this.
|
|
4811
|
-
if (metadata?.accountId) {
|
|
4812
|
-
this.store.setCodexAuthQuotaSnapshot(this.authRuntimeId(), candidate.name, metadata.accountId, quota);
|
|
4813
|
-
}
|
|
4814
|
-
await this.writeCodexAuthQuotaSnapshots();
|
|
4935
|
+
await this.recordCodexAuthQuotaSnapshot(candidate.name, metadata?.accountId ?? null, snapshot);
|
|
4815
4936
|
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
4816
4937
|
await this.syncCodexAuthCandidate(candidate.name);
|
|
4817
4938
|
}
|
|
@@ -4840,6 +4961,14 @@ export class BridgeSessionCore {
|
|
|
4840
4961
|
candidate.quota = mergeCodexAuthQuotaSnapshots(candidate.quota, snapshotsByAccount.get(accountId) ?? null);
|
|
4841
4962
|
}
|
|
4842
4963
|
}
|
|
4964
|
+
async recordCodexAuthQuotaSnapshot(candidateName, accountId, snapshot) {
|
|
4965
|
+
const quota = authQuotaSnapshotFromRateLimit(snapshot, accountId);
|
|
4966
|
+
this.authQuotaSnapshots[candidateName] = quota;
|
|
4967
|
+
if (accountId) {
|
|
4968
|
+
this.store.setCodexAuthQuotaSnapshot(this.authRuntimeId(), candidateName, accountId, quota);
|
|
4969
|
+
}
|
|
4970
|
+
await this.writeCodexAuthQuotaSnapshots();
|
|
4971
|
+
}
|
|
4843
4972
|
async readCodexAuthCandidateAccountIds(candidates) {
|
|
4844
4973
|
const entries = await Promise.all(candidates.map(async (candidate) => {
|
|
4845
4974
|
const metadata = await readChatGptAuthMetadata(candidate.path);
|
|
@@ -7455,6 +7584,30 @@ async function pointCodexAuthAtTarget(authDir, authPath, targetPath) {
|
|
|
7455
7584
|
throw error;
|
|
7456
7585
|
}
|
|
7457
7586
|
}
|
|
7587
|
+
async function restoreCodexAuthTarget(authDir, authPath, targetPath, regularAuthContents) {
|
|
7588
|
+
if (regularAuthContents !== null) {
|
|
7589
|
+
await fs.mkdir(authDir, { recursive: true });
|
|
7590
|
+
const temporary = path.join(authDir, `.auth.json.${process.pid}.${Date.now()}.restore`);
|
|
7591
|
+
try {
|
|
7592
|
+
await fs.writeFile(temporary, regularAuthContents, { mode: 0o600 });
|
|
7593
|
+
await fs.rename(temporary, authPath);
|
|
7594
|
+
}
|
|
7595
|
+
catch (error) {
|
|
7596
|
+
await fs.unlink(temporary).catch(() => undefined);
|
|
7597
|
+
throw error;
|
|
7598
|
+
}
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7601
|
+
if (targetPath) {
|
|
7602
|
+
await pointCodexAuthAtTarget(authDir, authPath, targetPath);
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
await fs.unlink(authPath).catch((error) => {
|
|
7606
|
+
if (!isFileMissingError(error)) {
|
|
7607
|
+
throw error;
|
|
7608
|
+
}
|
|
7609
|
+
});
|
|
7610
|
+
}
|
|
7458
7611
|
async function switchCodexAuth(targetPath, explicitAuthDir = null) {
|
|
7459
7612
|
const state = await listCodexAuthState(new Set(), explicitAuthDir);
|
|
7460
7613
|
const candidate = state.candidates.find(entry => entry.path === targetPath);
|
|
@@ -7513,9 +7666,33 @@ function authChoiceKeyboard(locale, record) {
|
|
|
7513
7666
|
{ text: t(locale, 'button_permissions'), callback_data: 'nav:permissions' },
|
|
7514
7667
|
{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` },
|
|
7515
7668
|
]);
|
|
7516
|
-
rows.push([
|
|
7669
|
+
rows.push([
|
|
7670
|
+
{ text: t(locale, 'button_auth_reload'), callback_data: `auth:${record.localId}:reload` },
|
|
7671
|
+
{ text: t(locale, 'button_auth_refresh_all'), callback_data: `auth:${record.localId}:refresh_all` },
|
|
7672
|
+
]);
|
|
7517
7673
|
return rows;
|
|
7518
7674
|
}
|
|
7675
|
+
function formatAuthRefreshAllResult(locale, result) {
|
|
7676
|
+
const lines = [t(locale, 'auth_refresh_all_done', {
|
|
7677
|
+
refreshed: String(result.refreshed.length),
|
|
7678
|
+
skipped: String(result.skipped.length),
|
|
7679
|
+
failed: String(result.failed.length),
|
|
7680
|
+
})];
|
|
7681
|
+
if (result.refreshed.length > 0) {
|
|
7682
|
+
lines.push(t(locale, 'auth_refresh_all_refreshed', { value: result.refreshed.join(', ') }));
|
|
7683
|
+
}
|
|
7684
|
+
if (result.skipped.length > 0) {
|
|
7685
|
+
lines.push(t(locale, 'auth_refresh_all_skipped', { value: result.skipped.join(', ') }));
|
|
7686
|
+
}
|
|
7687
|
+
if (result.failed.length > 0) {
|
|
7688
|
+
const details = result.failed
|
|
7689
|
+
.slice(0, 5)
|
|
7690
|
+
.map((failure) => `${failure.name}: ${failure.error}`)
|
|
7691
|
+
.join('; ');
|
|
7692
|
+
lines.push(t(locale, 'auth_refresh_all_failed', { value: details }));
|
|
7693
|
+
}
|
|
7694
|
+
return lines.join('\n');
|
|
7695
|
+
}
|
|
7519
7696
|
function normalizeHelpUsageKey(name) {
|
|
7520
7697
|
const normalized = name.toLowerCase();
|
|
7521
7698
|
switch (normalized) {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -137,7 +137,7 @@ declare const MESSAGES: {
|
|
|
137
137
|
readonly auth_reload_restarting: "Restarting Codex app-server to reload auth...";
|
|
138
138
|
readonly auth_reload_done: "Codex app-server restarted. Current auth has been reloaded.";
|
|
139
139
|
readonly auth_reload_blocked_active: "Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.";
|
|
140
|
-
readonly usage_auth: "Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]";
|
|
140
|
+
readonly usage_auth: "Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]";
|
|
141
141
|
readonly usage_auth_add: "Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.";
|
|
142
142
|
readonly auth_list_title: "Codex auth files:";
|
|
143
143
|
readonly auth_bot: "Bot runtime: {value}";
|
|
@@ -158,6 +158,12 @@ declare const MESSAGES: {
|
|
|
158
158
|
readonly auth_switching: "Switching Codex auth: {from} -> {to}...";
|
|
159
159
|
readonly auth_switch_done: "Codex auth switched: {from} -> {to}.";
|
|
160
160
|
readonly auth_recovered_newer_candidate: "Recovered a newer same-account credential for {value} from another Codex home before reload.";
|
|
161
|
+
readonly auth_refresh_all_starting: "Refreshing all ChatGPT auth candidates. Every runtime must stay idle...";
|
|
162
|
+
readonly auth_refresh_all_blocked_active: "Cannot refresh all auth candidates while any runtime, approval, input, login, or auth mirror write is active. Wait or use /interrupt first.";
|
|
163
|
+
readonly auth_refresh_all_done: "Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.";
|
|
164
|
+
readonly auth_refresh_all_refreshed: "Refreshed: {value}";
|
|
165
|
+
readonly auth_refresh_all_skipped: "Skipped non-ChatGPT/invalid candidates: {value}";
|
|
166
|
+
readonly auth_refresh_all_failed: "Failed: {value}";
|
|
161
167
|
readonly auth_auto_switching: "Codex auth problem detected ({error}). Switching: {from} -> {to}...";
|
|
162
168
|
readonly auth_auto_done: "Auto-switched Codex auth: {from} -> {to}.";
|
|
163
169
|
readonly auth_auto_retrying: "Retrying the same request with the new auth...";
|
|
@@ -173,6 +179,7 @@ declare const MESSAGES: {
|
|
|
173
179
|
readonly auth_add_missing_file: "Login completed, but the new auth file was not created: {value}";
|
|
174
180
|
readonly button_login_device: "🔑 Login";
|
|
175
181
|
readonly button_auth_reload: "🔄 Reload auth";
|
|
182
|
+
readonly button_auth_refresh_all: "🔁 Refresh all";
|
|
176
183
|
readonly button_auth_enable: "✅";
|
|
177
184
|
readonly button_auth_disable: "⏸️";
|
|
178
185
|
readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
|
|
@@ -713,7 +720,7 @@ declare const MESSAGES: {
|
|
|
713
720
|
readonly auth_reload_restarting: "正在重启 Codex app-server 以重新读取 auth...";
|
|
714
721
|
readonly auth_reload_done: "Codex app-server 已重启,当前 auth 已重新读取。";
|
|
715
722
|
readonly auth_reload_blocked_active: "当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。";
|
|
716
|
-
readonly usage_auth: "用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|add <名称>]";
|
|
723
|
+
readonly usage_auth: "用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|refresh all|add <名称>]";
|
|
717
724
|
readonly usage_auth_add: "用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。";
|
|
718
725
|
readonly auth_list_title: "Codex auth 文件:";
|
|
719
726
|
readonly auth_bot: "Bot runtime:{value}";
|
|
@@ -734,6 +741,12 @@ declare const MESSAGES: {
|
|
|
734
741
|
readonly auth_switching: "正在切换 Codex auth:{from} -> {to}...";
|
|
735
742
|
readonly auth_switch_done: "Codex auth 已切换:{from} -> {to}。";
|
|
736
743
|
readonly auth_recovered_newer_candidate: "重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。";
|
|
744
|
+
readonly auth_refresh_all_starting: "正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...";
|
|
745
|
+
readonly auth_refresh_all_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。";
|
|
746
|
+
readonly auth_refresh_all_done: "全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。";
|
|
747
|
+
readonly auth_refresh_all_refreshed: "已刷新:{value}";
|
|
748
|
+
readonly auth_refresh_all_skipped: "已跳过非 ChatGPT/无效候选:{value}";
|
|
749
|
+
readonly auth_refresh_all_failed: "失败:{value}";
|
|
737
750
|
readonly auth_auto_switching: "检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...";
|
|
738
751
|
readonly auth_auto_done: "已自动切换 Codex auth:{from} -> {to}。";
|
|
739
752
|
readonly auth_auto_retrying: "正在用新的 auth 重试同一条请求...";
|
|
@@ -749,6 +762,7 @@ declare const MESSAGES: {
|
|
|
749
762
|
readonly auth_add_missing_file: "登录已完成,但没有创建新的 auth 文件:{value}";
|
|
750
763
|
readonly button_login_device: "🔑 设备登录";
|
|
751
764
|
readonly button_auth_reload: "🔄 重载 auth";
|
|
765
|
+
readonly button_auth_refresh_all: "🔁 刷新全部";
|
|
752
766
|
readonly button_auth_enable: "✅";
|
|
753
767
|
readonly button_auth_disable: "⏸️";
|
|
754
768
|
readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
|
package/dist/i18n.js
CHANGED
|
@@ -135,7 +135,7 @@ const MESSAGES = {
|
|
|
135
135
|
auth_reload_restarting: 'Restarting Codex app-server to reload auth...',
|
|
136
136
|
auth_reload_done: 'Codex app-server restarted. Current auth has been reloaded.',
|
|
137
137
|
auth_reload_blocked_active: 'Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.',
|
|
138
|
-
usage_auth: 'Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]',
|
|
138
|
+
usage_auth: 'Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]',
|
|
139
139
|
usage_auth_add: 'Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.',
|
|
140
140
|
auth_list_title: 'Codex auth files:',
|
|
141
141
|
auth_bot: 'Bot runtime: {value}',
|
|
@@ -156,6 +156,12 @@ const MESSAGES = {
|
|
|
156
156
|
auth_switching: 'Switching Codex auth: {from} -> {to}...',
|
|
157
157
|
auth_switch_done: 'Codex auth switched: {from} -> {to}.',
|
|
158
158
|
auth_recovered_newer_candidate: 'Recovered a newer same-account credential for {value} from another Codex home before reload.',
|
|
159
|
+
auth_refresh_all_starting: 'Refreshing all ChatGPT auth candidates. Every runtime must stay idle...',
|
|
160
|
+
auth_refresh_all_blocked_active: 'Cannot refresh all auth candidates while any runtime, approval, input, login, or auth mirror write is active. Wait or use /interrupt first.',
|
|
161
|
+
auth_refresh_all_done: 'Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.',
|
|
162
|
+
auth_refresh_all_refreshed: 'Refreshed: {value}',
|
|
163
|
+
auth_refresh_all_skipped: 'Skipped non-ChatGPT/invalid candidates: {value}',
|
|
164
|
+
auth_refresh_all_failed: 'Failed: {value}',
|
|
159
165
|
auth_auto_switching: 'Codex auth problem detected ({error}). Switching: {from} -> {to}...',
|
|
160
166
|
auth_auto_done: 'Auto-switched Codex auth: {from} -> {to}.',
|
|
161
167
|
auth_auto_retrying: 'Retrying the same request with the new auth...',
|
|
@@ -171,6 +177,7 @@ const MESSAGES = {
|
|
|
171
177
|
auth_add_missing_file: 'Login completed, but the new auth file was not created: {value}',
|
|
172
178
|
button_login_device: '🔑 Login',
|
|
173
179
|
button_auth_reload: '🔄 Reload auth',
|
|
180
|
+
button_auth_refresh_all: '🔁 Refresh all',
|
|
174
181
|
button_auth_enable: '✅',
|
|
175
182
|
button_auth_disable: '⏸️',
|
|
176
183
|
another_turn_running: 'Another turn is already running. Use /interrupt, /takeover, /queue, or wait.',
|
|
@@ -711,7 +718,7 @@ const MESSAGES = {
|
|
|
711
718
|
auth_reload_restarting: '正在重启 Codex app-server 以重新读取 auth...',
|
|
712
719
|
auth_reload_done: 'Codex app-server 已重启,当前 auth 已重新读取。',
|
|
713
720
|
auth_reload_blocked_active: '当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。',
|
|
714
|
-
usage_auth: '用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|add <名称>]',
|
|
721
|
+
usage_auth: '用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|refresh all|add <名称>]',
|
|
715
722
|
usage_auth_add: '用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。',
|
|
716
723
|
auth_list_title: 'Codex auth 文件:',
|
|
717
724
|
auth_bot: 'Bot runtime:{value}',
|
|
@@ -732,6 +739,12 @@ const MESSAGES = {
|
|
|
732
739
|
auth_switching: '正在切换 Codex auth:{from} -> {to}...',
|
|
733
740
|
auth_switch_done: 'Codex auth 已切换:{from} -> {to}。',
|
|
734
741
|
auth_recovered_newer_candidate: '重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。',
|
|
742
|
+
auth_refresh_all_starting: '正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...',
|
|
743
|
+
auth_refresh_all_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。',
|
|
744
|
+
auth_refresh_all_done: '全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。',
|
|
745
|
+
auth_refresh_all_refreshed: '已刷新:{value}',
|
|
746
|
+
auth_refresh_all_skipped: '已跳过非 ChatGPT/无效候选:{value}',
|
|
747
|
+
auth_refresh_all_failed: '失败:{value}',
|
|
735
748
|
auth_auto_switching: '检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...',
|
|
736
749
|
auth_auto_done: '已自动切换 Codex auth:{from} -> {to}。',
|
|
737
750
|
auth_auto_retrying: '正在用新的 auth 重试同一条请求...',
|
|
@@ -747,6 +760,7 @@ const MESSAGES = {
|
|
|
747
760
|
auth_add_missing_file: '登录已完成,但没有创建新的 auth 文件:{value}',
|
|
748
761
|
button_login_device: '🔑 设备登录',
|
|
749
762
|
button_auth_reload: '🔄 重载 auth',
|
|
763
|
+
button_auth_refresh_all: '🔁 刷新全部',
|
|
750
764
|
button_auth_enable: '✅',
|
|
751
765
|
button_auth_disable: '⏸️',
|
|
752
766
|
another_turn_running: '已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。',
|
package/docs/user-manual.md
CHANGED
|
@@ -389,7 +389,7 @@ If the login is cancelled or fails, FoxClaw tries to restore the previous auth t
|
|
|
389
389
|
|
|
390
390
|
### 6.3 The `/auth` Panel
|
|
391
391
|
|
|
392
|
-
`/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, and
|
|
392
|
+
`/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, reload, and Refresh all. In multi-bot mode the panel names the `@botname` runtime being managed, because private chats, groups, and topics on one bot share that bot's current auth. The `5h|7d` numbers before each filename are the last recorded remaining percentages for the two quota windows; the current auth is refreshed when the panel opens, while other candidates are not switched merely to query quota. When multiple bot runtimes have recently used the same ChatGPT account, FoxClaw combines their cached quota snapshots by verified account ID, so one bot's `/auth` panel can show quota information learned by another bot without mixing different accounts.
|
|
393
393
|
|
|
394
394
|
Approximation:
|
|
395
395
|
|
|
@@ -405,10 +405,10 @@ Quota remaining: 5h|7d|auth
|
|
|
405
405
|
[✅ 20|25|auth.json_personal] [✅]
|
|
406
406
|
[🔐 --|--|auth.json_team] [✅]
|
|
407
407
|
[🛡️ Access] [🔑 Login]
|
|
408
|
-
[🔄 Reload auth]
|
|
408
|
+
[🔄 Reload auth] [🔁 Refresh all]
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
The right-side `✅` / `⏸️` button shows the current state. Tapping it toggles enabled/disabled, and the refreshed list shows the new state. Tapping a candidate switches auth, restarts that runtime, and refreshes the same panel with its buttons intact so you can switch again immediately. `--|--` means no quota snapshot has been observed for that candidate yet.
|
|
411
|
+
The right-side `✅` / `⏸️` button shows the current state. Tapping it toggles enabled/disabled, and the refreshed list shows the new state. Tapping a candidate switches auth, restarts that runtime, and refreshes the same panel with its buttons intact so you can switch again immediately. `Refresh all` is a maintenance action: it is allowed only when every Telegram runtime, the Weixin runtime, approvals, inputs, logins, and auth mirroring are idle. It then visits every ChatGPT candidate in the panel, asks Codex to force-refresh tokens with `account/read refreshToken=true`, verifies the result through the usage endpoint, mirrors successful candidates, restores the original current auth, and refreshes the panel with a summary. `--|--` means no quota snapshot has been observed for that candidate yet.
|
|
412
412
|
|
|
413
413
|
Equivalent commands:
|
|
414
414
|
|
|
@@ -417,6 +417,7 @@ Equivalent commands:
|
|
|
417
417
|
- `/auth enable <n>`: let candidate n participate in auto-rotation.
|
|
418
418
|
- `/auth disable <n>`: skip candidate n during auto-rotation.
|
|
419
419
|
- `/auth reload` or `/auth_reload`: restart app-server and reload the current `auth.json`.
|
|
420
|
+
- `/auth refresh all`: same as the panel's Refresh all button.
|
|
420
421
|
|
|
421
422
|
If the requesting bot runtime has active turns, pending approvals, pending user inputs, or MCP elicitations, FoxClaw refuses manual auth switching to avoid changing accounts mid-request; another idle bot is unaffected.
|
|
422
423
|
|
package/docs/zh/user-manual.md
CHANGED
|
@@ -389,7 +389,7 @@ cp -L ~/.codex/auth.json ~/.codex/auth.json_personal
|
|
|
389
389
|
|
|
390
390
|
### 6.3 `/auth` 面板
|
|
391
391
|
|
|
392
|
-
`/auth` 会列出候选账号、当前账号和 auth
|
|
392
|
+
`/auth` 会列出候选账号、当前账号和 auth 目录,并提供按钮切换、禁用、登录、重载和刷新全部。多 bot 模式中,面板顶部还会显示当前正在管理的 `@botname`,因为该 bot 内的私聊、群聊和话题共享同一个当前 auth。每个候选名前的 `5h|7d` 数字表示上次记录到的两个额度窗口剩余百分比;当前 auth 会在打开面板时刷新,其他候选不会为了查询额度被自动切换。如果多个 bot runtime 最近使用过同一个 ChatGPT 账号,FoxClaw 会按已验证的账号 ID 合并它们缓存到的额度快照,因此一个 bot 的 `/auth` 面板可以显示另一个 bot 掌握到的额度信息,同时不会把不同账号混在一起。
|
|
393
393
|
|
|
394
394
|
示意:
|
|
395
395
|
|
|
@@ -405,10 +405,10 @@ Candidates: 2
|
|
|
405
405
|
[✅ 20|25|auth.json_personal] [✅]
|
|
406
406
|
[🔐 --|--|auth.json_team] [✅]
|
|
407
407
|
[🛡️ Access] [🔑 设备登录]
|
|
408
|
-
[🔄 Reload auth]
|
|
408
|
+
[🔄 Reload auth] [🔁 刷新全部]
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
右侧 `✅` / `⏸️` 表示当前状态。点一下会切换启用/禁用,列表刷新后图标会随状态变化。点击候选会切换 auth、重启对应 runtime
|
|
411
|
+
右侧 `✅` / `⏸️` 表示当前状态。点一下会切换启用/禁用,列表刷新后图标会随状态变化。点击候选会切换 auth、重启对应 runtime,并在原消息上刷新面板且保留按钮,因此可以立即连续切换。`刷新全部` 是维护操作:只有所有 Telegram runtime、微信 runtime、审批、待输入、登录流程和 auth 镜像写入都空闲时才允许执行。它会逐个访问面板里的 ChatGPT 候选,让 Codex 通过 `account/read refreshToken=true` 强制刷新 token,再用 usage 接口验证,成功后镜像到其他 bot home,最后恢复原本的当前 auth 并刷新面板摘要。`--|--` 表示该候选还没有额度历史快照。
|
|
412
412
|
|
|
413
413
|
命令等价用法:
|
|
414
414
|
|
|
@@ -417,6 +417,7 @@ Candidates: 2
|
|
|
417
417
|
- `/auth enable <n>`:让第 n 个候选参与自动轮转。
|
|
418
418
|
- `/auth disable <n>`:禁用第 n 个候选,自动轮转会跳过它。
|
|
419
419
|
- `/auth reload` 或 `/auth_reload`:重启 app-server,重新加载当前 `auth.json`。
|
|
420
|
+
- `/auth refresh all`:等同于面板上的“刷新全部”按钮。
|
|
420
421
|
|
|
421
422
|
切换 auth 时,如果当前 bot runtime 还有活跃 turn、待审批、待用户输入或 MCP elicitation,FoxClaw 会先拒绝切换,避免中途换号破坏正在进行的请求;另一个空闲 bot 不受影响。
|
|
422
423
|
|
package/package.json
CHANGED
package/skills/foxclaw/SKILL.md
CHANGED
|
@@ -67,6 +67,7 @@ Telegram behavior:
|
|
|
67
67
|
- when `TG_BOT_TOKENS` contains multiple bots, one FoxClaw service starts independent Codex app-servers and auth selections per bot
|
|
68
68
|
- isolated Telegram runtimes use independent `CODEX_HOME` directories and file-backed credentials; they do not share Codex sessions
|
|
69
69
|
- refreshed ChatGPT candidates are mirrored only after online usage-endpoint validation; before auth switch or reload, a runtime restores any newer same-account credential found in another Codex home
|
|
70
|
+
- `/auth` includes a Refresh all panel button; it is a global-idle maintenance action that force-refreshes every ChatGPT candidate through Codex `account/read refreshToken=true`, validates usage, mirrors successful candidates, and restores the original current auth
|
|
70
71
|
- if `TG_BOT_TOKEN` is also set to one exact token from `TG_BOT_TOKENS`, that bot uses the default/shared-terminal runtime instead of an isolated Telegram home
|
|
71
72
|
- if Weixin is enabled alongside multiple Telegram bots, it remains on the default Codex runtime instead of borrowing a Telegram bot runtime
|
|
72
73
|
- in a group with multiple configured bots, address a bot by mention, reply, or suffixed command such as `/status@botname`
|
|
@@ -173,6 +174,8 @@ Do not describe the setup as "done" until this smoke test has either passed or b
|
|
|
173
174
|
|
|
174
175
|
Use this checklist when the user asks for standard closing actions, release wrap-up, local install updates, npm publish, or says things like "收尾动作", "中文 commit msg", "中英双语 commit msg", "push", "npm publish", or "本地安装更新".
|
|
175
176
|
|
|
177
|
+
When the user says development is complete, asks to "收尾", or asks to push/publish, treat that as authorization to finish the whole release path without asking for another confirmation. After validation passes, commit the scoped changes, push the branch, publish through the configured release flow, and verify the published result. Stop and ask only when a real external decision is required, such as npm/GitHub web auth, missing credentials, failed checks, merge conflicts, an already-published target version that requires choosing a new version, or unrelated local changes that would be staged.
|
|
178
|
+
|
|
176
179
|
1. Inspect scope before staging:
|
|
177
180
|
- `git status -sb`
|
|
178
181
|
- `git diff --stat`
|
|
@@ -7,6 +7,8 @@ description: Publish npm packages safely, especially packages that require npm 2
|
|
|
7
7
|
|
|
8
8
|
Use this skill to publish an npm package from a repo. Prefer CI trusted publishing when the repository has a publish workflow. Prefer the normal no-2FA publish path first only when CI publishing is not available. Fall back to npm web-auth only when npm explicitly prompts for it.
|
|
9
9
|
|
|
10
|
+
If the user asks to release, publish, "收尾", "push publish", or otherwise finish a completed package change, proceed through verification, commit, push, publish, and post-publish verification without asking for a second confirmation. Pause only for real blockers: missing credentials, failed checks, unrelated changes that would be staged, merge conflicts, an already-published target version that needs a version choice, or npm/GitHub web-auth that requires the user to click/confirm an external page.
|
|
11
|
+
|
|
10
12
|
## Release Checklist
|
|
11
13
|
|
|
12
14
|
1. Sync first when the user asks for it:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "NPM Publish"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Publish this npm package
|
|
3
|
+
short_description: "Verify, push, and publish npm packages"
|
|
4
|
+
default_prompt: "Publish this npm package: verify it, commit and push scoped release changes, run the configured publish flow, and only pause for real auth or failure blockers."
|