alchemylab 1.0.4 → 1.0.6

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.
@@ -26,6 +26,8 @@ function extractRequestId(text) {
26
26
  /Request finalized:\s*(req\d+)/i,
27
27
  /Request (req\d+) finalized/i,
28
28
  /finalized.*?(req\d{6})/i,
29
+ /Request (req\d+) merged/i,
30
+ /merged.*?(req\d{6})/i,
29
31
  ];
30
32
  for (const pattern of patterns) {
31
33
  const match = text.match(pattern);
@@ -87,12 +89,33 @@ function extractToolEventInfo(toolName, input, resultText, sessionId) {
87
89
  const isDocumentOps = toolName.includes('document_operations');
88
90
  const isQueryHistory = toolName.includes('query_history');
89
91
  if (isRequestOps && resultText) {
92
+ const toolArgs = input?.args || input?.input || {};
93
+ const operation = toolArgs.operation;
90
94
  const requestId = extractRequestId(resultText);
95
+ // Debug: log what we see for request ops to diagnose hook firing
96
+ try {
97
+ const fs = require('fs'), path = require('path');
98
+ const debugPath = path.join(process.env.ALCHEMY_PROJECT_PATH || process.cwd(), '.alchemy_lab', 'hook-debug.log');
99
+ const inputKeys = Object.keys(input || {}).join(',');
100
+ const inputInputKeys = Object.keys(input?.input || {}).join(',');
101
+ fs.appendFileSync(debugPath, `[${new Date().toISOString()}] request_ops: tool=${toolName} op=${operation} reqId=${requestId} inputKeys=[${inputKeys}] input.inputKeys=[${inputInputKeys}] input.input.op=${input?.input?.operation} input.operation=${input?.operation} result=${resultText?.substring(0, 80)}\n`);
102
+ }
103
+ catch { }
91
104
  if (requestId) {
92
- events.push({
93
- eventType: 'request_completed',
94
- variables: { ...baseVars, requestId },
95
- });
105
+ if (operation === 'merge' || operation === 'finalize') {
106
+ // Actual merge to main
107
+ events.push({
108
+ eventType: 'request_completed',
109
+ variables: { ...baseVars, requestId },
110
+ });
111
+ }
112
+ else if (operation === 'complete') {
113
+ // Phase 1 — work done, ready for merge
114
+ events.push({
115
+ eventType: 'request_ready_for_merge',
116
+ variables: { ...baseVars, requestId },
117
+ });
118
+ }
96
119
  }
97
120
  }
98
121
  else if (isTaskOps && resultText) {
@@ -112,7 +135,16 @@ function extractToolEventInfo(toolName, input, resultText, sessionId) {
112
135
  });
113
136
  }
114
137
  else if (isReplaceInFiles) {
115
- const operations = input?.input?.operations || [];
138
+ const replaceArgs = input?.args || input?.input || {};
139
+ let operations = replaceArgs.operations || [];
140
+ if (typeof operations === 'string') {
141
+ try {
142
+ operations = JSON.parse(operations);
143
+ }
144
+ catch {
145
+ operations = [];
146
+ }
147
+ }
116
148
  const paths = operations.map((op) => op.path || op.documentId).filter(Boolean);
117
149
  events.push({
118
150
  eventType: 'replace_in_files',
@@ -122,8 +154,8 @@ function extractToolEventInfo(toolName, input, resultText, sessionId) {
122
154
  else if (isQuickEdit) {
123
155
  const vars = {
124
156
  ...baseVars,
125
- path: input?.input?.operationArgs?.path || '',
126
- title: input?.input?.title || '',
157
+ path: (input?.args || input?.input)?.operationArgs?.path || '',
158
+ title: (input?.args || input?.input)?.title || '',
127
159
  };
128
160
  const qeRequestMatch = resultText?.match(/\*\*Request:\*\*\s*(req\d+)/i);
129
161
  if (qeRequestMatch)
@@ -138,7 +170,15 @@ function extractToolEventInfo(toolName, input, resultText, sessionId) {
138
170
  }
139
171
  }
140
172
  else if (isReadFiles) {
141
- const pathsInput = input?.input?.paths || [];
173
+ let pathsInput = (input?.args || input?.input)?.paths || [];
174
+ if (typeof pathsInput === 'string') {
175
+ try {
176
+ pathsInput = JSON.parse(pathsInput);
177
+ }
178
+ catch {
179
+ pathsInput = [];
180
+ }
181
+ }
142
182
  const paths = pathsInput.map((p) => p.path || p.documentId).filter(Boolean);
143
183
  events.push({
144
184
  eventType: 'read_files',
@@ -150,15 +190,24 @@ function extractToolEventInfo(toolName, input, resultText, sessionId) {
150
190
  eventType: 'search',
151
191
  variables: {
152
192
  ...baseVars,
153
- query: input?.input?.needle || '',
154
- directory: input?.input?.directory || '',
155
- fileExtension: input?.input?.options?.fileExtension || '*',
193
+ query: (input?.args || input?.input)?.needle || '',
194
+ directory: (input?.args || input?.input)?.directory || '',
195
+ fileExtension: (input?.args || input?.input)?.options?.fileExtension || '*',
156
196
  },
157
197
  });
158
198
  }
159
199
  else if (isManageFiles) {
160
- const operations = input?.input?.operations || [];
161
- const opTypes = operations.map((op) => op.operationType).filter(Boolean);
200
+ const manageArgs = input?.args || input?.input || {};
201
+ let manageOps = manageArgs.operations || [];
202
+ if (typeof manageOps === 'string') {
203
+ try {
204
+ manageOps = JSON.parse(manageOps);
205
+ }
206
+ catch {
207
+ manageOps = [];
208
+ }
209
+ }
210
+ const opTypes = manageOps.map((op) => op.operationType).filter(Boolean);
162
211
  events.push({
163
212
  eventType: 'manage_files',
164
213
  variables: { ...baseVars, operationTypes: [...new Set(opTypes)].join(', ') },
@@ -225,7 +274,7 @@ exports.default = async ({ directory }) => {
225
274
  if (!toolName)
226
275
  return;
227
276
  const sessionId = input?.sessionID || '';
228
- const inputText = JSON.stringify(input?.input || {}).substring(0, 1000);
277
+ const inputText = JSON.stringify(input?.args || input?.input || {}).substring(0, 1000);
229
278
  postHookEvent('tool_executed', sessionId, {
230
279
  tool: toolName,
231
280
  toolName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "private": false,
5
5
  "description": "AlchemyLab - agentic coding IDEm",
6
6
  "author": "Testfire Software",
@@ -30,7 +30,7 @@
30
30
  "build": "npm run build:dev",
31
31
  "build:dev": "tsc -p tsconfig.json && chmod +x dist-dev/*.js",
32
32
  "build:prod": "webpack --mode=production && chmod +x dist/*.js && npm run copy:plugins",
33
- "copy:plugins": "cp -r dist-dev/plugins dist/plugins",
33
+ "copy:plugins": "mkdir -p dist/plugins/hooks/lib && cp dist-dev/plugins/hooks/plugin.js dist/plugins/hooks/ && cp dist-dev/plugins/hooks/lib/*.js dist/plugins/hooks/lib/ && cp dist-dev/plugins/inject-session.js dist/plugins/ && cp dist-dev/plugins/system-logger.js dist/plugins/",
34
34
  "build:analyze": "ANALYZE=true webpack --mode=production",
35
35
  "test": "npm run build:dev && mocha --require ts-node/register test/**/*.test.ts",
36
36
  "test:watch": "mocha --require ts-node/register test/**/*.test.ts --watch --watch-files src/**/*.ts,test/**/*.ts",
@@ -1,399 +0,0 @@
1
- "use strict";
2
- /**
3
- * Alchemy Anthropic Auth Plugin for OpenCode
4
- *
5
- * Modified version of opencode-anthropic-auth that uses Title Case for tool names
6
- * to work around Anthropic's detection of third-party clients.
7
- *
8
- * Based on opencode-anthropic-auth@0.0.5
9
- */
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.default = AlchemyAnthropicAuthPlugin;
12
- const fs_1 = require("fs");
13
- const path_1 = require("path");
14
- // Debug logging to file
15
- const LOG_FILE = (0, path_1.join)(process.env.HOME || '/tmp', 'anthropic-auth-plugin.log');
16
- function log(message, data) {
17
- try {
18
- const timestamp = new Date().toISOString();
19
- const line = data
20
- ? `${timestamp} [anthropic-auth] ${message} ${JSON.stringify(data)}\n`
21
- : `${timestamp} [anthropic-auth] ${message}\n`;
22
- (0, fs_1.appendFileSync)(LOG_FILE, line);
23
- }
24
- catch (e) {
25
- // ignore logging errors
26
- }
27
- }
28
- // Log immediately when module is loaded
29
- log('MODULE LOADED - anthropic-auth plugin file imported');
30
- const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
31
- /**
32
- * Convert string to Title Case (first letter uppercase, rest lowercase for each word)
33
- */
34
- function toTitleCase(str) {
35
- return str.replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase());
36
- }
37
- /**
38
- * Convert Title Case back to original (lowercase with underscores preserved)
39
- */
40
- function fromTitleCase(str) {
41
- return str.replace(/\w\S*/g, text => text.toLowerCase());
42
- }
43
- /**
44
- * Generate PKCE challenge and verifier
45
- * Simple implementation that doesn't require external dependencies
46
- */
47
- async function generatePKCE() {
48
- // Generate random verifier (43-128 characters of unreserved URI characters)
49
- const array = new Uint8Array(32);
50
- crypto.getRandomValues(array);
51
- const verifier = btoa(String.fromCharCode(...array))
52
- .replace(/\+/g, '-')
53
- .replace(/\//g, '_')
54
- .replace(/=/g, '');
55
- // Generate challenge from verifier using SHA-256
56
- const encoder = new TextEncoder();
57
- const data = encoder.encode(verifier);
58
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
59
- const hashArray = new Uint8Array(hashBuffer);
60
- const challenge = btoa(String.fromCharCode(...hashArray))
61
- .replace(/\+/g, '-')
62
- .replace(/\//g, '_')
63
- .replace(/=/g, '');
64
- return { verifier, challenge };
65
- }
66
- async function authorize(mode) {
67
- log('authorize called', { mode });
68
- try {
69
- const pkce = await generatePKCE();
70
- log('PKCE generated', { challenge: pkce.challenge?.substring(0, 20) + '...' });
71
- const url = new URL(`https://${mode === "console" ? "console.anthropic.com" : "claude.ai"}/oauth/authorize`);
72
- url.searchParams.set("code", "true");
73
- url.searchParams.set("client_id", CLIENT_ID);
74
- url.searchParams.set("response_type", "code");
75
- url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback");
76
- url.searchParams.set("scope", "org:create_api_key user:profile user:inference");
77
- url.searchParams.set("code_challenge", pkce.challenge);
78
- url.searchParams.set("code_challenge_method", "S256");
79
- url.searchParams.set("state", pkce.verifier);
80
- log('authorize URL created', { url: url.toString() });
81
- return {
82
- url: url.toString(),
83
- verifier: pkce.verifier,
84
- };
85
- }
86
- catch (e) {
87
- log('ERROR in authorize', { error: String(e), stack: e?.stack });
88
- throw e;
89
- }
90
- }
91
- async function exchange(code, verifier) {
92
- log('exchange called');
93
- try {
94
- const splits = code.split("#");
95
- const result = await fetch("https://console.anthropic.com/v1/oauth/token", {
96
- method: "POST",
97
- headers: {
98
- "Content-Type": "application/json",
99
- },
100
- body: JSON.stringify({
101
- code: splits[0],
102
- state: splits[1],
103
- grant_type: "authorization_code",
104
- client_id: CLIENT_ID,
105
- redirect_uri: "https://console.anthropic.com/oauth/code/callback",
106
- code_verifier: verifier,
107
- }),
108
- });
109
- if (!result.ok) {
110
- log('exchange failed', { status: result.status });
111
- return {
112
- type: "failed",
113
- };
114
- }
115
- const json = await result.json();
116
- log('exchange successful');
117
- return {
118
- type: "success",
119
- refresh: json.refresh_token,
120
- access: json.access_token,
121
- expires: Date.now() + json.expires_in * 1000,
122
- };
123
- }
124
- catch (e) {
125
- log('ERROR in exchange', { error: String(e), stack: e?.stack });
126
- throw e;
127
- }
128
- }
129
- async function AlchemyAnthropicAuthPlugin({ client }) {
130
- log('AlchemyAnthropicAuthPlugin called', { hasClient: !!client });
131
- try {
132
- return {
133
- auth: {
134
- provider: "anthropic",
135
- async loader(getAuth, provider) {
136
- log('loader called', { hasGetAuth: !!getAuth, hasProvider: !!provider });
137
- try {
138
- const auth = await getAuth();
139
- log('getAuth returned', { type: auth?.type });
140
- if (auth.type === "oauth") {
141
- log('OAuth auth detected, setting up custom fetch');
142
- // Zero out cost for max plan
143
- for (const model of Object.values(provider.models || {})) {
144
- model.cost = {
145
- input: 0,
146
- output: 0,
147
- cache: {
148
- read: 0,
149
- write: 0,
150
- },
151
- };
152
- }
153
- return {
154
- apiKey: "",
155
- async fetch(input, init) {
156
- log('custom fetch called', {
157
- inputType: typeof input,
158
- hasInit: !!init
159
- });
160
- try {
161
- const currentAuth = await getAuth();
162
- if (currentAuth.type !== "oauth") {
163
- log('Not OAuth, using default fetch');
164
- return fetch(input, init);
165
- }
166
- let authAccess = currentAuth.access;
167
- if (!authAccess || (currentAuth.expires && currentAuth.expires < Date.now())) {
168
- log('Token expired or missing, refreshing');
169
- const response = await fetch("https://console.anthropic.com/v1/oauth/token", {
170
- method: "POST",
171
- headers: {
172
- "Content-Type": "application/json",
173
- },
174
- body: JSON.stringify({
175
- grant_type: "refresh_token",
176
- refresh_token: currentAuth.refresh,
177
- client_id: CLIENT_ID,
178
- }),
179
- });
180
- if (!response.ok) {
181
- log('Token refresh failed', { status: response.status });
182
- throw new Error(`Token refresh failed: ${response.status}`);
183
- }
184
- const json = await response.json();
185
- log('Token refreshed successfully');
186
- await client.auth.set({
187
- path: {
188
- id: "anthropic",
189
- },
190
- body: {
191
- type: "oauth",
192
- refresh: json.refresh_token,
193
- access: json.access_token,
194
- expires: Date.now() + json.expires_in * 1000,
195
- },
196
- });
197
- authAccess = json.access_token;
198
- }
199
- const requestInit = init ?? {};
200
- const requestHeaders = new Headers();
201
- if (input instanceof Request) {
202
- input.headers.forEach((value, key) => {
203
- requestHeaders.set(key, value);
204
- });
205
- }
206
- if (requestInit.headers) {
207
- if (requestInit.headers instanceof Headers) {
208
- requestInit.headers.forEach((value, key) => {
209
- requestHeaders.set(key, value);
210
- });
211
- }
212
- else if (Array.isArray(requestInit.headers)) {
213
- for (const [key, value] of requestInit.headers) {
214
- if (typeof value !== "undefined") {
215
- requestHeaders.set(key, String(value));
216
- }
217
- }
218
- }
219
- else {
220
- for (const [key, value] of Object.entries(requestInit.headers)) {
221
- if (typeof value !== "undefined") {
222
- requestHeaders.set(key, String(value));
223
- }
224
- }
225
- }
226
- }
227
- const incomingBeta = requestHeaders.get("anthropic-beta") || "";
228
- const incomingBetasList = incomingBeta
229
- .split(",")
230
- .map((b) => b.trim())
231
- .filter(Boolean);
232
- const includeClaudeCode = incomingBetasList.includes("claude-code-20250219");
233
- const mergedBetas = [
234
- "oauth-2025-04-20",
235
- "interleaved-thinking-2025-05-14",
236
- ...(includeClaudeCode ? ["claude-code-20250219"] : []),
237
- ].join(",");
238
- requestHeaders.set("authorization", `Bearer ${authAccess}`);
239
- requestHeaders.set("anthropic-beta", mergedBetas);
240
- requestHeaders.set("user-agent", "claude-cli/2.1.2 (external, cli)");
241
- requestHeaders.delete("x-api-key");
242
- // Transform tool names to Title Case
243
- let body = requestInit.body;
244
- if (body && typeof body === "string") {
245
- try {
246
- const parsed = JSON.parse(body);
247
- if (parsed.tools && Array.isArray(parsed.tools)) {
248
- log('Transforming tool names to Title Case', {
249
- count: parsed.tools.length,
250
- names: parsed.tools.map((t) => t.name).slice(0, 5)
251
- });
252
- parsed.tools = parsed.tools.map((tool) => ({
253
- ...tool,
254
- name: tool.name ? toTitleCase(tool.name) : tool.name,
255
- }));
256
- body = JSON.stringify(parsed);
257
- }
258
- }
259
- catch (e) {
260
- log('Error parsing body for tool transformation', { error: String(e) });
261
- }
262
- }
263
- let requestInput = input;
264
- let requestUrl = null;
265
- try {
266
- if (typeof input === "string" || input instanceof URL) {
267
- requestUrl = new URL(input.toString());
268
- }
269
- else if (input instanceof Request) {
270
- requestUrl = new URL(input.url);
271
- }
272
- }
273
- catch {
274
- requestUrl = null;
275
- }
276
- if (requestUrl &&
277
- requestUrl.pathname === "/v1/messages" &&
278
- !requestUrl.searchParams.has("beta")) {
279
- requestUrl.searchParams.set("beta", "true");
280
- requestInput =
281
- input instanceof Request
282
- ? new Request(requestUrl.toString(), input)
283
- : requestUrl;
284
- }
285
- log('Making authenticated request', {
286
- url: requestUrl?.toString()?.substring(0, 100)
287
- });
288
- const response = await fetch(requestInput, {
289
- ...requestInit,
290
- body,
291
- headers: requestHeaders,
292
- });
293
- log('Got response', { status: response.status });
294
- // Transform streaming response to convert Title Case tool names back
295
- if (response.body) {
296
- const reader = response.body.getReader();
297
- const decoder = new TextDecoder();
298
- const encoder = new TextEncoder();
299
- const stream = new ReadableStream({
300
- async pull(controller) {
301
- const { done, value } = await reader.read();
302
- if (done) {
303
- controller.close();
304
- return;
305
- }
306
- let text = decoder.decode(value, { stream: true });
307
- // Convert Title Case tool names back to lowercase
308
- text = text.replace(/"name"\s*:\s*"([A-Z][^"]+)"/g, (_match, name) => {
309
- return `"name": "${fromTitleCase(name)}"`;
310
- });
311
- controller.enqueue(encoder.encode(text));
312
- },
313
- });
314
- return new Response(stream, {
315
- status: response.status,
316
- statusText: response.statusText,
317
- headers: response.headers,
318
- });
319
- }
320
- return response;
321
- }
322
- catch (e) {
323
- log('ERROR in custom fetch', { error: String(e), stack: e?.stack });
324
- throw e;
325
- }
326
- },
327
- };
328
- }
329
- log('Not OAuth auth, returning empty config');
330
- return {};
331
- }
332
- catch (e) {
333
- log('ERROR in loader', { error: String(e), stack: e?.stack });
334
- throw e;
335
- }
336
- },
337
- methods: [
338
- {
339
- label: "Claude Pro/Max",
340
- type: "oauth",
341
- authorize: async () => {
342
- log('Claude Pro/Max authorize method called');
343
- const { url, verifier } = await authorize("max");
344
- return {
345
- url: url,
346
- instructions: "Paste the authorization code here: ",
347
- method: "code",
348
- callback: async (code) => {
349
- log('Claude Pro/Max callback called');
350
- const credentials = await exchange(code, verifier);
351
- return credentials;
352
- },
353
- };
354
- },
355
- },
356
- {
357
- label: "Create an API Key",
358
- type: "oauth",
359
- authorize: async () => {
360
- log('Create an API Key authorize method called');
361
- const { url, verifier } = await authorize("console");
362
- return {
363
- url: url,
364
- instructions: "Paste the authorization code here: ",
365
- method: "code",
366
- callback: async (code) => {
367
- log('Create an API Key callback called');
368
- const credentials = await exchange(code, verifier);
369
- if (credentials.type === "failed")
370
- return credentials;
371
- const result = await fetch(`https://api.anthropic.com/api/oauth/claude_cli/create_api_key`, {
372
- method: "POST",
373
- headers: {
374
- "Content-Type": "application/json",
375
- authorization: `Bearer ${credentials.access}`,
376
- },
377
- }).then((r) => r.json());
378
- log('API key created');
379
- return { type: "success", key: result.raw_key };
380
- },
381
- };
382
- },
383
- },
384
- {
385
- provider: "anthropic",
386
- label: "Manually enter API Key",
387
- type: "api",
388
- },
389
- ],
390
- },
391
- };
392
- }
393
- catch (e) {
394
- log('ERROR in AlchemyAnthropicAuthPlugin', { error: String(e), stack: e?.stack });
395
- throw e;
396
- }
397
- }
398
- log('Plugin module fully loaded');
399
- //# sourceMappingURL=anthropic-auth.js.map
@@ -1,83 +0,0 @@
1
- "use strict";
2
- /**
3
- * OpenCode Auto-Journal Plugin
4
- *
5
- * Automatically generates journal entries when requests are completed.
6
- * Hooks into tool.execute.after to detect request-operations complete calls.
7
- *
8
- * Checks .alchemy_lab/config.json for autoJournal setting before triggering.
9
- */
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- const child_process_1 = require("child_process");
12
- const path_1 = require("path");
13
- const fs_1 = require("fs");
14
- /**
15
- * Check if autoJournal is enabled in config
16
- */
17
- function isAutoJournalEnabled(directory) {
18
- try {
19
- const configPath = (0, path_1.join)(directory || process.cwd(), '.alchemy_lab', 'config.json');
20
- const configContent = (0, fs_1.readFileSync)(configPath, 'utf8');
21
- const config = JSON.parse(configContent);
22
- return config.autoJournal === true;
23
- }
24
- catch {
25
- // Config doesn't exist or can't be read - default to false
26
- return false;
27
- }
28
- }
29
- exports.default = async ({ project, directory }) => {
30
- return {
31
- "tool.execute.after": async (input, output) => {
32
- try {
33
- // Check if this is request-operations
34
- const toolName = input?.tool || input?.name;
35
- if (!toolName || !toolName.includes('request-operations')) {
36
- return; // Not the tool we're looking for
37
- }
38
- // Check if autoJournal is enabled in config
39
- if (!isAutoJournalEnabled(directory)) {
40
- return; // Auto-journal is disabled
41
- }
42
- // Check the output content for the operation result
43
- if (output?.content && Array.isArray(output.content)) {
44
- if (output.content[0]?.text) {
45
- const resultText = output.content[0].text;
46
- // Check if this was a complete operation - look for any message about completing a request
47
- // Format is "Request completed: req123" or similar
48
- const completionMatch = resultText.match(/Request completed:\s*(req\d+)/i);
49
- if (completionMatch) {
50
- const requestId = completionMatch[1];
51
- // Build the prompt for opencode
52
- const prompt = `Generate a journal entry for request ${requestId}.
53
-
54
- Steps:
55
- 1. Use ${toolName.replace(/_request_operations$/, '__generate-request-journal')} tool with requestId: ${requestId}
56
- 2. Write a concise narrative journal entry based on the returned data covering:
57
- - What was implemented and why
58
- - Key decisions and trade-offs made
59
- - Challenges encountered and solutions
60
- - Files modified and their purposes
61
- - How acceptance criteria were met
62
- 3. Save as JOURNAL-${requestId}.md using ${toolName.replace(/_request_operations$/, '__document-operations')} (operation: create, type: Report)
63
-
64
- Keep it story-focused and informative.`;
65
- // Spawn opencode in background (detached) using 'run' command with journal mode
66
- // OpenCode will find opencode.json in cwd automatically
67
- const child = (0, child_process_1.spawn)("opencode", ["run", "--agent", "journal", prompt], {
68
- detached: true,
69
- stdio: "ignore",
70
- cwd: directory || process.cwd()
71
- });
72
- child.unref();
73
- }
74
- }
75
- }
76
- }
77
- catch (error) {
78
- // Silently ignore errors to avoid breaking the main session
79
- }
80
- }
81
- };
82
- };
83
- //# sourceMappingURL=auto-journal.js.map