@openacp/cli 0.5.3 → 0.6.1

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 (100) hide show
  1. package/README.md +51 -16
  2. package/dist/action-detect-6M5GCGAU.js +15 -0
  3. package/dist/admin-IKPS5PFC.js +16 -0
  4. package/dist/agents-55NX3DHM.js +14 -0
  5. package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
  6. package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
  7. package/dist/{chunk-ECBD5I5R.js → chunk-2KJC3ILH.js} +123 -16
  8. package/dist/chunk-2KJC3ILH.js.map +1 -0
  9. package/dist/{chunk-2Z2XPUD5.js → chunk-4LFDEW22.js} +53 -5
  10. package/dist/chunk-4LFDEW22.js.map +1 -0
  11. package/dist/{chunk-Z46LGZ7R.js → chunk-4TR5Y3MP.js} +18 -1
  12. package/dist/chunk-4TR5Y3MP.js.map +1 -0
  13. package/dist/chunk-7G5QKLLF.js +105 -0
  14. package/dist/chunk-7G5QKLLF.js.map +1 -0
  15. package/dist/{chunk-IURZ4QHG.js → chunk-7QJS2XBD.js} +2 -1
  16. package/dist/chunk-7QJS2XBD.js.map +1 -0
  17. package/dist/chunk-AKIU4JBF.js +145 -0
  18. package/dist/chunk-AKIU4JBF.js.map +1 -0
  19. package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
  20. package/dist/chunk-EVFJW45N.js.map +1 -0
  21. package/dist/chunk-GINCOFNW.js +134 -0
  22. package/dist/chunk-GINCOFNW.js.map +1 -0
  23. package/dist/chunk-H7ZMPBZC.js +203 -0
  24. package/dist/chunk-H7ZMPBZC.js.map +1 -0
  25. package/dist/chunk-I7WC6E5S.js +71 -0
  26. package/dist/chunk-I7WC6E5S.js.map +1 -0
  27. package/dist/{chunk-6DAZSKE5.js → chunk-IMILOCR5.js} +2 -2
  28. package/dist/chunk-LGQYTK55.js +442 -0
  29. package/dist/chunk-LGQYTK55.js.map +1 -0
  30. package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
  31. package/dist/chunk-PMGNLNSH.js.map +1 -0
  32. package/dist/{chunk-LCJIPE5S.js → chunk-R3UJUOXI.js} +889 -591
  33. package/dist/chunk-R3UJUOXI.js.map +1 -0
  34. package/dist/chunk-SM3G6UAX.js +122 -0
  35. package/dist/chunk-SM3G6UAX.js.map +1 -0
  36. package/dist/chunk-T22OLSET.js +265 -0
  37. package/dist/chunk-T22OLSET.js.map +1 -0
  38. package/dist/chunk-THBR6OXH.js +62 -0
  39. package/dist/chunk-THBR6OXH.js.map +1 -0
  40. package/dist/{chunk-5KYLXEG3.js → chunk-TOZQ3JFN.js} +52 -9
  41. package/dist/chunk-TOZQ3JFN.js.map +1 -0
  42. package/dist/{chunk-IQIPQTQT.js → chunk-UB7XUO7C.js} +171 -26
  43. package/dist/chunk-UB7XUO7C.js.map +1 -0
  44. package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
  45. package/dist/chunk-W3EYKZNQ.js.map +1 -0
  46. package/dist/{chunk-K53OZH5Y.js → chunk-ZCHNAM3B.js} +76 -2
  47. package/dist/chunk-ZCHNAM3B.js.map +1 -0
  48. package/dist/cli.js +30 -29
  49. package/dist/cli.js.map +1 -1
  50. package/dist/{config-OH26EIWN.js → config-AK2W3E67.js} +2 -2
  51. package/dist/config-editor-VIA7A72X.js +12 -0
  52. package/dist/{config-registry-SNKA2EH2.js → config-registry-QQOJ2GQP.js} +2 -2
  53. package/dist/{daemon-VKCONJUY.js → daemon-G27YZUWB.js} +3 -3
  54. package/dist/discord-2DKRH45T.js +2044 -0
  55. package/dist/discord-2DKRH45T.js.map +1 -0
  56. package/dist/doctor-AN6AZ3PF.js +9 -0
  57. package/dist/doctor-CHCYUTV5.js +14 -0
  58. package/dist/doctor-CHCYUTV5.js.map +1 -0
  59. package/dist/index.d.ts +331 -6
  60. package/dist/index.js +21 -11
  61. package/dist/{main-NEYPQHB4.js → main-56SPFYW4.js} +32 -24
  62. package/dist/main-56SPFYW4.js.map +1 -0
  63. package/dist/{menu-J5YVH665.js → menu-XR2GET2B.js} +2 -2
  64. package/dist/menu-XR2GET2B.js.map +1 -0
  65. package/dist/new-session-DRRP2J7E.js +16 -0
  66. package/dist/new-session-DRRP2J7E.js.map +1 -0
  67. package/dist/session-FVFLBREJ.js +19 -0
  68. package/dist/session-FVFLBREJ.js.map +1 -0
  69. package/dist/settings-LPOLJ6SA.js +12 -0
  70. package/dist/settings-LPOLJ6SA.js.map +1 -0
  71. package/dist/{setup-ZCWGOEAH.js → setup-IPWJCIJM.js} +9 -5
  72. package/dist/setup-IPWJCIJM.js.map +1 -0
  73. package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
  74. package/dist/version-ALWGGVKM.js.map +1 -0
  75. package/package.json +2 -1
  76. package/dist/chunk-2Z2XPUD5.js.map +0 -1
  77. package/dist/chunk-5KYLXEG3.js.map +0 -1
  78. package/dist/chunk-ECBD5I5R.js.map +0 -1
  79. package/dist/chunk-IQIPQTQT.js.map +0 -1
  80. package/dist/chunk-IURZ4QHG.js.map +0 -1
  81. package/dist/chunk-K53OZH5Y.js.map +0 -1
  82. package/dist/chunk-KSIQZC3J.js.map +0 -1
  83. package/dist/chunk-LCJIPE5S.js.map +0 -1
  84. package/dist/chunk-OORPX73T.js.map +0 -1
  85. package/dist/chunk-X6LLG7XN.js.map +0 -1
  86. package/dist/chunk-Z46LGZ7R.js.map +0 -1
  87. package/dist/config-editor-5TICUK3K.js +0 -12
  88. package/dist/doctor-X6UCE7GQ.js +0 -9
  89. package/dist/main-NEYPQHB4.js.map +0 -1
  90. /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
  91. /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
  92. /package/dist/{config-OH26EIWN.js.map → agents-55NX3DHM.js.map} +0 -0
  93. /package/dist/{config-editor-5TICUK3K.js.map → api-client-BH2JFHQW.js.map} +0 -0
  94. /package/dist/{config-registry-SNKA2EH2.js.map → autostart-A7JRU4WJ.js.map} +0 -0
  95. /package/dist/{chunk-6DAZSKE5.js.map → chunk-IMILOCR5.js.map} +0 -0
  96. /package/dist/{daemon-VKCONJUY.js.map → config-AK2W3E67.js.map} +0 -0
  97. /package/dist/{doctor-X6UCE7GQ.js.map → config-editor-VIA7A72X.js.map} +0 -0
  98. /package/dist/{menu-J5YVH665.js.map → config-registry-QQOJ2GQP.js.map} +0 -0
  99. /package/dist/{setup-ZCWGOEAH.js.map → daemon-G27YZUWB.js.map} +0 -0
  100. /package/dist/{version-VC5CPXBX.js.map → doctor-AN6AZ3PF.js.map} +0 -0
@@ -134,6 +134,27 @@ var TunnelSchema = z.object({
134
134
  storeTtlMinutes: z.number().default(60),
135
135
  auth: TunnelAuthSchema
136
136
  }).default({});
137
+ var UsageSchema = z.object({
138
+ enabled: z.boolean().default(true),
139
+ monthlyBudget: z.number().optional(),
140
+ warningThreshold: z.number().default(0.8),
141
+ currency: z.string().default("USD"),
142
+ retentionDays: z.number().default(90)
143
+ }).default({});
144
+ var SpeechProviderSchema = z.object({
145
+ apiKey: z.string().min(1),
146
+ model: z.string().optional()
147
+ }).passthrough();
148
+ var SpeechSchema = z.object({
149
+ stt: z.object({
150
+ provider: z.string().nullable().default(null),
151
+ providers: z.record(SpeechProviderSchema).default({})
152
+ }).default({}),
153
+ tts: z.object({
154
+ provider: z.string().nullable().default(null),
155
+ providers: z.record(SpeechProviderSchema).default({})
156
+ }).default({})
157
+ }).optional().default({});
137
158
  var ConfigSchema = z.object({
138
159
  channels: z.record(z.string(), BaseChannelSchema),
139
160
  agents: z.record(z.string(), AgentSchema).optional().default({}),
@@ -157,10 +178,12 @@ var ConfigSchema = z.object({
157
178
  ttlDays: z.number().default(30)
158
179
  }).default({}),
159
180
  tunnel: TunnelSchema,
181
+ usage: UsageSchema,
160
182
  integrations: z.record(z.string(), z.object({
161
183
  installed: z.boolean(),
162
184
  installedAt: z.string().optional()
163
- })).default({})
185
+ })).default({}),
186
+ speech: SpeechSchema
164
187
  });
165
188
  function expandHome(p) {
166
189
  if (p.startsWith("~")) {
@@ -176,6 +199,14 @@ var DEFAULT_CONFIG = {
176
199
  chatId: 0,
177
200
  notificationTopicId: null,
178
201
  assistantTopicId: null
202
+ },
203
+ discord: {
204
+ enabled: false,
205
+ botToken: "YOUR_DISCORD_BOT_TOKEN_HERE",
206
+ guildId: "",
207
+ forumChannelId: null,
208
+ notificationChannelId: null,
209
+ assistantThreadId: null
179
210
  }
180
211
  },
181
212
  agents: {
@@ -197,7 +228,8 @@ var DEFAULT_CONFIG = {
197
228
  options: {},
198
229
  storeTtlMinutes: 60,
199
230
  auth: { enabled: false }
200
- }
231
+ },
232
+ usage: {}
201
233
  };
202
234
  var ConfigManager = class extends EventEmitter {
203
235
  config;
@@ -216,7 +248,7 @@ var ConfigManager = class extends EventEmitter {
216
248
  );
217
249
  log2.info({ configPath: this.configPath }, "Config created");
218
250
  log2.info(
219
- "Please edit it with your Telegram bot token and chat ID, then restart."
251
+ "Please edit it with your channel credentials (Telegram bot token, Discord bot token, etc.), then restart."
220
252
  );
221
253
  process.exit(1);
222
254
  }
@@ -252,7 +284,7 @@ var ConfigManager = class extends EventEmitter {
252
284
  this.config = result.data;
253
285
  }
254
286
  if (changePath) {
255
- const { getConfigValue } = await import("./config-registry-SNKA2EH2.js");
287
+ const { getConfigValue } = await import("./config-registry-QQOJ2GQP.js");
256
288
  const value = getConfigValue(this.config, changePath);
257
289
  const oldValue = oldConfig ? getConfigValue(oldConfig, changePath) : void 0;
258
290
  this.emit("config:changed", { path: changePath, value, oldValue });
@@ -289,6 +321,8 @@ var ConfigManager = class extends EventEmitter {
289
321
  const overrides = [
290
322
  ["OPENACP_TELEGRAM_BOT_TOKEN", ["channels", "telegram", "botToken"]],
291
323
  ["OPENACP_TELEGRAM_CHAT_ID", ["channels", "telegram", "chatId"]],
324
+ ["OPENACP_DISCORD_BOT_TOKEN", ["channels", "discord", "botToken"]],
325
+ ["OPENACP_DISCORD_GUILD_ID", ["channels", "discord", "guildId"]],
292
326
  ["OPENACP_DEFAULT_AGENT", ["defaultAgent"]],
293
327
  ["OPENACP_RUN_MODE", ["runMode"]],
294
328
  ["OPENACP_API_PORT", ["api", "port"]]
@@ -331,6 +365,20 @@ var ConfigManager = class extends EventEmitter {
331
365
  raw.tunnel = raw.tunnel || {};
332
366
  raw.tunnel.provider = process.env.OPENACP_TUNNEL_PROVIDER;
333
367
  }
368
+ if (process.env.OPENACP_SPEECH_STT_PROVIDER) {
369
+ raw.speech = raw.speech || {};
370
+ const speech = raw.speech;
371
+ speech.stt = speech.stt || {};
372
+ speech.stt.provider = process.env.OPENACP_SPEECH_STT_PROVIDER;
373
+ }
374
+ if (process.env.OPENACP_SPEECH_GROQ_API_KEY) {
375
+ raw.speech = raw.speech || {};
376
+ const speech = raw.speech;
377
+ speech.stt = speech.stt || {};
378
+ speech.stt.providers = speech.stt.providers || {};
379
+ speech.stt.providers.groq = speech.stt.providers.groq || {};
380
+ speech.stt.providers.groq.apiKey = process.env.OPENACP_SPEECH_GROQ_API_KEY;
381
+ }
334
382
  }
335
383
  deepMerge(target, source) {
336
384
  for (const key of Object.keys(source)) {
@@ -351,4 +399,4 @@ export {
351
399
  expandHome,
352
400
  ConfigManager
353
401
  };
354
- //# sourceMappingURL=chunk-2Z2XPUD5.js.map
402
+ //# sourceMappingURL=chunk-4LFDEW22.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/config.ts","../../src/core/config-migrations.ts"],"sourcesContent":["import { z } from \"zod\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { EventEmitter } from \"node:events\";\nimport { applyMigrations } from \"./config-migrations.js\";\nimport { createChildLogger } from \"./log.js\";\nconst log = createChildLogger({ module: \"config\" });\n\nconst BaseChannelSchema = z\n .object({\n enabled: z.boolean().default(false),\n adapter: z.string().optional(), // package name for plugin adapters\n })\n .passthrough();\n\nexport const PLUGINS_DIR = path.join(os.homedir(), \".openacp\", \"plugins\");\n\nconst AgentSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n workingDirectory: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n});\n\nconst LoggingSchema = z\n .object({\n level: z\n .enum([\"silent\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"])\n .default(\"info\"),\n logDir: z.string().default(\"~/.openacp/logs\"),\n maxFileSize: z.union([z.string(), z.number()]).default(\"10m\"),\n maxFiles: z.number().default(7),\n sessionLogRetentionDays: z.number().default(30),\n })\n .default({});\n\nexport type LoggingConfig = z.infer<typeof LoggingSchema>;\n\nconst TunnelAuthSchema = z\n .object({\n enabled: z.boolean().default(false),\n token: z.string().optional(),\n })\n .default({});\n\nconst TunnelSchema = z\n .object({\n enabled: z.boolean().default(false),\n port: z.number().default(3100),\n provider: z.enum([\"cloudflare\", \"ngrok\", \"bore\", \"tailscale\"]).default(\"cloudflare\"),\n options: z.record(z.string(), z.unknown()).default({}),\n maxUserTunnels: z.number().default(5),\n storeTtlMinutes: z.number().default(60),\n auth: TunnelAuthSchema,\n })\n .default({});\n\nexport type TunnelConfig = z.infer<typeof TunnelSchema>;\n\nconst UsageSchema = z\n .object({\n enabled: z.boolean().default(true),\n monthlyBudget: z.number().optional(),\n warningThreshold: z.number().default(0.8),\n currency: z.string().default(\"USD\"),\n retentionDays: z.number().default(90),\n })\n .default({});\n\nexport type UsageConfig = z.infer<typeof UsageSchema>;\n\nconst SpeechProviderSchema = z.object({\n apiKey: z.string().min(1),\n model: z.string().optional(),\n}).passthrough();\n\nconst SpeechSchema = z.object({\n stt: z.object({\n provider: z.string().nullable().default(null),\n providers: z.record(SpeechProviderSchema).default({}),\n }).default({}),\n tts: z.object({\n provider: z.string().nullable().default(null),\n providers: z.record(SpeechProviderSchema).default({}),\n }).default({}),\n}).optional().default({});\n\nexport const ConfigSchema = z.object({\n channels: z.record(z.string(), BaseChannelSchema),\n agents: z.record(z.string(), AgentSchema).optional().default({}),\n defaultAgent: z.string(),\n workspace: z\n .object({\n baseDir: z.string().default(\"~/openacp-workspace\"),\n })\n .default({}),\n security: z\n .object({\n allowedUserIds: z.array(z.string()).default([]),\n maxConcurrentSessions: z.number().default(20),\n sessionTimeoutMinutes: z.number().default(60),\n })\n .default({}),\n logging: LoggingSchema,\n runMode: z.enum(['foreground', 'daemon']).default('foreground'),\n autoStart: z.boolean().default(false),\n api: z.object({\n port: z.number().default(21420),\n host: z.string().default('127.0.0.1'),\n }).default({}),\n sessionStore: z\n .object({\n ttlDays: z.number().default(30),\n })\n .default({}),\n tunnel: TunnelSchema,\n usage: UsageSchema,\n integrations: z.record(z.string(), z.object({\n installed: z.boolean(),\n installedAt: z.string().optional(),\n })).default({}),\n speech: SpeechSchema,\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport function expandHome(p: string): string {\n if (p.startsWith(\"~\")) {\n return path.join(os.homedir(), p.slice(1));\n }\n return p;\n}\n\nconst DEFAULT_CONFIG = {\n channels: {\n telegram: {\n enabled: false,\n botToken: \"YOUR_BOT_TOKEN_HERE\",\n chatId: 0,\n notificationTopicId: null,\n assistantTopicId: null,\n },\n discord: {\n enabled: false,\n botToken: \"YOUR_DISCORD_BOT_TOKEN_HERE\",\n guildId: \"\",\n forumChannelId: null,\n notificationChannelId: null,\n assistantThreadId: null,\n },\n },\n agents: {\n claude: { command: \"claude-agent-acp\", args: [], env: {} },\n codex: { command: \"codex\", args: [\"--acp\"], env: {} },\n },\n defaultAgent: \"claude\",\n workspace: { baseDir: \"~/openacp-workspace\" },\n security: {\n allowedUserIds: [],\n maxConcurrentSessions: 20,\n sessionTimeoutMinutes: 60,\n },\n sessionStore: { ttlDays: 30 },\n tunnel: {\n enabled: true,\n port: 3100,\n provider: \"cloudflare\",\n options: {},\n storeTtlMinutes: 60,\n auth: { enabled: false },\n },\n usage: {},\n};\n\nexport class ConfigManager extends EventEmitter {\n private config!: Config;\n private configPath: string;\n\n constructor() {\n super();\n this.configPath =\n process.env.OPENACP_CONFIG_PATH || expandHome(\"~/.openacp/config.json\");\n }\n\n async load(): Promise<void> {\n // 1. Ensure directory exists\n const dir = path.dirname(this.configPath);\n fs.mkdirSync(dir, { recursive: true });\n\n // 2. If config file doesn't exist, create default\n if (!fs.existsSync(this.configPath)) {\n fs.writeFileSync(\n this.configPath,\n JSON.stringify(DEFAULT_CONFIG, null, 2),\n );\n log.info({ configPath: this.configPath }, \"Config created\");\n log.info(\n \"Please edit it with your channel credentials (Telegram bot token, Discord bot token, etc.), then restart.\",\n );\n process.exit(1);\n }\n\n // 3. Read and parse\n const raw = JSON.parse(fs.readFileSync(this.configPath, \"utf-8\"));\n\n // 3.5. Auto-migrate config\n const { changed: configUpdated } = applyMigrations(raw);\n if (configUpdated) {\n fs.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));\n }\n\n // 4. Apply env var overrides\n this.applyEnvOverrides(raw);\n\n // 5. Validate with Zod\n const result = ConfigSchema.safeParse(raw);\n if (!result.success) {\n log.error(\"Config validation failed\");\n for (const issue of result.error.issues) {\n log.error(\n { path: issue.path.join(\".\"), message: issue.message },\n \"Validation error\",\n );\n }\n process.exit(1);\n }\n this.config = result.data;\n }\n\n get(): Config {\n return this.config;\n }\n\n async save(updates: Record<string, unknown>, changePath?: string): Promise<void> {\n const oldConfig = this.config ? structuredClone(this.config) : undefined;\n // Read current file, merge updates, write back\n const raw = JSON.parse(fs.readFileSync(this.configPath, \"utf-8\"));\n this.deepMerge(raw, updates);\n fs.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));\n // Re-validate and update in-memory config\n const result = ConfigSchema.safeParse(raw);\n if (result.success) {\n this.config = result.data;\n }\n // Emit change event if path provided\n if (changePath) {\n const { getConfigValue } = await import('./config-registry.js')\n const value = getConfigValue(this.config, changePath)\n const oldValue = oldConfig ? getConfigValue(oldConfig, changePath) : undefined\n this.emit('config:changed', { path: changePath, value, oldValue })\n }\n }\n\n resolveWorkspace(input?: string): string {\n if (!input) {\n const resolved = expandHome(this.config.workspace.baseDir);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n if (input.startsWith(\"/\") || input.startsWith(\"~\")) {\n const resolved = expandHome(input);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n // Named workspace → lowercase, under baseDir\n const name = input.toLowerCase();\n const resolved = path.join(expandHome(this.config.workspace.baseDir), name);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n\n async exists(): Promise<boolean> {\n return fs.existsSync(this.configPath);\n }\n\n getConfigPath(): string {\n return this.configPath;\n }\n\n async writeNew(config: Config): Promise<void> {\n const dir = path.dirname(this.configPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));\n }\n\n private applyEnvOverrides(raw: Record<string, unknown>): void {\n const overrides: [string, string[]][] = [\n [\"OPENACP_TELEGRAM_BOT_TOKEN\", [\"channels\", \"telegram\", \"botToken\"]],\n [\"OPENACP_TELEGRAM_CHAT_ID\", [\"channels\", \"telegram\", \"chatId\"]],\n [\"OPENACP_DISCORD_BOT_TOKEN\", [\"channels\", \"discord\", \"botToken\"]],\n [\"OPENACP_DISCORD_GUILD_ID\", [\"channels\", \"discord\", \"guildId\"]],\n [\"OPENACP_DEFAULT_AGENT\", [\"defaultAgent\"]],\n [\"OPENACP_RUN_MODE\", [\"runMode\"]],\n [\"OPENACP_API_PORT\", [\"api\", \"port\"]],\n ];\n for (const [envVar, configPath] of overrides) {\n const value = process.env[envVar];\n if (value !== undefined) {\n let target = raw as Record<string, any>;\n for (let i = 0; i < configPath.length - 1; i++) {\n if (!target[configPath[i]]) target[configPath[i]] = {};\n target = target[configPath[i]];\n }\n const key = configPath[configPath.length - 1];\n // Convert numeric fields to number\n target[key] = (key === \"chatId\" || key === \"port\") ? Number(value) : value;\n }\n }\n\n // Logging env var overrides\n if (process.env.OPENACP_LOG_LEVEL) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).level =\n process.env.OPENACP_LOG_LEVEL;\n }\n if (process.env.OPENACP_LOG_DIR) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).logDir =\n process.env.OPENACP_LOG_DIR;\n }\n if (process.env.OPENACP_DEBUG && !process.env.OPENACP_LOG_LEVEL) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).level = \"debug\";\n }\n\n // Tunnel env var overrides\n if (process.env.OPENACP_TUNNEL_ENABLED) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).enabled =\n process.env.OPENACP_TUNNEL_ENABLED === \"true\";\n }\n if (process.env.OPENACP_TUNNEL_PORT) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).port = Number(\n process.env.OPENACP_TUNNEL_PORT,\n );\n }\n if (process.env.OPENACP_TUNNEL_PROVIDER) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).provider =\n process.env.OPENACP_TUNNEL_PROVIDER;\n }\n\n // Speech env var overrides\n if (process.env.OPENACP_SPEECH_STT_PROVIDER) {\n raw.speech = raw.speech || {};\n const speech = raw.speech as Record<string, any>;\n speech.stt = speech.stt || {};\n speech.stt.provider = process.env.OPENACP_SPEECH_STT_PROVIDER;\n }\n if (process.env.OPENACP_SPEECH_GROQ_API_KEY) {\n raw.speech = raw.speech || {};\n const speech = raw.speech as Record<string, any>;\n speech.stt = speech.stt || {};\n speech.stt.providers = speech.stt.providers || {};\n speech.stt.providers.groq = speech.stt.providers.groq || {};\n speech.stt.providers.groq.apiKey = process.env.OPENACP_SPEECH_GROQ_API_KEY;\n }\n }\n\n private deepMerge(\n target: Record<string, any>,\n source: Record<string, any>,\n ): void {\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === \"object\" &&\n !Array.isArray(source[key])\n ) {\n if (!target[key]) target[key] = {};\n this.deepMerge(target[key], source[key]);\n } else {\n target[key] = source[key];\n }\n }\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { createChildLogger } from \"./log.js\";\nconst log = createChildLogger({ module: \"config-migrations\" });\n\ntype RawConfig = Record<string, unknown>;\n\nexport interface Migration {\n name: string;\n apply: (raw: RawConfig) => boolean; // returns true if config was modified\n}\n\nexport const migrations: Migration[] = [\n {\n name: \"add-tunnel-section\",\n apply(raw) {\n if (raw.tunnel) return false;\n raw.tunnel = {\n enabled: true,\n port: 3100,\n provider: \"cloudflare\",\n options: {},\n storeTtlMinutes: 60,\n auth: { enabled: false },\n };\n log.info(\"Added tunnel section to config (enabled by default with cloudflare)\");\n return true;\n },\n },\n {\n name: \"fix-agent-commands\",\n apply(raw) {\n const COMMAND_MIGRATIONS: Record<string, string[]> = {\n \"claude-agent-acp\": [\"claude\", \"claude-code\"],\n };\n\n const agents = raw.agents;\n if (!agents || typeof agents !== \"object\") return false;\n\n let changed = false;\n for (const [agentName, agentDef] of Object.entries(agents as Record<string, any>)) {\n if (!agentDef?.command) continue;\n for (const [correctCmd, legacyCmds] of Object.entries(COMMAND_MIGRATIONS)) {\n if (legacyCmds.includes(agentDef.command)) {\n log.warn(\n { agent: agentName, oldCommand: agentDef.command, newCommand: correctCmd },\n `Auto-migrating agent command: \"${agentDef.command}\" → \"${correctCmd}\"`,\n );\n agentDef.command = correctCmd;\n changed = true;\n }\n }\n }\n return changed;\n },\n },\n {\n name: \"migrate-agents-to-store\",\n apply(raw) {\n const agentsJsonPath = path.join(os.homedir(), \".openacp\", \"agents.json\");\n if (fs.existsSync(agentsJsonPath)) return false;\n\n const agents = raw.agents as Record<string, any> | undefined;\n if (!agents || Object.keys(agents).length === 0) return false;\n\n const COMMAND_TO_REGISTRY: Record<string, string> = {\n \"claude-agent-acp\": \"claude-acp\",\n \"codex\": \"codex-acp\",\n };\n\n const installed: Record<string, any> = {};\n for (const [key, cfg] of Object.entries(agents)) {\n const registryId = COMMAND_TO_REGISTRY[cfg.command] ?? null;\n installed[key] = {\n registryId,\n name: key.charAt(0).toUpperCase() + key.slice(1),\n version: \"unknown\",\n distribution: \"custom\",\n command: cfg.command,\n args: cfg.args ?? [],\n env: cfg.env ?? {},\n workingDirectory: cfg.workingDirectory ?? undefined,\n installedAt: new Date().toISOString(),\n binaryPath: null,\n };\n }\n\n fs.mkdirSync(path.dirname(agentsJsonPath), { recursive: true });\n fs.writeFileSync(agentsJsonPath, JSON.stringify({ version: 1, installed }, null, 2));\n\n raw.agents = {};\n return true;\n },\n },\n];\n\n/**\n * Apply all migrations to raw config (mutates in place).\n * Returns whether any changes were made.\n */\nexport function applyMigrations(\n raw: RawConfig,\n migrationList: Migration[] = migrations,\n): { changed: boolean } {\n let changed = false;\n for (const migration of migrationList) {\n if (migration.apply(raw)) {\n changed = true;\n }\n }\n return { changed };\n}\n"],"mappings":"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","names":["fs","path","os","log","resolved"]}
@@ -70,6 +70,23 @@ var CONFIG_REGISTRY = [
70
70
  type: "number",
71
71
  scope: "safe",
72
72
  hotReload: true
73
+ },
74
+ {
75
+ path: "speech.stt.provider",
76
+ displayName: "Speech to Text",
77
+ group: "speech",
78
+ type: "select",
79
+ options: ["groq"],
80
+ scope: "safe",
81
+ hotReload: true
82
+ },
83
+ {
84
+ path: "speech.stt.apiKey",
85
+ displayName: "STT API Key",
86
+ group: "speech",
87
+ type: "string",
88
+ scope: "sensitive",
89
+ hotReload: true
73
90
  }
74
91
  ];
75
92
  function getFieldDef(path2) {
@@ -107,4 +124,4 @@ export {
107
124
  resolveOptions,
108
125
  getConfigValue
109
126
  };
110
- //# sourceMappingURL=chunk-Z46LGZ7R.js.map
127
+ //# sourceMappingURL=chunk-4TR5Y3MP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/config-registry.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { Config } from './config.js'\n\nexport interface ConfigFieldDef {\n path: string\n displayName: string\n group: string\n type: 'toggle' | 'select' | 'number' | 'string'\n options?: string[] | ((config: Config) => string[])\n scope: 'safe' | 'sensitive'\n hotReload: boolean\n}\n\nexport const CONFIG_REGISTRY: ConfigFieldDef[] = [\n {\n path: 'defaultAgent',\n displayName: 'Default Agent',\n group: 'agent',\n type: 'select',\n options: (config) => {\n try {\n const agentsPath = path.join(os.homedir(), \".openacp\", \"agents.json\");\n if (fs.existsSync(agentsPath)) {\n const data = JSON.parse(fs.readFileSync(agentsPath, \"utf-8\"));\n return Object.keys(data.installed ?? {});\n }\n } catch { /* fallback */ }\n return Object.keys(config.agents ?? {});\n },\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'logging.level',\n displayName: 'Log Level',\n group: 'logging',\n type: 'select',\n options: ['silent', 'debug', 'info', 'warn', 'error', 'fatal'],\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'tunnel.enabled',\n displayName: 'Tunnel',\n group: 'tunnel',\n type: 'toggle',\n scope: 'safe',\n hotReload: false,\n },\n {\n path: 'security.maxConcurrentSessions',\n displayName: 'Max Concurrent Sessions',\n group: 'security',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'security.sessionTimeoutMinutes',\n displayName: 'Session Timeout (min)',\n group: 'security',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'workspace.baseDir',\n displayName: 'Workspace Directory',\n group: 'workspace',\n type: 'string',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'sessionStore.ttlDays',\n displayName: 'Session Store TTL (days)',\n group: 'storage',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'speech.stt.provider',\n displayName: 'Speech to Text',\n group: 'speech',\n type: 'select',\n options: ['groq'],\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'speech.stt.apiKey',\n displayName: 'STT API Key',\n group: 'speech',\n type: 'string',\n scope: 'sensitive',\n hotReload: true,\n },\n]\n\nexport function getFieldDef(path: string): ConfigFieldDef | undefined {\n return CONFIG_REGISTRY.find((f) => f.path === path)\n}\n\nexport function getSafeFields(): ConfigFieldDef[] {\n return CONFIG_REGISTRY.filter((f) => f.scope === 'safe')\n}\n\nexport function isHotReloadable(path: string): boolean {\n const def = getFieldDef(path)\n return def?.hotReload ?? false\n}\n\nexport function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined {\n if (!def.options) return undefined\n return typeof def.options === 'function' ? def.options(config) : def.options\n}\n\nexport function getConfigValue(config: Config, path: string): unknown {\n const parts = path.split('.')\n let current: unknown = config\n for (const part of parts) {\n if (current && typeof current === 'object' && part in current) {\n current = (current as Record<string, unknown>)[part]\n } else {\n return undefined\n }\n }\n return current\n}\n"],"mappings":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,QAAQ;AAab,IAAM,kBAAoC;AAAA,EAC/C;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AACnB,UAAI;AACF,cAAM,aAAkB,UAAQ,WAAQ,GAAG,YAAY,aAAa;AACpE,YAAO,cAAW,UAAU,GAAG;AAC7B,gBAAM,OAAO,KAAK,MAAS,gBAAa,YAAY,OAAO,CAAC;AAC5D,iBAAO,OAAO,KAAK,KAAK,aAAa,CAAC,CAAC;AAAA,QACzC;AAAA,MACF,QAAQ;AAAA,MAAiB;AACzB,aAAO,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;AAAA,IACxC;AAAA,IACA,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,UAAU,SAAS,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAC7D,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,MAAM;AAAA,IAChB,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AACF;AAEO,SAAS,YAAYA,OAA0C;AACpE,SAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,SAASA,KAAI;AACpD;AAEO,SAAS,gBAAkC;AAChD,SAAO,gBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM;AACzD;AAEO,SAAS,gBAAgBA,OAAuB;AACrD,QAAM,MAAM,YAAYA,KAAI;AAC5B,SAAO,KAAK,aAAa;AAC3B;AAEO,SAAS,eAAe,KAAqB,QAAsC;AACxF,MAAI,CAAC,IAAI,QAAS,QAAO;AACzB,SAAO,OAAO,IAAI,YAAY,aAAa,IAAI,QAAQ,MAAM,IAAI,IAAI;AACvE;AAEO,SAAS,eAAe,QAAgBA,OAAuB;AACpE,QAAM,QAAQA,MAAK,MAAM,GAAG;AAC5B,MAAI,UAAmB;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,OAAO,YAAY,YAAY,QAAQ,SAAS;AAC7D,gBAAW,QAAoC,IAAI;AAAA,IACrD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":["path"]}
@@ -0,0 +1,105 @@
1
+ import {
2
+ log
3
+ } from "./chunk-ESOPMQAY.js";
4
+
5
+ // src/adapters/discord/commands/admin.ts
6
+ import {
7
+ ActionRowBuilder,
8
+ ButtonBuilder,
9
+ ButtonStyle
10
+ } from "discord.js";
11
+ function buildDangerousModeKeyboard(sessionId, isDangerous) {
12
+ return new ActionRowBuilder().addComponents(
13
+ new ButtonBuilder().setCustomId(`d:${sessionId}`).setLabel(isDangerous ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode").setStyle(isDangerous ? ButtonStyle.Secondary : ButtonStyle.Danger)
14
+ );
15
+ }
16
+ async function handleDangerous(interaction, adapter) {
17
+ await interaction.deferReply({ ephemeral: true });
18
+ const channelId = interaction.channelId;
19
+ const session = adapter.core.sessionManager.getSessionByThread("discord", channelId);
20
+ if (session) {
21
+ session.dangerousMode = !session.dangerousMode;
22
+ adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {
23
+ });
24
+ log.info({ sessionId: session.id, dangerousMode: session.dangerousMode }, "[discord-admin] Dangerous mode toggled via command");
25
+ const msg2 = session.dangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
26
+ await interaction.editReply(msg2);
27
+ return;
28
+ }
29
+ const record = adapter.core.sessionManager.getRecordByThread("discord", channelId);
30
+ if (!record || record.status === "cancelled" || record.status === "error") {
31
+ await interaction.editReply("\u26A0\uFE0F No active session in this channel.");
32
+ return;
33
+ }
34
+ const newDangerousMode = !(record.dangerousMode ?? false);
35
+ adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {
36
+ });
37
+ log.info({ sessionId: record.sessionId, dangerousMode: newDangerousMode }, "[discord-admin] Dangerous mode toggled via command (store-only)");
38
+ const msg = newDangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
39
+ await interaction.editReply(msg);
40
+ }
41
+ async function handleDangerousButton(interaction, adapter) {
42
+ const sessionId = interaction.customId.slice(2);
43
+ const session = adapter.core.sessionManager.getSession(sessionId);
44
+ if (session) {
45
+ session.dangerousMode = !session.dangerousMode;
46
+ adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
47
+ });
48
+ log.info({ sessionId, dangerousMode: session.dangerousMode }, "[discord-admin] Dangerous mode toggled via button");
49
+ const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
50
+ try {
51
+ await interaction.update({
52
+ components: [buildDangerousModeKeyboard(sessionId, session.dangerousMode)]
53
+ });
54
+ } catch {
55
+ }
56
+ try {
57
+ await interaction.followUp({ content: toastText2, ephemeral: true });
58
+ } catch {
59
+ }
60
+ return;
61
+ }
62
+ const record = adapter.core.sessionManager.getSessionRecord(sessionId);
63
+ if (!record || record.status === "cancelled" || record.status === "error") {
64
+ await interaction.reply({ content: "\u26A0\uFE0F Session not found or already ended.", ephemeral: true });
65
+ return;
66
+ }
67
+ const newDangerousMode = !(record.dangerousMode ?? false);
68
+ adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
69
+ });
70
+ log.info({ sessionId, dangerousMode: newDangerousMode }, "[discord-admin] Dangerous mode toggled via button (store-only)");
71
+ const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
72
+ try {
73
+ await interaction.update({
74
+ components: [buildDangerousModeKeyboard(sessionId, newDangerousMode)]
75
+ });
76
+ } catch {
77
+ }
78
+ try {
79
+ await interaction.followUp({ content: toastText, ephemeral: true });
80
+ } catch {
81
+ }
82
+ }
83
+ async function handleRestart(interaction, adapter) {
84
+ await interaction.deferReply({ ephemeral: true });
85
+ if (!adapter.core.requestRestart) {
86
+ await interaction.editReply("\u26A0\uFE0F Restart is not available (no restart handler registered).");
87
+ return;
88
+ }
89
+ await interaction.editReply("\u{1F504} **Restarting OpenACP...**\nRebuilding and restarting. Be back shortly.");
90
+ await new Promise((r) => setTimeout(r, 500));
91
+ await adapter.core.requestRestart();
92
+ }
93
+ async function handleUpdate(interaction, adapter) {
94
+ await interaction.deferReply({ ephemeral: true });
95
+ await interaction.editReply("\u26A0\uFE0F Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.");
96
+ }
97
+
98
+ export {
99
+ buildDangerousModeKeyboard,
100
+ handleDangerous,
101
+ handleDangerousButton,
102
+ handleRestart,
103
+ handleUpdate
104
+ };
105
+ //# sourceMappingURL=chunk-7G5QKLLF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/commands/admin.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\n\n// TODO: Replace `any` with DiscordAdapter once Task 12 is implemented\n\nexport function buildDangerousModeKeyboard(\n sessionId: string,\n isDangerous: boolean,\n): ActionRowBuilder<ButtonBuilder> {\n return new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`d:${sessionId}`)\n .setLabel(isDangerous ? '🔐 Disable Dangerous Mode' : '☠️ Enable Dangerous Mode')\n .setStyle(isDangerous ? ButtonStyle.Secondary : ButtonStyle.Danger),\n )\n}\n\nexport async function handleDangerous(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const channelId = interaction.channelId\n const session = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId: session.id, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via command')\n\n const msg = session.dangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n return\n }\n\n // Session not in memory — update store directly\n const record = adapter.core.sessionManager.getRecordByThread('discord', channelId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.editReply('⚠️ No active session in this channel.')\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId: record.sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via command (store-only)')\n\n const msg = newDangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n}\n\nexport async function handleDangerousButton(\n interaction: ButtonInteraction,\n adapter: any,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2) // strip 'd:'\n const session = adapter.core.sessionManager.getSession(sessionId)\n\n // Session live in memory — toggle directly\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via button')\n\n const toastText = session.dangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n await interaction.update({\n components: [buildDangerousModeKeyboard(sessionId, session.dangerousMode)],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Session not in memory — toggle in store\n const record = adapter.core.sessionManager.getSessionRecord(sessionId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.reply({ content: '⚠️ Session not found or already ended.', ephemeral: true })\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via button (store-only)')\n\n const toastText = newDangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n await interaction.update({\n components: [buildDangerousModeKeyboard(sessionId, newDangerousMode)],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n}\n\nexport async function handleRestart(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n if (!adapter.core.requestRestart) {\n await interaction.editReply('⚠️ Restart is not available (no restart handler registered).')\n return\n }\n\n await interaction.editReply('🔄 **Restarting OpenACP...**\\nRebuilding and restarting. Be back shortly.')\n await new Promise((r) => setTimeout(r, 500))\n await adapter.core.requestRestart()\n}\n\nexport async function handleUpdate(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n // Stub: not implemented yet\n await interaction.editReply('⚠️ Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.')\n}\n"],"mappings":"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","names":["msg","toastText"]}
@@ -36,6 +36,7 @@ Each session gets its own topic \u2014 chat there to work with the agent.
36
36
  /enable_dangerous \u2014 Auto-approve permissions
37
37
  /disable_dangerous \u2014 Restore permission prompts
38
38
  /handoff \u2014 Continue session in terminal
39
+ /archive \u2014 Archive session topic
39
40
  /clear \u2014 Clear assistant history
40
41
 
41
42
  \u{1F4AC} Need help? Just ask me in this topic!`,
@@ -88,4 +89,4 @@ export {
88
89
  handleClear,
89
90
  buildSkillMessages
90
91
  };
91
- //# sourceMappingURL=chunk-IURZ4QHG.js.map
92
+ //# sourceMappingURL=chunk-7QJS2XBD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/telegram/commands/menu.ts"],"sourcesContent":["import type { Context } from \"grammy\";\nimport { InlineKeyboard } from \"grammy\";\nimport type { AgentCommand } from \"../../../core/index.js\";\nimport type { CommandsAssistantContext } from \"../types.js\";\n\nexport function buildMenuKeyboard(): InlineKeyboard {\n return new InlineKeyboard()\n .text(\"🆕 New Session\", \"m:new\")\n .text(\"📋 Sessions\", \"m:topics\")\n .row()\n .text(\"📊 Status\", \"m:status\")\n .text(\"🤖 Agents\", \"m:agents\")\n .row()\n .text(\"⚙️ Settings\", \"m:settings\")\n .text(\"🔗 Integrate\", \"m:integrate\")\n .row()\n .text(\"🔄 Restart\", \"m:restart\")\n .text(\"⬆️ Update\", \"m:update\")\n .row()\n .text(\"❓ Help\", \"m:help\")\n .text(\"🩺 Doctor\", \"m:doctor\");\n}\n\nexport async function handleMenu(ctx: Context): Promise<void> {\n await ctx.reply(`<b>OpenACP Menu</b>\\nChoose an action:`, {\n parse_mode: \"HTML\",\n reply_markup: buildMenuKeyboard(),\n });\n}\n\nexport async function handleHelp(ctx: Context): Promise<void> {\n await ctx.reply(\n `📖 <b>OpenACP Help</b>\\n\\n` +\n `🚀 <b>Getting Started</b>\\n` +\n `Tap 🆕 New Session to start coding with AI.\\n` +\n `Each session gets its own topic — chat there to work with the agent.\\n\\n` +\n `💡 <b>Common Tasks</b>\\n` +\n `/new [agent] [workspace] — Create new session\\n` +\n `/cancel — Cancel session (in session topic)\\n` +\n `/status — Show session or system status\\n` +\n `/sessions — List all sessions\\n` +\n `/agents — Browse & install agents\\n` +\n `/install <name> — Install an agent\\n\\n` +\n `⚙️ <b>System</b>\\n` +\n `/restart — Restart OpenACP\\n` +\n `/update — Update to latest version\\n` +\n `/integrate — Manage agent integrations\\n` +\n `/menu — Show action menu\\n\\n` +\n `🔒 <b>Session Options</b>\\n` +\n `/enable_dangerous — Auto-approve permissions\\n` +\n `/disable_dangerous — Restore permission prompts\\n` +\n `/handoff — Continue session in terminal\\n` +\n `/archive — Archive session topic\\n` +\n `/clear — Clear assistant history\\n\\n` +\n `💬 Need help? Just ask me in this topic!`,\n { parse_mode: \"HTML\" },\n );\n}\n\nexport async function handleClear(ctx: Context, assistant?: CommandsAssistantContext): Promise<void> {\n if (!assistant) {\n await ctx.reply(\"⚠️ Assistant is not available.\", { parse_mode: \"HTML\" });\n return;\n }\n\n const threadId = ctx.message?.message_thread_id;\n if (threadId !== assistant.topicId) {\n await ctx.reply(\"ℹ️ /clear only works in the Assistant topic.\", { parse_mode: \"HTML\" });\n return;\n }\n\n await ctx.reply(\"🔄 Clearing assistant history...\", { parse_mode: \"HTML\" });\n\n try {\n await assistant.respawn();\n await ctx.reply(\"✅ Assistant history cleared.\", { parse_mode: \"HTML\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await ctx.reply(`❌ Failed to clear: <code>${message}</code>`, { parse_mode: \"HTML\" });\n }\n}\n\nconst TELEGRAM_MSG_LIMIT = 4096;\n\n/**\n * Build plain-text skill command messages. Each command is on its own line\n * wrapped in <code> for tap-to-copy. If the list exceeds Telegram's message\n * limit, it is split into multiple messages (cut at line boundaries).\n */\nexport function buildSkillMessages(commands: AgentCommand[]): string[] {\n const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));\n const header = \"🛠 <b>Available Skills</b>\\n\";\n const lines = sorted.map((c) => `<code>/${c.name}</code>`);\n\n const messages: string[] = [];\n let current = header;\n\n for (const line of lines) {\n const candidate = current + \"\\n\" + line;\n if (candidate.length > TELEGRAM_MSG_LIMIT) {\n messages.push(current);\n current = line;\n } else {\n current = candidate;\n }\n }\n if (current) messages.push(current);\n return messages;\n}\n"],"mappings":";AACA,SAAS,sBAAsB;AAIxB,SAAS,oBAAoC;AAClD,SAAO,IAAI,eAAe,EACvB,KAAK,yBAAkB,OAAO,EAC9B,KAAK,sBAAe,UAAU,EAC9B,IAAI,EACJ,KAAK,oBAAa,UAAU,EAC5B,KAAK,oBAAa,UAAU,EAC5B,IAAI,EACJ,KAAK,yBAAe,YAAY,EAChC,KAAK,uBAAgB,aAAa,EAClC,IAAI,EACJ,KAAK,qBAAc,WAAW,EAC9B,KAAK,uBAAa,UAAU,EAC5B,IAAI,EACJ,KAAK,eAAU,QAAQ,EACvB,KAAK,oBAAa,UAAU;AACjC;AAEA,eAAsB,WAAW,KAA6B;AAC5D,QAAM,IAAI,MAAM;AAAA,oBAA0C;AAAA,IACxD,YAAY;AAAA,IACZ,cAAc,kBAAkB;AAAA,EAClC,CAAC;AACH;AAEA,eAAsB,WAAW,KAA6B;AAC5D,QAAM,IAAI;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuBA,EAAE,YAAY,OAAO;AAAA,EACvB;AACF;AAEA,eAAsB,YAAY,KAAc,WAAqD;AACnG,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,4CAAkC,EAAE,YAAY,OAAO,CAAC;AACxE;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,SAAS;AAC9B,MAAI,aAAa,UAAU,SAAS;AAClC,UAAM,IAAI,MAAM,0DAAgD,EAAE,YAAY,OAAO,CAAC;AACtF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,2CAAoC,EAAE,YAAY,OAAO,CAAC;AAE1E,MAAI;AACF,UAAM,UAAU,QAAQ;AACxB,UAAM,IAAI,MAAM,qCAAgC,EAAE,YAAY,OAAO,CAAC;AAAA,EACxE,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAM,IAAI,MAAM,iCAA4B,OAAO,WAAW,EAAE,YAAY,OAAO,CAAC;AAAA,EACtF;AACF;AAEA,IAAM,qBAAqB;AAOpB,SAAS,mBAAmB,UAAoC;AACrE,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACxE,QAAM,SAAS;AACf,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,UAAU,EAAE,IAAI,SAAS;AAEzD,QAAM,WAAqB,CAAC;AAC5B,MAAI,UAAU;AAEd,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,UAAU,OAAO;AACnC,QAAI,UAAU,SAAS,oBAAoB;AACzC,eAAS,KAAK,OAAO;AACrB,gBAAU;AAAA,IACZ,OAAO;AACL,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,QAAS,UAAS,KAAK,OAAO;AAClC,SAAO;AACT;","names":[]}
@@ -0,0 +1,145 @@
1
+ import {
2
+ createSessionThread,
3
+ deleteSessionThread
4
+ } from "./chunk-SM3G6UAX.js";
5
+ import {
6
+ buildDangerousModeKeyboard
7
+ } from "./chunk-7G5QKLLF.js";
8
+ import {
9
+ log
10
+ } from "./chunk-ESOPMQAY.js";
11
+
12
+ // src/adapters/discord/commands/new-session.ts
13
+ import {
14
+ ActionRowBuilder,
15
+ ButtonBuilder,
16
+ ButtonStyle
17
+ } from "discord.js";
18
+ async function handleNew(interaction, adapter) {
19
+ await interaction.deferReply({ ephemeral: true });
20
+ const agentName = interaction.options.getString("agent") ?? void 0;
21
+ const workspace = interaction.options.getString("workspace") ?? void 0;
22
+ if (agentName) {
23
+ await executeNewSession(interaction, adapter, agentName, workspace);
24
+ return;
25
+ }
26
+ const installedEntries = adapter.core.agentCatalog.getInstalledEntries();
27
+ const agentKeys = Object.keys(installedEntries);
28
+ const config = adapter.core.configManager.get();
29
+ if (agentKeys.length === 0) {
30
+ await interaction.editReply("\u274C No agents installed. Use `/install` to install an agent first.");
31
+ return;
32
+ }
33
+ if (agentKeys.length === 1) {
34
+ await executeNewSession(interaction, adapter, config.defaultAgent, workspace);
35
+ return;
36
+ }
37
+ const row = new ActionRowBuilder();
38
+ for (const key of agentKeys) {
39
+ const agent = installedEntries[key];
40
+ const label = key === config.defaultAgent ? `${agent.name} (default)` : agent.name;
41
+ row.addComponents(
42
+ new ButtonBuilder().setCustomId(`m:new:agent:${key}`).setLabel(label).setStyle(ButtonStyle.Primary)
43
+ );
44
+ }
45
+ await interaction.editReply({
46
+ content: "\u{1F916} **Choose an agent:**",
47
+ components: [row]
48
+ });
49
+ }
50
+ async function handleNewChat(interaction, adapter) {
51
+ await interaction.deferReply({ ephemeral: true });
52
+ const channelId = interaction.channelId;
53
+ const currentSession = adapter.core.sessionManager.getSessionByThread("discord", channelId);
54
+ let agentName;
55
+ let workspace;
56
+ if (currentSession) {
57
+ agentName = currentSession.agentName;
58
+ workspace = currentSession.workingDirectory;
59
+ } else {
60
+ const record = adapter.core.sessionManager.getRecordByThread("discord", channelId);
61
+ if (!record || record.status === "cancelled" || record.status === "error") {
62
+ await interaction.editReply("No active session in this channel. Use `/new` to start one.");
63
+ return;
64
+ }
65
+ agentName = record.agentName;
66
+ workspace = record.workingDir;
67
+ }
68
+ await executeNewSession(interaction, adapter, agentName, workspace);
69
+ }
70
+ async function executeNewSession(interaction, adapter, agentName, workspace) {
71
+ const config = adapter.core.configManager.get();
72
+ const resolvedAgent = agentName || config.defaultAgent;
73
+ log.info({ agentName: resolvedAgent, workspace }, "[discord-new-session] Creating session");
74
+ const forumChannel = adapter.getForumChannel();
75
+ if (!forumChannel) {
76
+ const msg = "\u274C Forum channel not configured. Please run setup first.";
77
+ if (interaction.deferred || interaction.replied) {
78
+ await interaction.editReply(msg);
79
+ } else {
80
+ await interaction.reply({ content: msg, ephemeral: true });
81
+ }
82
+ return;
83
+ }
84
+ let thread;
85
+ try {
86
+ thread = await createSessionThread(forumChannel, `\u{1F504} ${resolvedAgent} \u2014 New Session`);
87
+ const session = await adapter.core.handleNewSession("discord", resolvedAgent, workspace);
88
+ session.threadId = thread.id;
89
+ await adapter.core.sessionManager.patchRecord(session.id, {
90
+ platform: { threadId: thread.id }
91
+ });
92
+ const dangerousRow = buildDangerousModeKeyboard(session.id, false);
93
+ await thread.send({
94
+ content: `\u2705 **Session started**
95
+ **Agent:** ${session.agentName}
96
+ **Workspace:** \`${session.workingDirectory}\`
97
+
98
+ This is your coding session \u2014 chat here to work with the agent.`,
99
+ components: [dangerousRow]
100
+ });
101
+ const replyMsg = `\u2705 Session created \u2192 [Open thread](https://discord.com/channels/${adapter.getGuildId()}/${thread.id})`;
102
+ if (interaction.deferred || interaction.replied) {
103
+ await interaction.editReply(replyMsg);
104
+ } else {
105
+ await interaction.reply({ content: replyMsg, ephemeral: true });
106
+ }
107
+ session.warmup().catch((err) => log.error({ err }, "[discord-new-session] Warm-up error"));
108
+ } catch (err) {
109
+ log.error({ err }, "[discord-new-session] Session creation failed");
110
+ if (thread) {
111
+ try {
112
+ await deleteSessionThread(adapter.getGuild(), thread.id);
113
+ } catch {
114
+ }
115
+ }
116
+ const errMsg = `\u274C ${err instanceof Error ? err.message : String(err)}`;
117
+ try {
118
+ if (interaction.deferred || interaction.replied) {
119
+ await interaction.editReply(errMsg);
120
+ } else {
121
+ await interaction.reply({ content: errMsg, ephemeral: true });
122
+ }
123
+ } catch {
124
+ }
125
+ }
126
+ }
127
+ async function handleNewSessionButton(interaction, adapter) {
128
+ const { customId } = interaction;
129
+ if (customId.startsWith("m:new:agent:")) {
130
+ const agentKey = customId.replace("m:new:agent:", "");
131
+ try {
132
+ await interaction.deferUpdate();
133
+ } catch {
134
+ }
135
+ await executeNewSession(interaction, adapter, agentKey, void 0);
136
+ }
137
+ }
138
+
139
+ export {
140
+ handleNew,
141
+ handleNewChat,
142
+ executeNewSession,
143
+ handleNewSessionButton
144
+ };
145
+ //# sourceMappingURL=chunk-AKIU4JBF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/commands/new-session.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport { buildDangerousModeKeyboard } from './admin.js'\nimport { createSessionThread, deleteSessionThread } from '../forums.js'\n\n// TODO: Replace `any` with DiscordAdapter once Task 12 is implemented\n\nexport async function handleNew(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const agentName = interaction.options.getString('agent') ?? undefined\n const workspace = interaction.options.getString('workspace') ?? undefined\n\n if (agentName) {\n await executeNewSession(interaction, adapter, agentName, workspace)\n return\n }\n\n // No agent specified — show agent picker\n const installedEntries = adapter.core.agentCatalog.getInstalledEntries()\n const agentKeys = Object.keys(installedEntries)\n const config = adapter.core.configManager.get()\n\n if (agentKeys.length === 0) {\n await interaction.editReply('❌ No agents installed. Use `/install` to install an agent first.')\n return\n }\n\n if (agentKeys.length === 1) {\n await executeNewSession(interaction, adapter, config.defaultAgent, workspace)\n return\n }\n\n // Multiple agents — show picker buttons\n const row = new ActionRowBuilder<ButtonBuilder>()\n for (const key of agentKeys) {\n const agent = installedEntries[key]!\n const label = key === config.defaultAgent ? `${agent.name} (default)` : agent.name\n row.addComponents(\n new ButtonBuilder()\n .setCustomId(`m:new:agent:${key}`)\n .setLabel(label)\n .setStyle(ButtonStyle.Primary),\n )\n }\n\n await interaction.editReply({\n content: '🤖 **Choose an agent:**',\n components: [row],\n })\n}\n\nexport async function handleNewChat(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n // Get current thread to inherit config from\n const channelId = interaction.channelId\n const currentSession = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n let agentName: string | undefined\n let workspace: string | undefined\n\n if (currentSession) {\n agentName = currentSession.agentName\n workspace = currentSession.workingDirectory\n } else {\n const record = adapter.core.sessionManager.getRecordByThread('discord', channelId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.editReply('No active session in this channel. Use `/new` to start one.')\n return\n }\n agentName = record.agentName\n workspace = record.workingDir\n }\n\n await executeNewSession(interaction, adapter, agentName, workspace)\n}\n\nexport async function executeNewSession(\n interaction: ChatInputCommandInteraction | ButtonInteraction,\n adapter: any,\n agentName?: string,\n workspace?: string,\n): Promise<void> {\n const config = adapter.core.configManager.get()\n const resolvedAgent = agentName || config.defaultAgent\n\n log.info({ agentName: resolvedAgent, workspace }, '[discord-new-session] Creating session')\n\n const forumChannel = adapter.getForumChannel()\n if (!forumChannel) {\n const msg = '❌ Forum channel not configured. Please run setup first.'\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(msg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: msg, ephemeral: true })\n }\n return\n }\n\n let thread: import('discord.js').ThreadChannel | undefined\n\n try {\n // Create forum thread BEFORE creating session to avoid race condition\n thread = await createSessionThread(forumChannel, `🔄 ${resolvedAgent} — New Session`)\n\n // Create session via core\n const session = await adapter.core.handleNewSession('discord', resolvedAgent, workspace)\n session.threadId = thread.id\n\n // Patch platform record with Discord thread ID\n await adapter.core.sessionManager.patchRecord(session.id, {\n platform: { threadId: thread.id },\n })\n\n // Send welcome message in the new thread\n const dangerousRow = buildDangerousModeKeyboard(session.id, false)\n await thread.send({\n content:\n `✅ **Session started**\\n` +\n `**Agent:** ${session.agentName}\\n` +\n `**Workspace:** \\`${session.workingDirectory}\\`\\n\\n` +\n `This is your coding session — chat here to work with the agent.`,\n components: [dangerousRow],\n })\n\n // Reply to the interaction with a link to the thread\n const replyMsg = `✅ Session created → [Open thread](https://discord.com/channels/${adapter.getGuildId()}/${thread.id})`\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(replyMsg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: replyMsg, ephemeral: true })\n }\n\n // Warm up model cache in background\n session.warmup().catch((err: unknown) => log.error({ err }, '[discord-new-session] Warm-up error'))\n } catch (err) {\n log.error({ err }, '[discord-new-session] Session creation failed')\n\n // Clean up orphaned thread on failure (archive+lock instead of permanent delete)\n if (thread) {\n try { await deleteSessionThread(adapter.getGuild(), thread.id) } catch { /* ignore */ }\n }\n\n const errMsg = `❌ ${err instanceof Error ? err.message : String(err)}`\n try {\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(errMsg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: errMsg, ephemeral: true })\n }\n } catch { /* ignore */ }\n }\n}\n\nexport async function handleNewSessionButton(\n interaction: ButtonInteraction,\n adapter: any,\n): Promise<void> {\n const { customId } = interaction\n\n if (customId.startsWith('m:new:agent:')) {\n const agentKey = customId.replace('m:new:agent:', '')\n try { await interaction.deferUpdate() } catch { /* ignore */ }\n await executeNewSession(interaction, adapter, agentKey, undefined)\n }\n}\n"],"mappings":"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","names":[]}
@@ -95,4 +95,4 @@ export {
95
95
  runUpdate,
96
96
  checkAndPromptUpdate
97
97
  };
98
- //# sourceMappingURL=chunk-KSIQZC3J.js.map
98
+ //# sourceMappingURL=chunk-EVFJW45N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/version.ts"],"sourcesContent":["import { fileURLToPath } from 'node:url'\nimport { dirname, join, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\n\nconst NPM_PACKAGE = '@openacp/cli'\n\nfunction findPackageJson(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, 'package.json')\n if (existsSync(candidate)) return candidate\n const parent = resolve(dir, '..')\n if (parent === dir) break\n dir = parent\n }\n return null\n}\n\nexport function getCurrentVersion(): string {\n try {\n const pkgPath = findPackageJson()\n if (!pkgPath) return '0.0.0-dev'\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n return pkg.version as string\n } catch {\n return '0.0.0-dev'\n }\n}\n\nexport async function getLatestVersion(): Promise<string | null> {\n try {\n const res = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {\n signal: AbortSignal.timeout(5000),\n })\n if (!res.ok) return null\n const data = (await res.json()) as { version?: string }\n return data.version ?? null\n } catch {\n return null\n }\n}\n\nexport function compareVersions(current: string, latest: string): -1 | 0 | 1 {\n const a = current.split('.').map(Number)\n const b = latest.split('.').map(Number)\n for (let i = 0; i < 3; i++) {\n if ((a[i] ?? 0) < (b[i] ?? 0)) return -1\n if ((a[i] ?? 0) > (b[i] ?? 0)) return 1\n }\n return 0\n}\n\nexport async function runUpdate(): Promise<boolean> {\n const { spawn } = await import('node:child_process')\n return new Promise((resolve) => {\n const child = spawn('npm', ['install', '-g', `${NPM_PACKAGE}@latest`], {\n stdio: 'inherit',\n shell: true,\n })\n const onSignal = () => {\n child.kill('SIGTERM')\n resolve(false)\n }\n process.on('SIGINT', onSignal)\n process.on('SIGTERM', onSignal)\n child.on('close', (code) => {\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n resolve(code === 0)\n })\n })\n}\n\nexport async function checkAndPromptUpdate(): Promise<void> {\n if (process.env.OPENACP_DEV_LOOP || process.env.OPENACP_SKIP_UPDATE_CHECK) return\n\n const current = getCurrentVersion()\n if (current === '0.0.0-dev') return\n\n const latest = await getLatestVersion()\n if (!latest || compareVersions(current, latest) >= 0) return\n\n console.log(`\\x1b[33mUpdate available: v${current} → v${latest}\\x1b[0m`)\n const { confirm } = await import('@inquirer/prompts')\n const yes = await confirm({\n message: 'Update now before starting?',\n default: true,\n })\n if (yes) {\n const ok = await runUpdate()\n if (ok) {\n console.log(`\\x1b[32m✓ Updated to v${latest}. Please re-run your command.\\x1b[0m`)\n process.exit(0)\n } else {\n console.error('\\x1b[31mUpdate failed. Continuing with current version.\\x1b[0m')\n }\n }\n}\n"],"mappings":"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","names":["resolve"]}