clementine-agent 1.0.0

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 (190) hide show
  1. package/.env.example +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +795 -0
  4. package/dist/agent/agent-manager.d.ts +69 -0
  5. package/dist/agent/agent-manager.js +441 -0
  6. package/dist/agent/assistant.d.ts +225 -0
  7. package/dist/agent/assistant.js +3888 -0
  8. package/dist/agent/auto-update.d.ts +32 -0
  9. package/dist/agent/auto-update.js +186 -0
  10. package/dist/agent/daily-planner.d.ts +24 -0
  11. package/dist/agent/daily-planner.js +379 -0
  12. package/dist/agent/execution-advisor.d.ts +10 -0
  13. package/dist/agent/execution-advisor.js +272 -0
  14. package/dist/agent/hooks.d.ts +45 -0
  15. package/dist/agent/hooks.js +564 -0
  16. package/dist/agent/insight-engine.d.ts +66 -0
  17. package/dist/agent/insight-engine.js +225 -0
  18. package/dist/agent/intent-classifier.d.ts +48 -0
  19. package/dist/agent/intent-classifier.js +214 -0
  20. package/dist/agent/link-extractor.d.ts +19 -0
  21. package/dist/agent/link-extractor.js +90 -0
  22. package/dist/agent/mcp-bridge.d.ts +62 -0
  23. package/dist/agent/mcp-bridge.js +435 -0
  24. package/dist/agent/metacognition.d.ts +66 -0
  25. package/dist/agent/metacognition.js +221 -0
  26. package/dist/agent/orchestrator.d.ts +81 -0
  27. package/dist/agent/orchestrator.js +790 -0
  28. package/dist/agent/profiles.d.ts +22 -0
  29. package/dist/agent/profiles.js +91 -0
  30. package/dist/agent/prompt-cache.d.ts +24 -0
  31. package/dist/agent/prompt-cache.js +68 -0
  32. package/dist/agent/prompt-evolver.d.ts +28 -0
  33. package/dist/agent/prompt-evolver.js +279 -0
  34. package/dist/agent/role-scaffolds.d.ts +28 -0
  35. package/dist/agent/role-scaffolds.js +433 -0
  36. package/dist/agent/safe-restart.d.ts +41 -0
  37. package/dist/agent/safe-restart.js +150 -0
  38. package/dist/agent/self-improve.d.ts +66 -0
  39. package/dist/agent/self-improve.js +1706 -0
  40. package/dist/agent/session-event-log.d.ts +114 -0
  41. package/dist/agent/session-event-log.js +233 -0
  42. package/dist/agent/skill-extractor.d.ts +72 -0
  43. package/dist/agent/skill-extractor.js +435 -0
  44. package/dist/agent/source-mods.d.ts +61 -0
  45. package/dist/agent/source-mods.js +230 -0
  46. package/dist/agent/source-preflight.d.ts +25 -0
  47. package/dist/agent/source-preflight.js +100 -0
  48. package/dist/agent/stall-guard.d.ts +62 -0
  49. package/dist/agent/stall-guard.js +109 -0
  50. package/dist/agent/strategic-planner.d.ts +60 -0
  51. package/dist/agent/strategic-planner.js +352 -0
  52. package/dist/agent/team-bus.d.ts +89 -0
  53. package/dist/agent/team-bus.js +556 -0
  54. package/dist/agent/team-router.d.ts +26 -0
  55. package/dist/agent/team-router.js +37 -0
  56. package/dist/agent/tool-loop-detector.d.ts +59 -0
  57. package/dist/agent/tool-loop-detector.js +242 -0
  58. package/dist/agent/workflow-runner.d.ts +36 -0
  59. package/dist/agent/workflow-runner.js +317 -0
  60. package/dist/agent/workflow-variables.d.ts +16 -0
  61. package/dist/agent/workflow-variables.js +62 -0
  62. package/dist/channels/discord-agent-bot.d.ts +101 -0
  63. package/dist/channels/discord-agent-bot.js +881 -0
  64. package/dist/channels/discord-bot-manager.d.ts +80 -0
  65. package/dist/channels/discord-bot-manager.js +262 -0
  66. package/dist/channels/discord-utils.d.ts +51 -0
  67. package/dist/channels/discord-utils.js +293 -0
  68. package/dist/channels/discord.d.ts +12 -0
  69. package/dist/channels/discord.js +1832 -0
  70. package/dist/channels/slack-agent-bot.d.ts +73 -0
  71. package/dist/channels/slack-agent-bot.js +320 -0
  72. package/dist/channels/slack-bot-manager.d.ts +66 -0
  73. package/dist/channels/slack-bot-manager.js +236 -0
  74. package/dist/channels/slack-utils.d.ts +39 -0
  75. package/dist/channels/slack-utils.js +189 -0
  76. package/dist/channels/slack.d.ts +11 -0
  77. package/dist/channels/slack.js +196 -0
  78. package/dist/channels/telegram.d.ts +10 -0
  79. package/dist/channels/telegram.js +235 -0
  80. package/dist/channels/webhook.d.ts +9 -0
  81. package/dist/channels/webhook.js +78 -0
  82. package/dist/channels/whatsapp.d.ts +11 -0
  83. package/dist/channels/whatsapp.js +181 -0
  84. package/dist/cli/chat.d.ts +14 -0
  85. package/dist/cli/chat.js +220 -0
  86. package/dist/cli/cron.d.ts +17 -0
  87. package/dist/cli/cron.js +552 -0
  88. package/dist/cli/dashboard.d.ts +15 -0
  89. package/dist/cli/dashboard.js +17677 -0
  90. package/dist/cli/index.d.ts +3 -0
  91. package/dist/cli/index.js +2474 -0
  92. package/dist/cli/routes/delegations.d.ts +19 -0
  93. package/dist/cli/routes/delegations.js +154 -0
  94. package/dist/cli/routes/digest.d.ts +17 -0
  95. package/dist/cli/routes/digest.js +375 -0
  96. package/dist/cli/routes/goals.d.ts +14 -0
  97. package/dist/cli/routes/goals.js +258 -0
  98. package/dist/cli/routes/workflows.d.ts +18 -0
  99. package/dist/cli/routes/workflows.js +97 -0
  100. package/dist/cli/setup.d.ts +8 -0
  101. package/dist/cli/setup.js +619 -0
  102. package/dist/cli/tunnel.d.ts +35 -0
  103. package/dist/cli/tunnel.js +141 -0
  104. package/dist/config.d.ts +145 -0
  105. package/dist/config.js +278 -0
  106. package/dist/events/bus.d.ts +43 -0
  107. package/dist/events/bus.js +136 -0
  108. package/dist/gateway/cron-scheduler.d.ts +166 -0
  109. package/dist/gateway/cron-scheduler.js +1767 -0
  110. package/dist/gateway/delivery-queue.d.ts +30 -0
  111. package/dist/gateway/delivery-queue.js +110 -0
  112. package/dist/gateway/heartbeat-scheduler.d.ts +99 -0
  113. package/dist/gateway/heartbeat-scheduler.js +1298 -0
  114. package/dist/gateway/heartbeat.d.ts +3 -0
  115. package/dist/gateway/heartbeat.js +3 -0
  116. package/dist/gateway/lanes.d.ts +24 -0
  117. package/dist/gateway/lanes.js +76 -0
  118. package/dist/gateway/notifications.d.ts +29 -0
  119. package/dist/gateway/notifications.js +75 -0
  120. package/dist/gateway/router.d.ts +210 -0
  121. package/dist/gateway/router.js +1330 -0
  122. package/dist/index.d.ts +12 -0
  123. package/dist/index.js +1015 -0
  124. package/dist/memory/chunker.d.ts +28 -0
  125. package/dist/memory/chunker.js +226 -0
  126. package/dist/memory/consolidation.d.ts +44 -0
  127. package/dist/memory/consolidation.js +171 -0
  128. package/dist/memory/context-assembler.d.ts +50 -0
  129. package/dist/memory/context-assembler.js +149 -0
  130. package/dist/memory/embeddings.d.ts +38 -0
  131. package/dist/memory/embeddings.js +180 -0
  132. package/dist/memory/graph-store.d.ts +66 -0
  133. package/dist/memory/graph-store.js +613 -0
  134. package/dist/memory/mmr.d.ts +21 -0
  135. package/dist/memory/mmr.js +75 -0
  136. package/dist/memory/search.d.ts +26 -0
  137. package/dist/memory/search.js +67 -0
  138. package/dist/memory/store.d.ts +530 -0
  139. package/dist/memory/store.js +2022 -0
  140. package/dist/security/integrity.d.ts +24 -0
  141. package/dist/security/integrity.js +58 -0
  142. package/dist/security/patterns.d.ts +34 -0
  143. package/dist/security/patterns.js +110 -0
  144. package/dist/security/scanner.d.ts +32 -0
  145. package/dist/security/scanner.js +263 -0
  146. package/dist/tools/admin-tools.d.ts +12 -0
  147. package/dist/tools/admin-tools.js +1278 -0
  148. package/dist/tools/external-tools.d.ts +11 -0
  149. package/dist/tools/external-tools.js +1327 -0
  150. package/dist/tools/goal-tools.d.ts +9 -0
  151. package/dist/tools/goal-tools.js +159 -0
  152. package/dist/tools/mcp-server.d.ts +13 -0
  153. package/dist/tools/mcp-server.js +141 -0
  154. package/dist/tools/memory-tools.d.ts +10 -0
  155. package/dist/tools/memory-tools.js +568 -0
  156. package/dist/tools/session-tools.d.ts +6 -0
  157. package/dist/tools/session-tools.js +146 -0
  158. package/dist/tools/shared.d.ts +216 -0
  159. package/dist/tools/shared.js +340 -0
  160. package/dist/tools/team-tools.d.ts +6 -0
  161. package/dist/tools/team-tools.js +447 -0
  162. package/dist/tools/tool-meta.d.ts +34 -0
  163. package/dist/tools/tool-meta.js +133 -0
  164. package/dist/tools/vault-tools.d.ts +8 -0
  165. package/dist/tools/vault-tools.js +457 -0
  166. package/dist/types.d.ts +716 -0
  167. package/dist/types.js +16 -0
  168. package/dist/vault-migrations/0001-add-execution-framework.d.ts +10 -0
  169. package/dist/vault-migrations/0001-add-execution-framework.js +47 -0
  170. package/dist/vault-migrations/0002-add-agentic-communication.d.ts +12 -0
  171. package/dist/vault-migrations/0002-add-agentic-communication.js +79 -0
  172. package/dist/vault-migrations/0003-update-execution-pipeline-narration.d.ts +11 -0
  173. package/dist/vault-migrations/0003-update-execution-pipeline-narration.js +73 -0
  174. package/dist/vault-migrations/helpers.d.ts +14 -0
  175. package/dist/vault-migrations/helpers.js +44 -0
  176. package/dist/vault-migrations/runner.d.ts +14 -0
  177. package/dist/vault-migrations/runner.js +139 -0
  178. package/dist/vault-migrations/types.d.ts +42 -0
  179. package/dist/vault-migrations/types.js +9 -0
  180. package/install.sh +320 -0
  181. package/package.json +84 -0
  182. package/scripts/postinstall.js +125 -0
  183. package/vault/00-System/AGENTS.md +66 -0
  184. package/vault/00-System/CRON.md +71 -0
  185. package/vault/00-System/HEARTBEAT.md +58 -0
  186. package/vault/00-System/MEMORY.md +16 -0
  187. package/vault/00-System/SOUL.md +96 -0
  188. package/vault/05-Tasks/TASKS.md +19 -0
  189. package/vault/06-Templates/_Daily-Template.md +28 -0
  190. package/vault/06-Templates/_People-Template.md +22 -0
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Clementine TypeScript — HTTP API webhook server.
3
+ *
4
+ * Provides a REST API for programmatic access to the assistant.
5
+ * Uses Express with Bearer token authentication.
6
+ */
7
+ import type { Gateway } from '../gateway/router.js';
8
+ export declare function startWebhook(gateway: Gateway): Promise<void>;
9
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Clementine TypeScript — HTTP API webhook server.
3
+ *
4
+ * Provides a REST API for programmatic access to the assistant.
5
+ * Uses Express with Bearer token authentication.
6
+ */
7
+ import express from 'express';
8
+ import pino from 'pino';
9
+ import { WEBHOOK_PORT, WEBHOOK_SECRET } from '../config.js';
10
+ const logger = pino({ name: 'clementine.webhook' });
11
+ // ── Entry point ───────────────────────────────────────────────────────
12
+ export async function startWebhook(gateway) {
13
+ const app = express();
14
+ app.use(express.json());
15
+ // ── Bearer token auth middleware ──────────────────────────────────
16
+ function requireAuth(req, res, next) {
17
+ const authHeader = req.headers.authorization ?? '';
18
+ const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';
19
+ if (!token || token !== WEBHOOK_SECRET) {
20
+ res.status(401).json({ error: 'Unauthorized' });
21
+ return;
22
+ }
23
+ next();
24
+ }
25
+ // ── POST /api/message — process a message ─────────────────────────
26
+ app.post('/api/message', requireAuth, async (req, res) => {
27
+ const { text, session_key: sessionKey, model } = req.body;
28
+ if (!text) {
29
+ res.status(400).json({ error: 'Missing "text" field' });
30
+ return;
31
+ }
32
+ const effectiveSessionKey = sessionKey ?? 'webhook:default';
33
+ try {
34
+ const response = await gateway.handleMessage(effectiveSessionKey, text, undefined, model);
35
+ res.json({ response, session_key: effectiveSessionKey });
36
+ }
37
+ catch (err) {
38
+ logger.error({ err }, 'Webhook message processing failed');
39
+ res.status(500).json({ error: 'Internal server error' });
40
+ }
41
+ });
42
+ // ── POST /webhook/:source — generic webhook intake ────────────────
43
+ app.post('/webhook/:source', requireAuth, async (req, res) => {
44
+ const source = req.params.source;
45
+ const body = req.body;
46
+ const text = String(body.text ?? body.message ?? body.content ?? JSON.stringify(body));
47
+ const sessionKey = `webhook:${source}`;
48
+ try {
49
+ const response = await gateway.handleMessage(sessionKey, text);
50
+ res.json({ response, source, session_key: sessionKey });
51
+ }
52
+ catch (err) {
53
+ logger.error({ err, source }, 'Webhook intake processing failed');
54
+ res.status(500).json({ error: 'Internal server error' });
55
+ }
56
+ });
57
+ // ── GET /api/status — health check ────────────────────────────────
58
+ app.get('/api/status', (_req, res) => {
59
+ res.json({
60
+ status: 'ok',
61
+ uptime: process.uptime(),
62
+ timestamp: new Date().toISOString(),
63
+ });
64
+ });
65
+ // ── Start server ──────────────────────────────────────────────────
66
+ const port = WEBHOOK_PORT;
67
+ await new Promise((resolve) => {
68
+ app.listen(port, '0.0.0.0', () => {
69
+ logger.info(`Webhook API server listening on port ${port}`);
70
+ resolve();
71
+ });
72
+ });
73
+ // Keep alive
74
+ await new Promise((_, reject) => {
75
+ process.once('SIGTERM', () => reject(new Error('SIGTERM')));
76
+ });
77
+ }
78
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Clementine TypeScript — WhatsApp channel adapter (via Twilio).
3
+ *
4
+ * Uses Express for webhook receiving and Twilio REST API for sending.
5
+ * Supports incoming messages with Twilio signature validation,
6
+ * message chunking, and markdown cleanup.
7
+ */
8
+ import type { NotificationDispatcher } from '../gateway/notifications.js';
9
+ import type { Gateway } from '../gateway/router.js';
10
+ export declare function startWhatsApp(gateway: Gateway, dispatcher: NotificationDispatcher): Promise<void>;
11
+ //# sourceMappingURL=whatsapp.d.ts.map
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Clementine TypeScript — WhatsApp channel adapter (via Twilio).
3
+ *
4
+ * Uses Express for webhook receiving and Twilio REST API for sending.
5
+ * Supports incoming messages with Twilio signature validation,
6
+ * message chunking, and markdown cleanup.
7
+ */
8
+ import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
9
+ import express from 'express';
10
+ import pino from 'pino';
11
+ import { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, WHATSAPP_OWNER_PHONE, WHATSAPP_FROM_PHONE, WHATSAPP_WEBHOOK_PORT, } from '../config.js';
12
+ const logger = pino({ name: 'clementine.whatsapp' });
13
+ const TWILIO_API_URL = `https://api.twilio.com/2010-04-01/Accounts/${TWILIO_ACCOUNT_SID}/Messages.json`;
14
+ const WHATSAPP_MAX_LENGTH = 4096;
15
+ // ── Phone hashing ─────────────────────────────────────────────────────
16
+ function hashPhone(phone) {
17
+ return createHash('sha256').update(phone).digest('hex').slice(0, 12);
18
+ }
19
+ // ── Twilio signature validation ───────────────────────────────────────
20
+ function validateTwilioSignature(url, params, signature, authToken) {
21
+ // Build the data string: URL + sorted params
22
+ let data = url;
23
+ for (const key of Object.keys(params).sort()) {
24
+ data += key + params[key];
25
+ }
26
+ const expected = createHmac('sha1', authToken).update(data).digest('base64');
27
+ // Constant-time comparison
28
+ try {
29
+ return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ // ── WhatsApp formatting ───────────────────────────────────────────────
36
+ function cleanForWhatsApp(text) {
37
+ // WhatsApp supports *bold* and _italic_ natively, but not code blocks
38
+ text = text.replace(/\*\*/g, '*'); // markdown bold -> WhatsApp bold
39
+ text = text.replace(/```/g, ''); // code fences
40
+ text = text.replace(/`/g, ''); // inline code
41
+ return text;
42
+ }
43
+ function splitMessage(text, maxLength = WHATSAPP_MAX_LENGTH) {
44
+ if (text.length <= maxLength)
45
+ return [text];
46
+ const chunks = [];
47
+ let remaining = text;
48
+ while (remaining) {
49
+ if (remaining.length <= maxLength) {
50
+ chunks.push(remaining);
51
+ break;
52
+ }
53
+ // Find a good split point (newline or space)
54
+ let splitAt = remaining.lastIndexOf('\n', maxLength);
55
+ if (splitAt === -1 || splitAt < maxLength / 2) {
56
+ splitAt = remaining.lastIndexOf(' ', maxLength);
57
+ }
58
+ if (splitAt === -1 || splitAt < maxLength / 2) {
59
+ splitAt = maxLength;
60
+ }
61
+ chunks.push(remaining.slice(0, splitAt));
62
+ remaining = remaining.slice(splitAt).trimStart();
63
+ }
64
+ return chunks;
65
+ }
66
+ // ── Twilio REST API sending ───────────────────────────────────────────
67
+ async function sendWhatsApp(to, body) {
68
+ const params = new URLSearchParams({
69
+ To: `whatsapp:${to}`,
70
+ From: `whatsapp:${WHATSAPP_FROM_PHONE}`,
71
+ Body: body,
72
+ });
73
+ const auth = Buffer.from(`${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}`).toString('base64');
74
+ const resp = await fetch(TWILIO_API_URL, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/x-www-form-urlencoded',
78
+ 'Authorization': `Basic ${auth}`,
79
+ },
80
+ body: params.toString(),
81
+ });
82
+ if (!resp.ok) {
83
+ const text = await resp.text();
84
+ logger.error(`Twilio send failed (${resp.status}): ${text.slice(0, 300)}`);
85
+ }
86
+ }
87
+ // ── Entry point ───────────────────────────────────────────────────────
88
+ export async function startWhatsApp(gateway, dispatcher) {
89
+ if (!TWILIO_ACCOUNT_SID || !TWILIO_AUTH_TOKEN) {
90
+ logger.error('TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN not set — WhatsApp channel disabled');
91
+ return;
92
+ }
93
+ if (!WHATSAPP_OWNER_PHONE) {
94
+ logger.error('WHATSAPP_OWNER_PHONE not set — WhatsApp channel disabled');
95
+ return;
96
+ }
97
+ const ownerHash = hashPhone(WHATSAPP_OWNER_PHONE);
98
+ const app = express();
99
+ // Parse URL-encoded bodies (Twilio sends form data)
100
+ app.use(express.urlencoded({ extended: false }));
101
+ app.post('/webhook/whatsapp', async (req, res) => {
102
+ const params = {};
103
+ for (const [key, value] of Object.entries(req.body)) {
104
+ params[key] = String(value);
105
+ }
106
+ // Validate Twilio signature
107
+ const signature = req.headers['x-twilio-signature'] ?? '';
108
+ const requestUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
109
+ if (!validateTwilioSignature(requestUrl, params, signature, TWILIO_AUTH_TOKEN)) {
110
+ logger.warn('Invalid Twilio signature — rejecting request');
111
+ res.status(403).send('Invalid signature');
112
+ return;
113
+ }
114
+ // Extract message fields
115
+ const body = (params.Body ?? '').trim();
116
+ const fromNumber = (params.From ?? '').replace('whatsapp:', '');
117
+ if (!fromNumber) {
118
+ res.status(400).send('Missing From field');
119
+ return;
120
+ }
121
+ // Owner-only check
122
+ if (fromNumber !== WHATSAPP_OWNER_PHONE) {
123
+ logger.warn(`Ignoring message from non-owner: ${hashPhone(fromNumber)}`);
124
+ res.type('application/xml').send('<Response></Response>');
125
+ return;
126
+ }
127
+ if (!body) {
128
+ res.type('application/xml').send('<Response></Response>');
129
+ return;
130
+ }
131
+ const sessionKey = `whatsapp:user:${ownerHash}`;
132
+ logger.info(`WhatsApp message: ${body.slice(0, 80)}...`);
133
+ // Return TwiML immediately; process in background
134
+ res.type('application/xml').send('<Response></Response>');
135
+ // Process and reply asynchronously
136
+ try {
137
+ const response = await gateway.handleMessage(sessionKey, body);
138
+ if (response) {
139
+ const clean = cleanForWhatsApp(response);
140
+ const chunks = splitMessage(clean);
141
+ for (const chunk of chunks) {
142
+ await sendWhatsApp(fromNumber, chunk);
143
+ }
144
+ }
145
+ }
146
+ catch (err) {
147
+ logger.error({ err }, 'Error processing WhatsApp message');
148
+ // Don't leave the user in silence — send an error message back
149
+ try {
150
+ await sendWhatsApp(fromNumber, 'Something went wrong — try again in a moment.');
151
+ }
152
+ catch (sendErr) {
153
+ logger.error({ err: sendErr }, 'Failed to send WhatsApp error notification');
154
+ }
155
+ }
156
+ });
157
+ // Register notification sender
158
+ async function whatsappNotify(text) {
159
+ const clean = cleanForWhatsApp(text);
160
+ const chunks = splitMessage(clean);
161
+ for (const chunk of chunks) {
162
+ await sendWhatsApp(WHATSAPP_OWNER_PHONE, chunk);
163
+ }
164
+ }
165
+ dispatcher.register('whatsapp', whatsappNotify);
166
+ const port = WHATSAPP_WEBHOOK_PORT;
167
+ await new Promise((resolve) => {
168
+ app.listen(port, '0.0.0.0', () => {
169
+ logger.info(`WhatsApp webhook server listening on port ${port}`);
170
+ resolve();
171
+ });
172
+ });
173
+ // Keep alive — the Express server runs in the background
174
+ await new Promise((_, reject) => {
175
+ process.once('SIGTERM', () => {
176
+ dispatcher.unregister('whatsapp');
177
+ reject(new Error('SIGTERM'));
178
+ });
179
+ });
180
+ }
181
+ //# sourceMappingURL=whatsapp.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Clementine CLI — Interactive REPL chat session.
3
+ *
4
+ * Usage: clementine chat [--model sonnet] [--project myapp] [--profile researcher]
5
+ *
6
+ * Lazy-initializes a gateway instance and streams responses to stdout.
7
+ * Bang commands: !model, !project, !clear, !help, !q, !d prefixes.
8
+ */
9
+ export declare function cmdChat(opts: {
10
+ model?: string;
11
+ project?: string;
12
+ profile?: string;
13
+ }): Promise<void>;
14
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Clementine CLI — Interactive REPL chat session.
3
+ *
4
+ * Usage: clementine chat [--model sonnet] [--project myapp] [--profile researcher]
5
+ *
6
+ * Lazy-initializes a gateway instance and streams responses to stdout.
7
+ * Bang commands: !model, !project, !clear, !help, !q, !d prefixes.
8
+ */
9
+ import readline from 'node:readline';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ const BASE_DIR = process.env.CLEMENTINE_HOME || path.join(os.homedir(), '.clementine');
13
+ // ── ANSI helpers ─────────────────────────────────────────────────────
14
+ const BOLD = '\x1b[1m';
15
+ const DIM = '\x1b[0;90m';
16
+ const CYAN = '\x1b[0;36m';
17
+ const GREEN = '\x1b[0;32m';
18
+ const YELLOW = '\x1b[1;33m';
19
+ const RESET = '\x1b[0m';
20
+ /** Simple ANSI markdown renderer for terminal output. */
21
+ function renderMarkdown(text) {
22
+ return text
23
+ // Bold
24
+ .replace(/\*\*(.+?)\*\*/g, `${BOLD}$1${RESET}`)
25
+ // Inline code
26
+ .replace(/`([^`]+)`/g, `${DIM}$1${RESET}`)
27
+ // Headers
28
+ .replace(/^(#{1,3})\s+(.+)$/gm, (_m, hashes, title) => {
29
+ const color = hashes.length === 1 ? CYAN : hashes.length === 2 ? GREEN : YELLOW;
30
+ return `${color}${BOLD}${title}${RESET}`;
31
+ });
32
+ }
33
+ // ── Lazy gateway init ────────────────────────────────────────────────
34
+ let gatewayInstance = null;
35
+ async function getGateway() {
36
+ if (gatewayInstance)
37
+ return gatewayInstance;
38
+ process.env.CLEMENTINE_HOME = BASE_DIR;
39
+ delete process.env['CLAUDECODE'];
40
+ const { PersonalAssistant } = await import('../agent/assistant.js');
41
+ const assistant = new PersonalAssistant();
42
+ const { Gateway: GatewayClass } = await import('../gateway/router.js');
43
+ gatewayInstance = new GatewayClass(assistant);
44
+ const { setApprovalCallback } = await import('../agent/hooks.js');
45
+ setApprovalCallback(async () => false);
46
+ return gatewayInstance;
47
+ }
48
+ // ── REPL ─────────────────────────────────────────────────────────────
49
+ export async function cmdChat(opts) {
50
+ const sessionKey = `cli:repl:${process.pid}`;
51
+ console.log(`${CYAN}${BOLD}Clementine Chat${RESET} ${DIM}(Ctrl+C to exit)${RESET}`);
52
+ console.log();
53
+ const gateway = await getGateway();
54
+ // Apply initial model override
55
+ if (opts.model) {
56
+ const { MODELS } = await import('../config.js');
57
+ const tier = opts.model.toLowerCase();
58
+ if (tier in MODELS) {
59
+ gateway.setSessionModel(sessionKey, MODELS[tier]);
60
+ console.log(`${DIM}Model: ${tier} (${MODELS[tier]})${RESET}`);
61
+ }
62
+ }
63
+ // Apply initial project override
64
+ if (opts.project) {
65
+ const { findProjectByName } = await import('../agent/assistant.js');
66
+ const project = findProjectByName(opts.project);
67
+ if (project) {
68
+ gateway.setSessionProject(sessionKey, project);
69
+ console.log(`${DIM}Project: ${path.basename(project.path)}${RESET}`);
70
+ }
71
+ else {
72
+ console.log(`${YELLOW}Project "${opts.project}" not found — using auto-matching${RESET}`);
73
+ }
74
+ }
75
+ // Apply initial profile override
76
+ if (opts.profile) {
77
+ const { PROFILES_DIR, AGENTS_DIR } = await import('../config.js');
78
+ const { AgentManager } = await import('../agent/agent-manager.js');
79
+ const pm = new AgentManager(AGENTS_DIR, PROFILES_DIR);
80
+ const profile = pm.get(opts.profile);
81
+ if (profile) {
82
+ gateway.setSessionProfile(sessionKey, opts.profile);
83
+ console.log(`${DIM}Profile: ${profile.name}${RESET}`);
84
+ }
85
+ else {
86
+ console.log(`${YELLOW}Profile "${opts.profile}" not found${RESET}`);
87
+ }
88
+ }
89
+ console.log();
90
+ const rl = readline.createInterface({
91
+ input: process.stdin,
92
+ output: process.stdout,
93
+ prompt: `${GREEN}>${RESET} `,
94
+ });
95
+ rl.prompt();
96
+ rl.on('line', async (line) => {
97
+ const text = line.trim();
98
+ if (!text) {
99
+ rl.prompt();
100
+ return;
101
+ }
102
+ // ── Bang commands ────────────────────────────────────────────
103
+ if (text === '!help' || text === '!h') {
104
+ console.log([
105
+ `${BOLD}Commands:${RESET}`,
106
+ ` ${CYAN}!model [haiku|sonnet|opus]${RESET} — Switch model`,
107
+ ` ${CYAN}!project <name|list|clear|status>${RESET} — Set project context`,
108
+ ` ${CYAN}!clear${RESET} — Reset conversation`,
109
+ ` ${CYAN}!q <msg>${RESET} — Quick reply (Haiku)`,
110
+ ` ${CYAN}!d <msg>${RESET} — Deep reply (Opus)`,
111
+ ` ${CYAN}!help${RESET} — This message`,
112
+ ` ${DIM}Ctrl+C to exit${RESET}`,
113
+ ].join('\n'));
114
+ rl.prompt();
115
+ return;
116
+ }
117
+ if (text === '!clear') {
118
+ gateway.clearSession(sessionKey);
119
+ console.log(`${DIM}Session cleared.${RESET}`);
120
+ rl.prompt();
121
+ return;
122
+ }
123
+ if (text.startsWith('!model')) {
124
+ const parts = text.split(/\s+/);
125
+ const tier = parts[1]?.toLowerCase();
126
+ const { MODELS } = await import('../config.js');
127
+ if (tier && tier in MODELS) {
128
+ gateway.setSessionModel(sessionKey, MODELS[tier]);
129
+ console.log(`${DIM}Model switched to ${tier} (${MODELS[tier]})${RESET}`);
130
+ }
131
+ else {
132
+ const current = gateway.getSessionModel(sessionKey) ?? 'default';
133
+ console.log(`${DIM}Current model: ${current}. Options: haiku, sonnet, opus${RESET}`);
134
+ }
135
+ rl.prompt();
136
+ return;
137
+ }
138
+ if (text.startsWith('!project')) {
139
+ const parts = text.split(/\s+/);
140
+ const subCmd = parts[1]?.toLowerCase();
141
+ if (subCmd === 'clear') {
142
+ gateway.clearSessionProject(sessionKey);
143
+ console.log(`${DIM}Project context cleared.${RESET}`);
144
+ }
145
+ else if (subCmd === 'status') {
146
+ const current = gateway.getSessionProject(sessionKey);
147
+ if (current) {
148
+ console.log(`${DIM}Active: ${path.basename(current.path)} — ${current.path}${RESET}`);
149
+ }
150
+ else {
151
+ console.log(`${DIM}No active project. Using auto-matching.${RESET}`);
152
+ }
153
+ }
154
+ else if (subCmd === 'list' || !subCmd) {
155
+ const { getLinkedProjects } = await import('../agent/assistant.js');
156
+ const projects = getLinkedProjects();
157
+ if (projects.length === 0) {
158
+ console.log(`${DIM}No linked projects.${RESET}`);
159
+ }
160
+ else {
161
+ const current = gateway.getSessionProject(sessionKey);
162
+ for (const p of projects) {
163
+ const name = path.basename(p.path);
164
+ const active = current && p.path === current.path ? ` ${GREEN}(active)${RESET}` : '';
165
+ console.log(` ${CYAN}${name}${RESET}${active}`);
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ // Set project
171
+ const projectName = parts.slice(1).join(' ');
172
+ const { findProjectByName } = await import('../agent/assistant.js');
173
+ const project = findProjectByName(projectName);
174
+ if (project) {
175
+ gateway.clearSession(sessionKey);
176
+ gateway.setSessionProject(sessionKey, project);
177
+ console.log(`${DIM}Switched to ${path.basename(project.path)}. Session cleared.${RESET}`);
178
+ }
179
+ else {
180
+ console.log(`${YELLOW}Project "${projectName}" not found.${RESET}`);
181
+ }
182
+ }
183
+ rl.prompt();
184
+ return;
185
+ }
186
+ // ── Per-message model prefix ───────────────────────────────
187
+ let effectiveText = text;
188
+ let oneOffModel;
189
+ if (text.startsWith('!q ')) {
190
+ const { MODELS } = await import('../config.js');
191
+ oneOffModel = MODELS.haiku;
192
+ effectiveText = text.slice(3);
193
+ }
194
+ else if (text.startsWith('!d ')) {
195
+ const { MODELS } = await import('../config.js');
196
+ oneOffModel = MODELS.opus;
197
+ effectiveText = text.slice(3);
198
+ }
199
+ // ── Send message ──────────────────────────────────────────
200
+ process.stdout.write(`\n${DIM}thinking...${RESET}\r`);
201
+ try {
202
+ const response = await gateway.handleMessage(sessionKey, effectiveText, undefined, oneOffModel);
203
+ // Clear "thinking..." line
204
+ process.stdout.write('\x1b[2K\r');
205
+ console.log(renderMarkdown(response));
206
+ console.log();
207
+ }
208
+ catch (err) {
209
+ process.stdout.write('\x1b[2K\r');
210
+ console.error(`${YELLOW}Error: ${err}${RESET}`);
211
+ console.log();
212
+ }
213
+ rl.prompt();
214
+ });
215
+ rl.on('close', () => {
216
+ console.log(`\n${DIM}Goodbye.${RESET}`);
217
+ process.exit(0);
218
+ });
219
+ }
220
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Clementine CLI — Standalone cron runner.
3
+ *
4
+ * Lightweight runner that initializes just the agent + gateway (no channels,
5
+ * no daemon), parses CRON.md, and executes a single job or heartbeat.
6
+ * Designed to be called by OS scheduler and exit.
7
+ */
8
+ export declare function cmdCronList(): Promise<void>;
9
+ export declare function cmdCronRun(jobName: string): Promise<void>;
10
+ export declare function cmdCronRunDue(): Promise<void>;
11
+ export declare function cmdCronAdd(name: string, schedule: string, prompt: string, options: {
12
+ tier?: string;
13
+ }): Promise<void>;
14
+ export declare function cmdCronTest(jobNameOrIndex: string): Promise<void>;
15
+ export declare function cmdCronRuns(jobName?: string): Promise<void>;
16
+ export declare function cmdHeartbeat(): Promise<void>;
17
+ //# sourceMappingURL=cron.d.ts.map