@trenchwork/erosolar 1.1.28 → 1.1.29

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 (94) hide show
  1. package/SECURITY.md +35 -0
  2. package/dist/bin/deepseek.js +14 -8
  3. package/dist/bin/deepseek.js.map +1 -1
  4. package/dist/capabilities/index.d.ts +6 -0
  5. package/dist/capabilities/index.d.ts.map +1 -1
  6. package/dist/capabilities/index.js +6 -0
  7. package/dist/capabilities/index.js.map +1 -1
  8. package/dist/capabilities/interactionCapability.d.ts +6 -0
  9. package/dist/capabilities/interactionCapability.d.ts.map +1 -0
  10. package/dist/capabilities/interactionCapability.js +17 -0
  11. package/dist/capabilities/interactionCapability.js.map +1 -0
  12. package/dist/capabilities/mcpCapability.d.ts.map +1 -1
  13. package/dist/capabilities/mcpCapability.js +4 -2
  14. package/dist/capabilities/mcpCapability.js.map +1 -1
  15. package/dist/capabilities/monitorCapability.d.ts +6 -0
  16. package/dist/capabilities/monitorCapability.d.ts.map +1 -0
  17. package/dist/capabilities/monitorCapability.js +19 -0
  18. package/dist/capabilities/monitorCapability.js.map +1 -0
  19. package/dist/capabilities/planModeCapability.d.ts +6 -0
  20. package/dist/capabilities/planModeCapability.d.ts.map +1 -0
  21. package/dist/capabilities/planModeCapability.js +16 -0
  22. package/dist/capabilities/planModeCapability.js.map +1 -0
  23. package/dist/capabilities/scheduleCapability.d.ts +6 -0
  24. package/dist/capabilities/scheduleCapability.d.ts.map +1 -0
  25. package/dist/capabilities/scheduleCapability.js +16 -0
  26. package/dist/capabilities/scheduleCapability.js.map +1 -0
  27. package/dist/capabilities/triggerCapability.d.ts +6 -0
  28. package/dist/capabilities/triggerCapability.d.ts.map +1 -0
  29. package/dist/capabilities/triggerCapability.js +16 -0
  30. package/dist/capabilities/triggerCapability.js.map +1 -0
  31. package/dist/capabilities/worktreeCapability.d.ts +6 -0
  32. package/dist/capabilities/worktreeCapability.d.ts.map +1 -0
  33. package/dist/capabilities/worktreeCapability.js +16 -0
  34. package/dist/capabilities/worktreeCapability.js.map +1 -0
  35. package/dist/contracts/agent-schemas.json +1 -1
  36. package/dist/core/projectTracker.d.ts +96 -0
  37. package/dist/core/projectTracker.d.ts.map +1 -0
  38. package/dist/core/projectTracker.js +275 -0
  39. package/dist/core/projectTracker.js.map +1 -0
  40. package/dist/headless/interactiveShell.js +35 -0
  41. package/dist/headless/interactiveShell.js.map +1 -1
  42. package/dist/plugins/tools/mcp/mcpClient.d.ts +10 -0
  43. package/dist/plugins/tools/mcp/mcpClient.d.ts.map +1 -1
  44. package/dist/plugins/tools/mcp/mcpClient.js +6 -0
  45. package/dist/plugins/tools/mcp/mcpClient.js.map +1 -1
  46. package/dist/runtime/node.d.ts +2 -0
  47. package/dist/runtime/node.d.ts.map +1 -1
  48. package/dist/runtime/node.js +28 -28
  49. package/dist/runtime/node.js.map +1 -1
  50. package/dist/runtime/profileGates.d.ts +19 -0
  51. package/dist/runtime/profileGates.d.ts.map +1 -0
  52. package/dist/runtime/profileGates.js +23 -0
  53. package/dist/runtime/profileGates.js.map +1 -0
  54. package/dist/tools/interactionTools.d.ts +16 -0
  55. package/dist/tools/interactionTools.d.ts.map +1 -0
  56. package/dist/tools/interactionTools.js +207 -0
  57. package/dist/tools/interactionTools.js.map +1 -0
  58. package/dist/tools/monitorTools.d.ts +16 -0
  59. package/dist/tools/monitorTools.d.ts.map +1 -0
  60. package/dist/tools/monitorTools.js +159 -0
  61. package/dist/tools/monitorTools.js.map +1 -0
  62. package/dist/tools/planModeTools.d.ts +32 -0
  63. package/dist/tools/planModeTools.d.ts.map +1 -0
  64. package/dist/tools/planModeTools.js +200 -0
  65. package/dist/tools/planModeTools.js.map +1 -0
  66. package/dist/tools/scheduleTools.d.ts +39 -0
  67. package/dist/tools/scheduleTools.d.ts.map +1 -0
  68. package/dist/tools/scheduleTools.js +182 -0
  69. package/dist/tools/scheduleTools.js.map +1 -0
  70. package/dist/tools/triggerTools.d.ts +28 -0
  71. package/dist/tools/triggerTools.d.ts.map +1 -0
  72. package/dist/tools/triggerTools.js +210 -0
  73. package/dist/tools/triggerTools.js.map +1 -0
  74. package/dist/tools/worktreeTools.d.ts +21 -0
  75. package/dist/tools/worktreeTools.d.ts.map +1 -0
  76. package/dist/tools/worktreeTools.js +158 -0
  77. package/dist/tools/worktreeTools.js.map +1 -0
  78. package/dist/ui/ink/App.d.ts.map +1 -1
  79. package/dist/ui/ink/App.js +2 -1
  80. package/dist/ui/ink/App.js.map +1 -1
  81. package/dist/ui/ink/ChatStatic.d.ts.map +1 -1
  82. package/dist/ui/ink/ChatStatic.js +7 -6
  83. package/dist/ui/ink/ChatStatic.js.map +1 -1
  84. package/dist/ui/ink/StatusLine.d.ts.map +1 -1
  85. package/dist/ui/ink/StatusLine.js +2 -1
  86. package/dist/ui/ink/StatusLine.js.map +1 -1
  87. package/dist/ui/theme.d.ts.map +1 -1
  88. package/dist/ui/theme.js +31 -29
  89. package/dist/ui/theme.js.map +1 -1
  90. package/dist/utils/lambdaClient.d.ts +35 -0
  91. package/dist/utils/lambdaClient.d.ts.map +1 -0
  92. package/dist/utils/lambdaClient.js +81 -0
  93. package/dist/utils/lambdaClient.js.map +1 -0
  94. package/package.json +1 -1
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Project tracking — pairs every CLI session with a stable project
3
+ * doc keyed off the working directory. The project doc rolls up
4
+ * lastSeenAt / sessionCount / totalTokens so the audit portal can
5
+ * show "what's the CLI been doing in /home/bo/GitHub/foo lately"
6
+ * without scanning every session.
7
+ *
8
+ * Identity:
9
+ * projectId = sha256(absolute path).slice(0, 12)
10
+ * Same checkout on the same machine → same project across launches,
11
+ * with no operator input needed.
12
+ *
13
+ * Schema (Firestore):
14
+ * projects/{projectId}
15
+ * ownerUid: string
16
+ * name: string (basename of workingDir)
17
+ * workingDir: string
18
+ * workingDirHash: string (the projectId, repeated for queries)
19
+ * gitRemote: string | null
20
+ * gitBranchAtFirst: string | null
21
+ * createdAt: timestamp (server time)
22
+ * lastSeenAt: timestamp (server time, every boot)
23
+ * sessionCount: int (Firestore Increment(+1) per boot)
24
+ * totalTokens: int (Increment on session-end with usage)
25
+ * status: 'active' | 'archived'
26
+ *
27
+ * usage_logs/{uid}/sessions/{sessionId}
28
+ * ... existing session-start fields ...
29
+ * projectId: string (FK)
30
+ * gitBranch: string | null
31
+ * gitCommit: string | null
32
+ * outcome: 'in_progress' | 'completed' | 'cancelled' | 'errored'
33
+ * filesModified: string[] (post-end update)
34
+ * toolsUsed: string[] (post-end update)
35
+ * tokensIn / tokensOut: int (post-end update)
36
+ * endedAt: timestamp | null (post-end update)
37
+ */
38
+ import { createHash } from 'node:crypto';
39
+ import { execSync } from 'node:child_process';
40
+ import { existsSync } from 'node:fs';
41
+ import { basename, resolve } from 'node:path';
42
+ import { FIREBASE_PROJECT_ID, getAuthStatus, getValidIdToken } from './auth.js';
43
+ const FIRESTORE_BASE = `https://firestore.googleapis.com/v1/projects/${FIREBASE_PROJECT_ID}/databases/(default)/documents`;
44
+ export function deriveProjectId(workingDir) {
45
+ return createHash('sha256').update(resolve(workingDir)).digest('hex').slice(0, 12);
46
+ }
47
+ function safeGit(args, cwd) {
48
+ try {
49
+ const out = execSync(`git ${args.map(shellQuote).join(' ')}`, {
50
+ cwd,
51
+ encoding: 'utf8',
52
+ stdio: ['ignore', 'pipe', 'ignore'],
53
+ timeout: 2000,
54
+ });
55
+ return out.trim() || null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function shellQuote(s) {
62
+ return /^[\w./:=-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
63
+ }
64
+ function gitInfo(workingDir) {
65
+ if (!existsSync(workingDir))
66
+ return { branch: null, commit: null, remote: null };
67
+ return {
68
+ branch: safeGit(['rev-parse', '--abbrev-ref', 'HEAD'], workingDir),
69
+ commit: safeGit(['rev-parse', 'HEAD'], workingDir),
70
+ remote: safeGit(['config', '--get', 'remote.origin.url'], workingDir),
71
+ };
72
+ }
73
+ export function buildProjectContext(workingDir) {
74
+ const abs = resolve(workingDir);
75
+ const id = deriveProjectId(abs);
76
+ const { branch, commit, remote } = gitInfo(abs);
77
+ return {
78
+ projectId: id,
79
+ workingDir: abs,
80
+ name: basename(abs) || abs,
81
+ gitBranch: branch,
82
+ gitCommit: commit,
83
+ gitRemote: remote,
84
+ };
85
+ }
86
+ function strField(v) {
87
+ if (v == null || v === '')
88
+ return { nullValue: null };
89
+ return { stringValue: v };
90
+ }
91
+ /**
92
+ * Idempotent register of `projects/{projectId}`. Uses Firestore's
93
+ * REST `commit` endpoint with two writes in a transaction-like
94
+ * batch: an upsert of mutable fields + a server-side
95
+ * `Increment(1)` on sessionCount. The createdAt-vs-lastSeenAt
96
+ * distinction is preserved because Firestore PATCH with a partial
97
+ * field mask only touches the listed fields.
98
+ */
99
+ export async function registerProject(workingDir) {
100
+ const context = buildProjectContext(workingDir);
101
+ const status = getAuthStatus();
102
+ if (!status.authenticated || !status.uid) {
103
+ return { ok: false, context, reason: 'not signed in' };
104
+ }
105
+ try {
106
+ const idToken = await getValidIdToken();
107
+ // Two writes via :commit so server-side timestamps + Increment fire
108
+ // atomically. The first PATCH ensures createdAt and gitBranchAtFirst
109
+ // only land on first write (updateMask omits them on subsequent
110
+ // calls — see below). The second write Increments sessionCount and
111
+ // sets lastSeenAt every boot.
112
+ const docPath = `projects/${context.projectId}`;
113
+ const fullName = `projects/${FIREBASE_PROJECT_ID}/databases/(default)/documents/${docPath}`;
114
+ // Discover whether the doc exists (so we can skip writing
115
+ // createdAt / gitBranchAtFirst on subsequent boots).
116
+ const getRes = await fetch(`${FIRESTORE_BASE}/${docPath}`, {
117
+ headers: { Authorization: `Bearer ${idToken}` },
118
+ });
119
+ const exists = getRes.status === 200;
120
+ const fields = {
121
+ ownerUid: { stringValue: status.uid },
122
+ name: { stringValue: context.name },
123
+ workingDir: { stringValue: context.workingDir },
124
+ workingDirHash: { stringValue: context.projectId },
125
+ gitRemote: strField(context.gitRemote),
126
+ lastSeenAt: { timestampValue: new Date().toISOString() },
127
+ status: { stringValue: 'active' },
128
+ };
129
+ if (!exists) {
130
+ fields['createdAt'] = { timestampValue: new Date().toISOString() };
131
+ fields['gitBranchAtFirst'] = strField(context.gitBranch);
132
+ fields['sessionCount'] = { integerValue: '1' };
133
+ fields['totalTokens'] = { integerValue: '0' };
134
+ }
135
+ const updateMask = Object.keys(fields).map((k) => `updateMask.fieldPaths=${k}`).join('&');
136
+ const patchUrl = `${FIRESTORE_BASE}/${docPath}?${updateMask}`;
137
+ const patchRes = await fetch(patchUrl, {
138
+ method: 'PATCH',
139
+ headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
140
+ body: JSON.stringify({ fields }),
141
+ });
142
+ if (!patchRes.ok) {
143
+ const txt = await patchRes.text().catch(() => '<no body>');
144
+ return { ok: false, context, reason: `PATCH ${patchRes.status}: ${txt.slice(0, 200)}` };
145
+ }
146
+ // For existing docs, increment sessionCount via the :commit endpoint.
147
+ if (exists) {
148
+ const commitUrl = `${FIRESTORE_BASE.replace(/\/documents$/, '')}/documents:commit`;
149
+ const commitBody = {
150
+ writes: [{
151
+ transform: {
152
+ document: fullName,
153
+ fieldTransforms: [{
154
+ fieldPath: 'sessionCount',
155
+ increment: { integerValue: '1' },
156
+ }],
157
+ },
158
+ }],
159
+ };
160
+ await fetch(commitUrl, {
161
+ method: 'POST',
162
+ headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
163
+ body: JSON.stringify(commitBody),
164
+ }).catch(() => { });
165
+ }
166
+ return { ok: true, context };
167
+ }
168
+ catch (err) {
169
+ return { ok: false, context, reason: err instanceof Error ? err.message : String(err) };
170
+ }
171
+ }
172
+ /**
173
+ * Append `usage_logs/{uid}/sessions/{sessionId}` with the project FK.
174
+ * Returns the sessionId so the caller can later write session-end.
175
+ * Replaces the prior `writeUsageLog` for the project-aware path.
176
+ */
177
+ export async function writeSessionStart(entry) {
178
+ const status = getAuthStatus();
179
+ if (!status.authenticated || !status.uid) {
180
+ return { ok: false, sessionId: null, reason: 'not signed in' };
181
+ }
182
+ try {
183
+ const idToken = await getValidIdToken();
184
+ const sessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
185
+ const url = `${FIRESTORE_BASE}/usage_logs/${encodeURIComponent(status.uid)}/sessions?documentId=${encodeURIComponent(sessionId)}`;
186
+ const body = {
187
+ fields: {
188
+ uid: { stringValue: status.uid },
189
+ email: strField(status.email ?? ''),
190
+ startedAt: { timestampValue: new Date().toISOString() },
191
+ profile: { stringValue: entry.profile },
192
+ model: { stringValue: entry.model },
193
+ version: { stringValue: entry.version },
194
+ platform: { stringValue: entry.platform },
195
+ projectId: { stringValue: entry.project.projectId },
196
+ gitBranch: strField(entry.project.gitBranch),
197
+ gitCommit: strField(entry.project.gitCommit),
198
+ outcome: { stringValue: 'in_progress' },
199
+ tokensIn: { integerValue: '0' },
200
+ tokensOut: { integerValue: '0' },
201
+ },
202
+ };
203
+ const res = await fetch(url, {
204
+ method: 'POST',
205
+ headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
206
+ body: JSON.stringify(body),
207
+ });
208
+ if (!res.ok) {
209
+ const txt = await res.text().catch(() => '<no body>');
210
+ return { ok: false, sessionId: null, reason: `Firestore ${res.status}: ${txt.slice(0, 200)}` };
211
+ }
212
+ return { ok: true, sessionId };
213
+ }
214
+ catch (err) {
215
+ return { ok: false, sessionId: null, reason: err instanceof Error ? err.message : String(err) };
216
+ }
217
+ }
218
+ /**
219
+ * Patch the session row on shell exit + bump the parent project's
220
+ * totalTokens. Best-effort; a kill -9 leaves the session at
221
+ * `outcome: in_progress` which the audit page can highlight.
222
+ */
223
+ export async function writeSessionEnd(entry) {
224
+ const status = getAuthStatus();
225
+ if (!status.authenticated || !status.uid) {
226
+ return { ok: false, reason: 'not signed in' };
227
+ }
228
+ try {
229
+ const idToken = await getValidIdToken();
230
+ const sessionDoc = `usage_logs/${encodeURIComponent(status.uid)}/sessions/${encodeURIComponent(entry.sessionId)}`;
231
+ const sessionFields = ['outcome', 'endedAt', 'filesModified', 'toolsUsed', 'tokensIn', 'tokensOut'];
232
+ const sessionMask = sessionFields.map((k) => `updateMask.fieldPaths=${k}`).join('&');
233
+ const sessionBody = {
234
+ fields: {
235
+ outcome: { stringValue: entry.outcome },
236
+ endedAt: { timestampValue: new Date().toISOString() },
237
+ filesModified: { arrayValue: { values: entry.filesModified.slice(0, 100).map((s) => ({ stringValue: s })) } },
238
+ toolsUsed: { arrayValue: { values: entry.toolsUsed.slice(0, 100).map((s) => ({ stringValue: s })) } },
239
+ tokensIn: { integerValue: String(entry.tokensIn) },
240
+ tokensOut: { integerValue: String(entry.tokensOut) },
241
+ },
242
+ };
243
+ await fetch(`${FIRESTORE_BASE}/${sessionDoc}?${sessionMask}`, {
244
+ method: 'PATCH',
245
+ headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
246
+ body: JSON.stringify(sessionBody),
247
+ });
248
+ // Increment the project's totalTokens via the :commit endpoint.
249
+ const totalDelta = entry.tokensIn + entry.tokensOut;
250
+ if (totalDelta > 0) {
251
+ const commitUrl = `${FIRESTORE_BASE.replace(/\/documents$/, '')}/documents:commit`;
252
+ const commitBody = {
253
+ writes: [{
254
+ transform: {
255
+ document: `projects/${FIREBASE_PROJECT_ID}/databases/(default)/documents/projects/${entry.projectId}`,
256
+ fieldTransforms: [{
257
+ fieldPath: 'totalTokens',
258
+ increment: { integerValue: String(totalDelta) },
259
+ }],
260
+ },
261
+ }],
262
+ };
263
+ await fetch(commitUrl, {
264
+ method: 'POST',
265
+ headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
266
+ body: JSON.stringify(commitBody),
267
+ }).catch(() => { });
268
+ }
269
+ return { ok: true };
270
+ }
271
+ catch (err) {
272
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
273
+ }
274
+ }
275
+ //# sourceMappingURL=projectTracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectTracker.js","sourceRoot":"","sources":["../../src/core/projectTracker.ts"],"names":[],"mappings":"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"}
@@ -206,6 +206,13 @@ class InteractiveShell {
206
206
  // immediately resume the work the user just cancelled. Cleared when the
207
207
  // user submits a fresh prompt.
208
208
  userInterruptedRun = false;
209
+ // Session-level aggregates rolled up across every processPrompt call
210
+ // and flushed to Firestore on shutdown. Bounded by the writeSessionEnd
211
+ // slicer (top 100 of each).
212
+ sessionToolsUsed = new Set();
213
+ sessionFilesModified = new Set();
214
+ sessionTokensIn = 0;
215
+ sessionTokensOut = 0;
209
216
  cachedProviders = null;
210
217
  secretInputMode = {
211
218
  active: false,
@@ -291,6 +298,29 @@ class InteractiveShell {
291
298
  this.promptController?.stop();
292
299
  setStatusSink(null);
293
300
  });
301
+ // Session-end Firestore write — transitions the session row from
302
+ // `in_progress` to `completed` and rolls up the project's
303
+ // totalTokens. Best-effort: a kill -9 leaves the row stale at
304
+ // `in_progress`, which the audit portal can highlight.
305
+ onShutdown(async () => {
306
+ const sessionId = process.env['EROSOLAR_SESSION_ID'];
307
+ const projectId = process.env['EROSOLAR_PROJECT_ID'];
308
+ if (!sessionId || !projectId)
309
+ return;
310
+ try {
311
+ const { writeSessionEnd } = await import('../core/projectTracker.js');
312
+ await writeSessionEnd({
313
+ sessionId,
314
+ projectId,
315
+ outcome: this.userInterruptedRun ? 'cancelled' : 'completed',
316
+ filesModified: Array.from(this.sessionFilesModified).slice(0, 100),
317
+ toolsUsed: Array.from(this.sessionToolsUsed).slice(0, 100),
318
+ tokensIn: this.sessionTokensIn,
319
+ tokensOut: this.sessionTokensOut,
320
+ });
321
+ }
322
+ catch { /* best-effort */ }
323
+ });
294
324
  setStatusSink((message) => this.promptController?.setStatusMessage(message));
295
325
  // Hand the terminal off to the HITL prompt while it's open: suspend
296
326
  // prompt rendering and detach our keypress handler so arrow keys aren't
@@ -1646,11 +1676,13 @@ class InteractiveShell {
1646
1676
  if (!toolsUsed.includes(toolName)) {
1647
1677
  toolsUsed.push(toolName);
1648
1678
  }
1679
+ this.sessionToolsUsed.add(toolName);
1649
1680
  const filePath = args?.['file_path'];
1650
1681
  if (filePath && (toolName === 'Write' || toolName === 'Edit')) {
1651
1682
  if (!filesModified.includes(filePath)) {
1652
1683
  filesModified.push(filePath);
1653
1684
  }
1685
+ this.sessionFilesModified.add(filePath);
1654
1686
  }
1655
1687
  if (toolName === 'Bash' && args?.['command']) {
1656
1688
  toolDisplay = `Bash(${String(args['command']).slice(0, 120)})`;
@@ -1758,6 +1790,9 @@ class InteractiveShell {
1758
1790
  tokensUsed: event.totalTokens,
1759
1791
  tokenLimit: 200000, // Approximate limit
1760
1792
  });
1793
+ // Roll up to session totals for the session-end Firestore write.
1794
+ this.sessionTokensIn += event.inputTokens || 0;
1795
+ this.sessionTokensOut += event.outputTokens || 0;
1761
1796
  break;
1762
1797
  case 'provider.fallback': {
1763
1798
  // Display fallback notification