cliclaw 1.0.22 → 1.0.24

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/bin/cli.js CHANGED
@@ -189,19 +189,18 @@ async function runConfigWizard() {
189
189
  console.log(`${c.gray} Leave empty to configure later.\n${c.reset}`)
190
190
  const forumId = await prompt('FORUM_GROUP_ID (Enter to skip)', existing.FORUM_GROUP_ID || '')
191
191
 
192
- console.log(`\n${c.bold}Permission mode${c.reset}`)
193
- console.log(` ${c.bold}1) auto${c.reset} — always skip prompts ${c.cyan}(recommended)${c.reset}`)
194
- console.log(` ${c.bold}2) session${c.reset} ask per /new session`)
195
- console.log(` ${c.bold}3) ask${c.reset} — prefix message with ! to allow\n`)
196
- const permChoice = await prompt('Choose [1/2/3]',
197
- existing.PERMISSION_MODE === 'session' ? '2' : existing.PERMISSION_MODE === 'ask' ? '3' : '1')
198
- const permMode = permChoice === '2' ? 'session' : permChoice === '3' ? 'ask' : 'auto'
199
- ok(`Permission mode: ${permMode}`)
192
+ console.log(`\n${c.bold}Codex — aprovação de comandos${c.reset}`)
193
+ console.log(` ${c.bold}1) ask${c.reset} — pergunta antes de cada comando ${c.cyan}(recomendado)${c.reset}`)
194
+ console.log(` ${c.bold}2) allow${c.reset} executa tudo automaticamente\n`)
195
+ const approvalChoice = await prompt('Escolha [1/2]',
196
+ existing.CODEX_APPROVAL === 'allow' ? '2' : '1')
197
+ const codexApproval = approvalChoice === '2' ? 'allow' : 'ask'
198
+ ok(`Codex approval: ${codexApproval}`)
200
199
 
201
200
  const dataDir = existing.DATA_DIR || path.join(USER_DIR, 'data')
202
201
  let envContent = `TELEGRAM_BOT_TOKEN=${token}\n`
203
202
  if (forumId) envContent += `FORUM_GROUP_ID=${forumId}\n`
204
- envContent += `PERMISSION_MODE=${permMode}\n`
203
+ envContent += `CODEX_APPROVAL=${codexApproval}\n`
205
204
  envContent += `DATA_DIR=${dataDir}\n`
206
205
  fs.writeFileSync(envFile, envContent, 'utf8')
207
206
  ok(`.env saved → ${envFile}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cliclaw",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Telegram bot bridging AI CLIs (Claude Code, Codex) to Forum Topics",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -14,8 +14,8 @@ const BASE_ENV = {
14
14
  LANG: 'en_US.UTF-8',
15
15
  }
16
16
 
17
- const SESSION_TTL_MS = 30 * 60 * 1000 // kill idle processes after 30 min
18
- const RESPONSE_TIMEOUT_MS = 120_000 // 2 min per message
17
+ const SESSION_TTL_MS = 30 * 60 * 1000 // kill idle processes after 30 min
18
+ const RESPONSE_TIMEOUT_MS = 10 * 60_000 // 10 min allows time for user approval clicks
19
19
 
20
20
  interface PendingReq {
21
21
  resolve: (r: { text: string; usage?: TokenUsage }) => void
@@ -199,11 +199,14 @@ function getOrStart(appSessionId: string): ProtoSession {
199
199
 
200
200
  // ─── public API ──────────────────────────────────────────────────────────────
201
201
  export async function askCodex(
202
- session: { id: string; codexThreadId?: string },
203
- userMessage: string,
204
- _onNewThreadId?: (id: string) => void
202
+ session: { id: string; codexThreadId?: string },
203
+ userMessage: string,
204
+ _onNewThreadId?: (id: string) => void,
205
+ approvalHandler?: (callId: string, commandStr: string) => Promise<boolean>
205
206
  ): Promise<{ text: string; usage?: TokenUsage }> {
206
207
  const ps = getOrStart(session.id)
208
+ // Update handler every call (chat context may have changed)
209
+ if (approvalHandler !== undefined) ps.approvalHandler = approvalHandler
207
210
  const msgId = randomUUID()
208
211
 
209
212
  return new Promise((resolve, reject) => {
@@ -237,15 +240,6 @@ export async function askCodex(
237
240
  )
238
241
  }
239
242
 
240
- /** Attach a per-session handler that is called whenever codex wants to run a command. */
241
- export function setCodexApprovalHandler(
242
- appSessionId: string,
243
- handler: (callId: string, commandStr: string) => Promise<boolean>
244
- ): void {
245
- const ps = protoSessions.get(appSessionId)
246
- if (ps) ps.approvalHandler = handler
247
- }
248
-
249
243
  export function killCodexSession(appSessionId: string) {
250
244
  const ps = protoSessions.get(appSessionId)
251
245
  if (ps) {
package/src/config.ts CHANGED
@@ -5,6 +5,7 @@ import { homedir } from 'os'
5
5
 
6
6
  export type AgentName = 'claude' | 'codex'
7
7
  export type PermissionMode = 'auto' | 'session' | 'ask'
8
+ export type CodexApproval = 'ask' | 'allow'
8
9
 
9
10
  export interface Config {
10
11
  TELEGRAM_BOT_TOKEN: string
@@ -12,6 +13,7 @@ export interface Config {
12
13
  FORUM_GROUP_ID: string
13
14
  TELEGRAM_ADMIN_IDS: string[]
14
15
  PERMISSION_MODE: PermissionMode
16
+ CODEX_APPROVAL: CodexApproval
15
17
  availableAgents: AgentName[]
16
18
  }
17
19
 
@@ -59,6 +61,9 @@ export function loadConfig(): Config {
59
61
  rawMode === 'session' ? 'session' :
60
62
  rawMode === 'ask' ? 'ask' : 'auto'
61
63
 
64
+ const rawApproval = (process.env.CODEX_APPROVAL || 'ask').toLowerCase()
65
+ const codexApproval: CodexApproval = rawApproval === 'allow' ? 'allow' : 'ask'
66
+
62
67
  const config: Config = {
63
68
  TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN || '',
64
69
  DATA_DIR: process.env.DATA_DIR || join(USER_DIR, 'data'),
@@ -66,6 +71,7 @@ export function loadConfig(): Config {
66
71
  TELEGRAM_ADMIN_IDS: (process.env.TELEGRAM_ADMIN_IDS || '')
67
72
  .split(',').map(id => id.trim()).filter(Boolean),
68
73
  PERMISSION_MODE: permMode,
74
+ CODEX_APPROVAL: codexApproval,
69
75
  availableAgents: checkAgents(),
70
76
  }
71
77
 
@@ -3,7 +3,7 @@ import type { Bot, Context, Api } from 'grammy'
3
3
  import type { Storage, Session } from '../storage'
4
4
  import type { Config } from '../config'
5
5
  import { askClaude, type TokenUsage } from '../agents/claude'
6
- import { askCodex, setCodexApprovalHandler } from '../agents/codex'
6
+ import { askCodex } from '../agents/codex'
7
7
  import { mdToTg, splitHtml } from '../utils/markdown'
8
8
  import { getLang, t } from '../i18n'
9
9
  import { registerApproval, respondApproval } from './approvals'
@@ -102,26 +102,24 @@ export function registerMessageHandler(bot: Bot<Context>, storage: Storage, conf
102
102
 
103
103
  storage.addMessage(chatId, session.id, 'user', text)
104
104
 
105
- // ── Codex approval handler ──────────────────────────────────────────────
106
- if (session.model === 'codex') {
107
- setCodexApprovalHandler(session.id, (callId, cmdStr) => {
108
- const approvalId = randomUUID()
109
- const label = `🔧 <b>Codex quer executar:</b>\n<code>${escapeHtml(cmdStr)}</code>`
110
- ctx.api.sendMessage(chatId, label, {
111
- ...replyOpts,
112
- parse_mode: 'HTML',
113
- reply_markup: {
114
- inline_keyboard: [[
115
- { text: '✅ Aprovar', callback_data: `capprove:${approvalId}` },
116
- { text: '❌ Negar', callback_data: `cdeny:${approvalId}` },
117
- ]],
118
- },
119
- }).catch(() => {})
120
- return new Promise<boolean>((resolve) => {
121
- registerApproval(approvalId, resolve)
122
- })
123
- })
124
- }
105
+ // ── build codex approval handler (only if CODEX_APPROVAL=ask) ──────────
106
+ const codexApprovalHandler = (session.model === 'codex' && config.CODEX_APPROVAL === 'ask')
107
+ ? (callId: string, cmdStr: string) => {
108
+ const approvalId = randomUUID()
109
+ ctx.api.sendMessage(chatId,
110
+ `🔧 <b>Codex quer executar:</b>\n<code>${escapeHtml(cmdStr)}</code>`,
111
+ {
112
+ ...replyOpts,
113
+ parse_mode: 'HTML',
114
+ reply_markup: { inline_keyboard: [[
115
+ { text: '✅ Aprovar', callback_data: `capprove:${approvalId}` },
116
+ { text: '❌ Negar', callback_data: `cdeny:${approvalId}` },
117
+ ]] },
118
+ }
119
+ ).catch(() => {})
120
+ return new Promise<boolean>((resolve) => registerApproval(approvalId, resolve))
121
+ }
122
+ : undefined
125
123
 
126
124
  let response: string
127
125
  try {
@@ -131,7 +129,8 @@ export function registerMessageHandler(bot: Bot<Context>, storage: Storage, conf
131
129
  (id) => storage.setClaudeSessionId(chatId, session!.id, id))
132
130
  } else {
133
131
  result = await askCodex(session, text,
134
- (id) => storage.setCodexThreadId(chatId, session!.id, id))
132
+ (id) => storage.setCodexThreadId(chatId, session!.id, id),
133
+ codexApprovalHandler)
135
134
  }
136
135
  response = (isFirstMessage && result.usage) ? result.text + formatUsage(result.usage) : result.text
137
136
  } catch (err: any) {