@contextgraph/agent 0.4.0 → 0.4.2

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/dist/index.js CHANGED
@@ -91,44 +91,120 @@ function getSuccessPage() {
91
91
  <head>
92
92
  <meta charset="utf-8">
93
93
  <title>Authentication Successful</title>
94
+ <link rel="preconnect" href="https://fonts.googleapis.com">
95
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
96
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
94
97
  <style>
98
+ :root {
99
+ --bg: hsl(0 0% 8%);
100
+ --tile-bg: hsl(0 0% 12%);
101
+ --cream: hsl(45 30% 85%);
102
+ --orange: hsl(30 95% 55%);
103
+ --subtitle: hsl(0 0% 55%);
104
+ --border: hsl(0 0% 20%);
105
+ }
106
+
107
+ * {
108
+ box-sizing: border-box;
109
+ }
110
+
95
111
  body {
96
- font-family: system-ui, -apple-system, sans-serif;
112
+ font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', 'Inconsolata', monospace;
97
113
  display: flex;
98
114
  align-items: center;
99
115
  justify-content: center;
100
116
  min-height: 100vh;
101
117
  margin: 0;
102
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
118
+ background: var(--bg);
119
+ padding: 1rem;
103
120
  }
121
+
104
122
  .container {
105
- background: white;
123
+ background: var(--tile-bg);
106
124
  padding: 3rem;
107
- border-radius: 1rem;
108
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
125
+ border-radius: 0.75rem;
126
+ border: 1px solid var(--border);
109
127
  text-align: center;
110
128
  max-width: 400px;
129
+ width: 100%;
111
130
  }
131
+
132
+ .icon-container {
133
+ width: 80px;
134
+ height: 80px;
135
+ margin: 0 auto 1.5rem;
136
+ background: hsl(145 50% 12%);
137
+ border-radius: 50%;
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ border: 2px solid hsl(145 50% 25%);
142
+ }
143
+
112
144
  .icon {
113
- font-size: 4rem;
114
- margin-bottom: 1rem;
145
+ width: 40px;
146
+ height: 40px;
147
+ stroke: hsl(145 70% 55%);
148
+ stroke-width: 3;
149
+ fill: none;
115
150
  }
151
+
116
152
  h1 {
117
- color: #667eea;
118
- margin: 0 0 1rem 0;
119
- font-size: 1.5rem;
153
+ color: var(--cream);
154
+ margin: 0 0 0.75rem 0;
155
+ font-size: 1.25rem;
156
+ font-weight: 500;
157
+ letter-spacing: -0.02em;
120
158
  }
159
+
121
160
  p {
122
- color: #666;
161
+ color: var(--subtitle);
123
162
  margin: 0;
163
+ font-size: 0.875rem;
164
+ line-height: 1.6;
165
+ }
166
+
167
+ .brand {
168
+ margin-top: 2rem;
169
+ padding-top: 1.5rem;
170
+ border-top: 1px solid var(--border);
171
+ }
172
+
173
+ .brand-text {
174
+ color: var(--orange);
175
+ font-size: 0.75rem;
176
+ font-weight: 500;
177
+ letter-spacing: 0.05em;
178
+ }
179
+
180
+ @keyframes check-draw {
181
+ 0% {
182
+ stroke-dashoffset: 24;
183
+ }
184
+ 100% {
185
+ stroke-dashoffset: 0;
186
+ }
187
+ }
188
+
189
+ .icon polyline {
190
+ stroke-dasharray: 24;
191
+ stroke-dashoffset: 24;
192
+ animation: check-draw 0.4s ease-out 0.2s forwards;
124
193
  }
125
194
  </style>
126
195
  </head>
127
196
  <body>
128
197
  <div class="container">
129
- <div class="icon">\u2705</div>
130
- <h1>Authentication successful!</h1>
198
+ <div class="icon-container">
199
+ <svg class="icon" viewBox="0 0 24 24">
200
+ <polyline points="4 12 10 18 20 6"></polyline>
201
+ </svg>
202
+ </div>
203
+ <h1>Authentication successful</h1>
131
204
  <p>You can close this window and return to your terminal.</p>
205
+ <div class="brand">
206
+ <span class="brand-text">CONTEXTGRAPH</span>
207
+ </div>
132
208
  </div>
133
209
  </body>
134
210
  </html>
@@ -141,44 +217,131 @@ function getErrorPage(message) {
141
217
  <head>
142
218
  <meta charset="utf-8">
143
219
  <title>Authentication Error</title>
220
+ <link rel="preconnect" href="https://fonts.googleapis.com">
221
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
222
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
144
223
  <style>
224
+ :root {
225
+ --bg: hsl(0 0% 8%);
226
+ --tile-bg: hsl(0 0% 12%);
227
+ --red: hsl(0 80% 60%);
228
+ --red-dim: hsl(0 50% 12%);
229
+ --red-border: hsl(0 50% 25%);
230
+ --cream: hsl(45 30% 85%);
231
+ --orange: hsl(30 95% 55%);
232
+ --subtitle: hsl(0 0% 55%);
233
+ --border: hsl(0 0% 20%);
234
+ }
235
+
236
+ * {
237
+ box-sizing: border-box;
238
+ }
239
+
145
240
  body {
146
- font-family: system-ui, -apple-system, sans-serif;
241
+ font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', 'Inconsolata', monospace;
147
242
  display: flex;
148
243
  align-items: center;
149
244
  justify-content: center;
150
245
  min-height: 100vh;
151
246
  margin: 0;
152
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
247
+ background: var(--bg);
248
+ padding: 1rem;
153
249
  }
250
+
154
251
  .container {
155
- background: white;
252
+ background: var(--tile-bg);
156
253
  padding: 3rem;
157
- border-radius: 1rem;
158
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
254
+ border-radius: 0.75rem;
255
+ border: 1px solid var(--border);
159
256
  text-align: center;
160
257
  max-width: 400px;
258
+ width: 100%;
259
+ }
260
+
261
+ .icon-container {
262
+ width: 80px;
263
+ height: 80px;
264
+ margin: 0 auto 1.5rem;
265
+ background: var(--red-dim);
266
+ border-radius: 50%;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ border: 2px solid var(--red-border);
161
271
  }
272
+
162
273
  .icon {
163
- font-size: 4rem;
164
- margin-bottom: 1rem;
274
+ width: 40px;
275
+ height: 40px;
276
+ stroke: var(--red);
277
+ stroke-width: 3;
278
+ fill: none;
165
279
  }
280
+
166
281
  h1 {
167
- color: #f5576c;
168
- margin: 0 0 1rem 0;
169
- font-size: 1.5rem;
282
+ color: var(--red);
283
+ margin: 0 0 0.75rem 0;
284
+ font-size: 1.25rem;
285
+ font-weight: 500;
286
+ letter-spacing: -0.02em;
170
287
  }
288
+
171
289
  p {
172
- color: #666;
290
+ color: var(--subtitle);
173
291
  margin: 0;
292
+ font-size: 0.875rem;
293
+ line-height: 1.6;
294
+ }
295
+
296
+ .brand {
297
+ margin-top: 2rem;
298
+ padding-top: 1.5rem;
299
+ border-top: 1px solid var(--border);
300
+ }
301
+
302
+ .brand-text {
303
+ color: var(--orange);
304
+ font-size: 0.75rem;
305
+ font-weight: 500;
306
+ letter-spacing: 0.05em;
307
+ }
308
+
309
+ @keyframes x-draw {
310
+ 0% {
311
+ stroke-dashoffset: 34;
312
+ }
313
+ 100% {
314
+ stroke-dashoffset: 0;
315
+ }
316
+ }
317
+
318
+ .icon line {
319
+ stroke-dasharray: 17;
320
+ stroke-dashoffset: 17;
321
+ }
322
+
323
+ .icon line:first-child {
324
+ animation: x-draw 0.3s ease-out 0.2s forwards;
325
+ }
326
+
327
+ .icon line:last-child {
328
+ animation: x-draw 0.3s ease-out 0.35s forwards;
174
329
  }
175
330
  </style>
176
331
  </head>
177
332
  <body>
178
333
  <div class="container">
179
- <div class="icon">\u274C</div>
334
+ <div class="icon-container">
335
+ <svg class="icon" viewBox="0 0 24 24">
336
+ <line x1="6" y1="6" x2="18" y2="18"></line>
337
+ <line x1="18" y1="6" x2="6" y2="18"></line>
338
+ </svg>
339
+ </div>
180
340
  <h1>Authentication error</h1>
181
341
  <p>${message}</p>
342
+ <div class="brand">
343
+ <span class="brand-text">CONTEXTGRAPH</span>
344
+ </div>
182
345
  </div>
183
346
  </body>
184
347
  </html>
@@ -191,33 +354,80 @@ function getNotFoundPage() {
191
354
  <head>
192
355
  <meta charset="utf-8">
193
356
  <title>Not Found</title>
357
+ <link rel="preconnect" href="https://fonts.googleapis.com">
358
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
359
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
194
360
  <style>
361
+ :root {
362
+ --bg: hsl(0 0% 8%);
363
+ --tile-bg: hsl(0 0% 12%);
364
+ --cream: hsl(45 30% 85%);
365
+ --orange: hsl(30 95% 55%);
366
+ --subtitle: hsl(0 0% 55%);
367
+ --border: hsl(0 0% 20%);
368
+ }
369
+
370
+ * {
371
+ box-sizing: border-box;
372
+ }
373
+
195
374
  body {
196
- font-family: system-ui, -apple-system, sans-serif;
375
+ font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', 'Inconsolata', monospace;
197
376
  display: flex;
198
377
  align-items: center;
199
378
  justify-content: center;
200
379
  min-height: 100vh;
201
380
  margin: 0;
202
- background: #f0f0f0;
381
+ background: var(--bg);
382
+ padding: 1rem;
203
383
  }
384
+
204
385
  .container {
205
- background: white;
386
+ background: var(--tile-bg);
206
387
  padding: 3rem;
207
- border-radius: 1rem;
208
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
388
+ border-radius: 0.75rem;
389
+ border: 1px solid var(--border);
209
390
  text-align: center;
210
391
  max-width: 400px;
392
+ width: 100%;
393
+ }
394
+
395
+ .code {
396
+ color: var(--orange);
397
+ font-size: 3rem;
398
+ font-weight: 700;
399
+ margin: 0 0 0.5rem 0;
400
+ letter-spacing: -0.02em;
211
401
  }
402
+
212
403
  h1 {
213
- color: #666;
404
+ color: var(--cream);
214
405
  margin: 0;
406
+ font-size: 1rem;
407
+ font-weight: 400;
408
+ }
409
+
410
+ .brand {
411
+ margin-top: 2rem;
412
+ padding-top: 1.5rem;
413
+ border-top: 1px solid var(--border);
414
+ }
415
+
416
+ .brand-text {
417
+ color: var(--orange);
418
+ font-size: 0.75rem;
419
+ font-weight: 500;
420
+ letter-spacing: 0.05em;
215
421
  }
216
422
  </style>
217
423
  </head>
218
424
  <body>
219
425
  <div class="container">
220
- <h1>404 Not Found</h1>
426
+ <div class="code">404</div>
427
+ <h1>Not Found</h1>
428
+ <div class="brand">
429
+ <span class="brand-text">CONTEXTGRAPH</span>
430
+ </div>
221
431
  </div>
222
432
  </body>
223
433
  </html>
@@ -1267,12 +1477,154 @@ ${errorText}`);
1267
1477
  }
1268
1478
  }
1269
1479
 
1480
+ // src/workflows/learn.ts
1481
+ var API_BASE_URL3 = "https://www.contextgraph.dev";
1482
+ async function runLearn(actionId, options) {
1483
+ const credentials = await loadCredentials();
1484
+ if (!credentials) {
1485
+ console.error("\u274C Not authenticated. Run authentication first.");
1486
+ process.exit(1);
1487
+ }
1488
+ if (isExpired(credentials) || isTokenExpired(credentials.clerkToken)) {
1489
+ console.error("\u274C Token expired. Re-authenticate to continue.");
1490
+ process.exit(1);
1491
+ }
1492
+ console.log(`Fetching learning instructions for action ${actionId}...
1493
+ `);
1494
+ const response = await fetchWithRetry(
1495
+ `${API_BASE_URL3}/api/prompts/learn`,
1496
+ {
1497
+ method: "POST",
1498
+ headers: {
1499
+ "Authorization": `Bearer ${credentials.clerkToken}`,
1500
+ "Content-Type": "application/json"
1501
+ },
1502
+ body: JSON.stringify({ actionId })
1503
+ }
1504
+ );
1505
+ if (!response.ok) {
1506
+ const errorText = await response.text();
1507
+ throw new Error(`Failed to fetch learn prompt: ${response.statusText}
1508
+ ${errorText}`);
1509
+ }
1510
+ const { prompt } = await response.json();
1511
+ const logTransport = new LogTransportService(API_BASE_URL3, credentials.clerkToken);
1512
+ let runId;
1513
+ let heartbeatManager;
1514
+ let logBuffer;
1515
+ try {
1516
+ console.log("[Log Streaming] Creating run for learn phase...");
1517
+ runId = await logTransport.createRun(actionId);
1518
+ console.log(`[Log Streaming] Run created: ${runId}`);
1519
+ await logTransport.updateRunState("learning");
1520
+ heartbeatManager = new HeartbeatManager(API_BASE_URL3, credentials.clerkToken, runId);
1521
+ heartbeatManager.start();
1522
+ console.log("[Log Streaming] Heartbeat started");
1523
+ logBuffer = new LogBuffer(logTransport);
1524
+ logBuffer.start();
1525
+ console.log("Spawning Claude for learning extraction...\n");
1526
+ const claudeResult = await executeClaude({
1527
+ prompt,
1528
+ cwd: options?.cwd || process.cwd(),
1529
+ authToken: credentials.clerkToken,
1530
+ onLogEvent: (event) => {
1531
+ logBuffer.push(event);
1532
+ }
1533
+ });
1534
+ if (claudeResult.exitCode === 0) {
1535
+ await logTransport.finishRun("success", {
1536
+ exitCode: claudeResult.exitCode,
1537
+ cost: claudeResult.cost,
1538
+ usage: claudeResult.usage
1539
+ });
1540
+ console.log("\n\u2705 Learning extraction complete");
1541
+ } else {
1542
+ await logTransport.finishRun("error", {
1543
+ exitCode: claudeResult.exitCode,
1544
+ errorMessage: `Claude learning extraction failed with exit code ${claudeResult.exitCode}`
1545
+ });
1546
+ console.error(`
1547
+ \u274C Claude learning extraction failed with exit code ${claudeResult.exitCode}`);
1548
+ process.exit(1);
1549
+ }
1550
+ } catch (error) {
1551
+ if (runId) {
1552
+ try {
1553
+ await logTransport.finishRun("error", {
1554
+ errorMessage: error instanceof Error ? error.message : String(error)
1555
+ });
1556
+ } catch (stateError) {
1557
+ console.error("[Log Streaming] Failed to update run state:", stateError);
1558
+ }
1559
+ }
1560
+ throw error;
1561
+ } finally {
1562
+ if (heartbeatManager) {
1563
+ heartbeatManager.stop();
1564
+ console.log("[Log Streaming] Heartbeat stopped");
1565
+ }
1566
+ if (logBuffer) {
1567
+ await logBuffer.stop();
1568
+ console.log("[Log Streaming] Logs flushed");
1569
+ }
1570
+ }
1571
+ }
1572
+
1270
1573
  // src/workflows/agent.ts
1271
1574
  import { randomUUID } from "crypto";
1272
1575
  import { readFileSync } from "fs";
1273
1576
  import { fileURLToPath } from "url";
1274
1577
  import { dirname, join as join3 } from "path";
1275
1578
 
1579
+ // package.json
1580
+ var package_default = {
1581
+ name: "@contextgraph/agent",
1582
+ version: "0.4.2",
1583
+ description: "Autonomous agent for contextgraph action execution",
1584
+ type: "module",
1585
+ bin: {
1586
+ "contextgraph-agent": "dist/index.js"
1587
+ },
1588
+ scripts: {
1589
+ build: "tsup",
1590
+ dev: "tsup --watch",
1591
+ test: "jest"
1592
+ },
1593
+ keywords: [
1594
+ "contextgraph",
1595
+ "agent",
1596
+ "autonomous",
1597
+ "cli"
1598
+ ],
1599
+ author: "contextgraph",
1600
+ license: "MIT",
1601
+ repository: {
1602
+ type: "git",
1603
+ url: "git+https://github.com/contextgraph/agent.git"
1604
+ },
1605
+ homepage: "https://github.com/contextgraph/agent#readme",
1606
+ bugs: {
1607
+ url: "https://github.com/contextgraph/agent/issues"
1608
+ },
1609
+ dependencies: {
1610
+ "@anthropic-ai/claude-agent-sdk": "^0.1.50",
1611
+ commander: "^11.0.0",
1612
+ open: "^10.0.0"
1613
+ },
1614
+ devDependencies: {
1615
+ "@jest/globals": "^30.2.0",
1616
+ "@types/jest": "^30.0.0",
1617
+ "@types/node": "^20.0.0",
1618
+ jest: "^30.2.0",
1619
+ "ts-jest": "^29.4.5",
1620
+ tsup: "^8.0.0",
1621
+ typescript: "^5.0.0"
1622
+ },
1623
+ engines: {
1624
+ node: ">=18.0.0"
1625
+ }
1626
+ };
1627
+
1276
1628
  // src/api-client.ts
1277
1629
  var ApiClient = class {
1278
1630
  constructor(baseUrl = "https://www.contextgraph.dev") {
@@ -1342,7 +1694,10 @@ var ApiClient = class {
1342
1694
  "x-authorization": `Bearer ${token}`,
1343
1695
  "Content-Type": "application/json"
1344
1696
  },
1345
- body: JSON.stringify({ worker_id: workerId })
1697
+ body: JSON.stringify({
1698
+ worker_id: workerId,
1699
+ agent_version: package_default.version
1700
+ })
1346
1701
  }
1347
1702
  );
1348
1703
  if (!response.ok) {
@@ -1384,9 +1739,9 @@ import { spawn as spawn2 } from "child_process";
1384
1739
  import { mkdtemp, rm } from "fs/promises";
1385
1740
  import { tmpdir } from "os";
1386
1741
  import { join as join2 } from "path";
1387
- var API_BASE_URL3 = "https://www.contextgraph.dev";
1742
+ var API_BASE_URL4 = "https://www.contextgraph.dev";
1388
1743
  async function fetchGitHubCredentials(authToken) {
1389
- const response = await fetchWithRetry(`${API_BASE_URL3}/api/cli/credentials`, {
1744
+ const response = await fetchWithRetry(`${API_BASE_URL4}/api/cli/credentials`, {
1390
1745
  headers: {
1391
1746
  "x-authorization": `Bearer ${authToken}`,
1392
1747
  "Content-Type": "application/json"
@@ -1502,6 +1857,7 @@ var apiClient = null;
1502
1857
  var stats = {
1503
1858
  startTime: Date.now(),
1504
1859
  prepared: 0,
1860
+ learned: 0,
1505
1861
  executed: 0,
1506
1862
  errors: 0
1507
1863
  };
@@ -1518,8 +1874,8 @@ function formatDuration(ms) {
1518
1874
  }
1519
1875
  function printStatus() {
1520
1876
  const uptime = formatDuration(Date.now() - stats.startTime);
1521
- const total = stats.prepared + stats.executed;
1522
- console.log(`Status: ${total} actions (${stats.prepared} prepared, ${stats.executed} executed, ${stats.errors} errors) | Uptime: ${uptime}`);
1877
+ const total = stats.prepared + stats.learned + stats.executed;
1878
+ console.log(`Status: ${total} actions (${stats.prepared} prepared, ${stats.learned} learned, ${stats.executed} executed, ${stats.errors} errors) | Uptime: ${uptime}`);
1523
1879
  }
1524
1880
  async function cleanupAndExit() {
1525
1881
  if (currentClaim && apiClient) {
@@ -1636,7 +1992,29 @@ async function runLocalAgent() {
1636
1992
  workerId
1637
1993
  };
1638
1994
  }
1639
- const isPrepared = actionDetail.prepared !== false;
1995
+ let phase;
1996
+ if (actionDetail.done && actionDetail.reviewed && !actionDetail.learned) {
1997
+ phase = "learn";
1998
+ } else if (!actionDetail.prepared) {
1999
+ phase = "prepare";
2000
+ } else if (!actionDetail.done) {
2001
+ phase = "execute";
2002
+ } else {
2003
+ console.log(`\u23ED\uFE0F Skipping action "${actionDetail.title}" - done but not yet reviewed`);
2004
+ if (currentClaim && apiClient) {
2005
+ try {
2006
+ await apiClient.releaseClaim({
2007
+ action_id: currentClaim.actionId,
2008
+ worker_id: currentClaim.workerId,
2009
+ claim_id: currentClaim.claimId
2010
+ });
2011
+ } catch (releaseError) {
2012
+ console.error("\u26A0\uFE0F Failed to release claim:", releaseError.message);
2013
+ }
2014
+ }
2015
+ currentClaim = null;
2016
+ continue;
2017
+ }
1640
2018
  const repoUrl = actionDetail.resolved_repository_url || actionDetail.repository_url;
1641
2019
  const branch = actionDetail.resolved_branch || actionDetail.branch;
1642
2020
  if (!repoUrl) {
@@ -1657,7 +2035,7 @@ async function runLocalAgent() {
1657
2035
  });
1658
2036
  workspacePath = workspace.path;
1659
2037
  cleanup = workspace.cleanup;
1660
- if (!isPrepared) {
2038
+ if (phase === "prepare") {
1661
2039
  await runPrepare(actionDetail.id, { cwd: workspacePath });
1662
2040
  stats.prepared++;
1663
2041
  if (currentClaim && apiClient) {
@@ -1674,6 +2052,30 @@ async function runLocalAgent() {
1674
2052
  currentClaim = null;
1675
2053
  continue;
1676
2054
  }
2055
+ if (phase === "learn") {
2056
+ try {
2057
+ await runLearn(actionDetail.id, { cwd: workspacePath });
2058
+ stats.learned++;
2059
+ console.log(`Learning extracted: ${actionDetail.title}`);
2060
+ } catch (learnError) {
2061
+ stats.errors++;
2062
+ console.error(`Error during learning: ${learnError.message}. Continuing...`);
2063
+ } finally {
2064
+ if (currentClaim && apiClient) {
2065
+ try {
2066
+ await apiClient.releaseClaim({
2067
+ action_id: currentClaim.actionId,
2068
+ worker_id: currentClaim.workerId,
2069
+ claim_id: currentClaim.claimId
2070
+ });
2071
+ } catch (releaseError) {
2072
+ console.error("\u26A0\uFE0F Failed to release claim:", releaseError.message);
2073
+ }
2074
+ }
2075
+ currentClaim = null;
2076
+ }
2077
+ continue;
2078
+ }
1677
2079
  try {
1678
2080
  await runExecute(actionDetail.id, { cwd: workspacePath });
1679
2081
  stats.executed++;
@@ -1768,6 +2170,14 @@ program.command("execute").argument("<action-id>", "Action ID to execute").descr
1768
2170
  process.exit(1);
1769
2171
  }
1770
2172
  });
2173
+ program.command("learn").argument("<action-id>", "Action ID to learn from").description("Extract learnings from a completed action").action(async (actionId) => {
2174
+ try {
2175
+ await runLearn(actionId);
2176
+ } catch (error) {
2177
+ console.error("Error learning from action:", error instanceof Error ? error.message : error);
2178
+ process.exit(1);
2179
+ }
2180
+ });
1771
2181
  program.command("whoami").description("Show current authentication status").action(async () => {
1772
2182
  try {
1773
2183
  const credentials = await loadCredentials();