amicus 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 (93) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +477 -0
  4. package/bin/amicus.js +382 -0
  5. package/electron/assets/icon.png +0 -0
  6. package/electron/assets/icon.svg +5 -0
  7. package/electron/fold.js +163 -0
  8. package/electron/ipc-setup.js +176 -0
  9. package/electron/load-failsafe.js +85 -0
  10. package/electron/main.js +468 -0
  11. package/electron/preload-setup.js +38 -0
  12. package/electron/preload.js +33 -0
  13. package/electron/setup-ui-alias-script.js +218 -0
  14. package/electron/setup-ui-aliases.js +85 -0
  15. package/electron/setup-ui-keys-script.js +115 -0
  16. package/electron/setup-ui-keys.js +97 -0
  17. package/electron/setup-ui-model.js +138 -0
  18. package/electron/setup-ui-styles.js +327 -0
  19. package/electron/setup-ui.js +465 -0
  20. package/electron/summary.js +118 -0
  21. package/electron/toolbar.js +229 -0
  22. package/electron/window-position.js +35 -0
  23. package/package.json +98 -0
  24. package/scripts/postinstall.js +193 -0
  25. package/scripts/setup-hooks.js +42 -0
  26. package/skill/SKILL.md +976 -0
  27. package/skills/second-opinion/COUNCIL-DESIGN.md +227 -0
  28. package/skills/second-opinion/MODEL-NOTES.md +104 -0
  29. package/skills/second-opinion/SKILL.md +389 -0
  30. package/src/cli-handlers.js +188 -0
  31. package/src/cli.js +400 -0
  32. package/src/conflict.js +144 -0
  33. package/src/context-compression.js +102 -0
  34. package/src/context.js +199 -0
  35. package/src/drift.js +144 -0
  36. package/src/environment.js +157 -0
  37. package/src/headless.js +742 -0
  38. package/src/index.js +106 -0
  39. package/src/jsonl-parser.js +180 -0
  40. package/src/mcp-server.js +625 -0
  41. package/src/mcp-tools.js +407 -0
  42. package/src/opencode-client.js +615 -0
  43. package/src/prompt-builder.js +355 -0
  44. package/src/prompts/cowork-agent-prompt.js +118 -0
  45. package/src/session-manager.js +414 -0
  46. package/src/session.js +180 -0
  47. package/src/sidecar/context-builder.js +297 -0
  48. package/src/sidecar/continue.js +212 -0
  49. package/src/sidecar/crash-handler.js +56 -0
  50. package/src/sidecar/fanout-leg.js +107 -0
  51. package/src/sidecar/fanout-output.js +46 -0
  52. package/src/sidecar/fanout.js +236 -0
  53. package/src/sidecar/interactive.js +217 -0
  54. package/src/sidecar/models.js +135 -0
  55. package/src/sidecar/progress.js +218 -0
  56. package/src/sidecar/read.js +183 -0
  57. package/src/sidecar/resume.js +221 -0
  58. package/src/sidecar/session-utils.js +288 -0
  59. package/src/sidecar/setup-window.js +79 -0
  60. package/src/sidecar/setup.js +280 -0
  61. package/src/sidecar/start.js +251 -0
  62. package/src/utils/agent-mapping.js +138 -0
  63. package/src/utils/alias-audit.js +98 -0
  64. package/src/utils/alias-resolver.js +77 -0
  65. package/src/utils/api-key-store.js +259 -0
  66. package/src/utils/api-key-validation.js +97 -0
  67. package/src/utils/auth-json.js +109 -0
  68. package/src/utils/config.js +291 -0
  69. package/src/utils/curated-models.js +82 -0
  70. package/src/utils/env-compat.js +38 -0
  71. package/src/utils/env-loader.js +54 -0
  72. package/src/utils/idle-watchdog.js +225 -0
  73. package/src/utils/input-validators.js +127 -0
  74. package/src/utils/lifecycle.js +43 -0
  75. package/src/utils/logger.js +84 -0
  76. package/src/utils/mcp-discovery.js +194 -0
  77. package/src/utils/mcp-validators.js +78 -0
  78. package/src/utils/model-catalog.js +103 -0
  79. package/src/utils/model-fetcher.js +179 -0
  80. package/src/utils/model-validator.js +207 -0
  81. package/src/utils/path-setup.js +41 -0
  82. package/src/utils/port-pid.js +39 -0
  83. package/src/utils/prompt-source.js +53 -0
  84. package/src/utils/result-schema.js +261 -0
  85. package/src/utils/server-setup.js +93 -0
  86. package/src/utils/session-abort.js +53 -0
  87. package/src/utils/session-lock.js +95 -0
  88. package/src/utils/shared-server.js +216 -0
  89. package/src/utils/start-helpers.js +76 -0
  90. package/src/utils/thinking-validators.js +92 -0
  91. package/src/utils/update-notifier-loader.js +18 -0
  92. package/src/utils/updater.js +157 -0
  93. package/src/utils/validators.js +300 -0
package/src/index.js ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Amicus - Main Module
3
+ *
4
+ * Spec Reference: §9 Implementation
5
+ * Re-exports all public APIs for the sidecar CLI.
6
+ */
7
+
8
+ // Import sidecar operations from modular components
9
+ const { startSidecar, generateTaskId, runInteractive, HEARTBEAT_INTERVAL } = require('./sidecar/start');
10
+ const { resumeSidecar } = require('./sidecar/resume');
11
+ const { continueSidecar } = require('./sidecar/continue');
12
+ const { readSidecar, listSidecars, formatAge } = require('./sidecar/read');
13
+ const { buildContext, parseDuration } = require('./sidecar/context-builder');
14
+ const { runFanout } = require('./sidecar/fanout');
15
+
16
+ // Import from prompt-builder
17
+ const { buildSystemPrompt, buildPrompts, getSummaryTemplate, SUMMARY_TEMPLATE } = require('./prompt-builder');
18
+
19
+ // Import from headless
20
+ const { runHeadless, extractSummary, formatFoldOutput, DEFAULT_TIMEOUT, FOLD_MARKER, COMPLETE_MARKER } = require('./headless');
21
+
22
+ // Track B modules - Context, Session Management, Conflict & Drift Detection
23
+ const { filterContext, estimateTokens, takeLastNTurns } = require('./context');
24
+ const { createSession, updateSession, getSession, saveConversation, saveSummary, getSessionDir, SESSION_STATUS } = require('./session-manager');
25
+ const { detectConflicts, formatConflictWarning } = require('./conflict');
26
+ const { calculateDrift, formatDriftWarning, countTurnsSince, isDriftSignificant } = require('./drift');
27
+
28
+ // v3 modules - Environment detection & Context compression
29
+ const { detectEnvironment, inferClient, getSessionRoot } = require('./environment');
30
+ const { compressContext, estimateTokenCount, buildPreamble } = require('./context-compression');
31
+
32
+ module.exports = {
33
+ // Canonical Amicus public API (the *Sidecar names below are deprecated shims)
34
+ startAmicus: startSidecar,
35
+ listAmicus: listSidecars,
36
+ resumeAmicus: resumeSidecar,
37
+ continueAmicus: continueSidecar,
38
+ readAmicus: readSidecar,
39
+ runFanout,
40
+
41
+ // DEPRECATED(amicus-shim): remove *Sidecar exports in a future revision — see docs/SHIMS.md
42
+ // Primary sidecar APIs
43
+ startSidecar,
44
+ listSidecars,
45
+ resumeSidecar,
46
+ continueSidecar,
47
+ readSidecar,
48
+ generateTaskId,
49
+
50
+ // Context building
51
+ buildContext,
52
+ parseDuration,
53
+ formatAge,
54
+
55
+ // Re-export from prompt-builder
56
+ buildSystemPrompt,
57
+ buildPrompts,
58
+ getSummaryTemplate,
59
+ SUMMARY_TEMPLATE,
60
+
61
+ // Re-export from headless
62
+ runHeadless,
63
+ extractSummary,
64
+ formatFoldOutput,
65
+ DEFAULT_TIMEOUT,
66
+ FOLD_MARKER,
67
+ COMPLETE_MARKER,
68
+
69
+ // Re-export from context (Track B)
70
+ filterContext,
71
+ estimateTokens,
72
+ takeLastNTurns,
73
+
74
+ // Re-export from session-manager (Track B)
75
+ createSession,
76
+ updateSession,
77
+ getSession,
78
+ saveConversation,
79
+ saveSummary,
80
+ getSessionDir,
81
+ SESSION_STATUS,
82
+
83
+ // Re-export from conflict (Track B)
84
+ detectConflicts,
85
+ formatConflictWarning,
86
+
87
+ // Re-export from drift (Track B)
88
+ calculateDrift,
89
+ formatDriftWarning,
90
+ countTurnsSince,
91
+ isDriftSignificant,
92
+
93
+ // Re-export from environment (v3)
94
+ detectEnvironment,
95
+ inferClient,
96
+ getSessionRoot,
97
+
98
+ // Re-export from context-compression (v3)
99
+ compressContext,
100
+ estimateTokenCount,
101
+ buildPreamble,
102
+
103
+ // Internal exports (for use by sidecar modules)
104
+ runInteractive,
105
+ HEARTBEAT_INTERVAL
106
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * JSONL Parser
3
+ *
4
+ * Spec Reference: §5.3 Context Filtering Algorithm, §5.3 Context Format
5
+ * Parses Claude Code conversation JSONL files and formats them for context passing.
6
+ */
7
+
8
+ const fs = require('fs');
9
+
10
+ /**
11
+ * Parse a single line of JSONL content
12
+ * @param {string} line - A single line from a JSONL file
13
+ * @returns {object|null} Parsed JSON object or null if invalid
14
+ */
15
+ function parseJSONLLine(line) {
16
+ if (!line || !line.trim()) {
17
+ return null;
18
+ }
19
+
20
+ try {
21
+ return JSON.parse(line);
22
+ } catch (error) {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Read and parse a JSONL file
29
+ * @param {string} filePath - Path to the JSONL file
30
+ * @returns {object[]} Array of parsed message objects
31
+ * @throws {Error} If file cannot be read
32
+ */
33
+ function readJSONL(filePath) {
34
+ const content = fs.readFileSync(filePath, 'utf-8');
35
+
36
+ return content
37
+ .split('\n')
38
+ .map(line => parseJSONLLine(line))
39
+ .filter(Boolean);
40
+ }
41
+
42
+ /**
43
+ * Extract timestamp from a message object
44
+ * @param {object} message - Message object with optional timestamp field
45
+ * @returns {Date|null} Date object or null if invalid/missing
46
+ */
47
+ function extractTimestamp(message) {
48
+ if (!message || !message.timestamp) {
49
+ return null;
50
+ }
51
+
52
+ const date = new Date(message.timestamp);
53
+
54
+ // Check if date is valid (invalid dates return NaN for getTime())
55
+ if (isNaN(date.getTime())) {
56
+ return null;
57
+ }
58
+
59
+ return date;
60
+ }
61
+
62
+ /**
63
+ * Format a timestamp for display (e.g., "10:30" or "10:30 AM")
64
+ * @param {string} timestamp - ISO timestamp string
65
+ * @returns {string} Formatted time string
66
+ */
67
+ function formatTime(timestamp) {
68
+ const date = new Date(timestamp);
69
+ return date.toLocaleTimeString('en-US', {
70
+ hour: '2-digit',
71
+ minute: '2-digit'
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Extract text content from a message
77
+ * Handles both string content and array content (Claude API format)
78
+ * @param {object} message - Message object with content field
79
+ * @returns {string} Extracted text content
80
+ */
81
+ function extractContent(message) {
82
+ if (!message || !message.message) {
83
+ return '';
84
+ }
85
+
86
+ const content = message.message.content;
87
+
88
+ if (!content) {
89
+ return '';
90
+ }
91
+
92
+ // String content (simple format)
93
+ if (typeof content === 'string') {
94
+ return content;
95
+ }
96
+
97
+ // Array content (Claude API format with text blocks)
98
+ if (Array.isArray(content)) {
99
+ return content
100
+ .map(block => block.text || '')
101
+ .join('');
102
+ }
103
+
104
+ return '';
105
+ }
106
+
107
+ /**
108
+ * Format a single message for context output
109
+ * Spec Reference: §5.3 Context Format
110
+ *
111
+ * @param {object} message - Parsed message object
112
+ * @returns {string} Formatted message string
113
+ *
114
+ * @example
115
+ * // User message
116
+ * formatMessage({ type: 'user', message: { content: 'Hello' }, timestamp: '2025-01-25T10:30:00Z' })
117
+ * // Returns: "[User @ 10:30 AM] Hello"
118
+ *
119
+ * @example
120
+ * // Tool use message
121
+ * formatMessage({ type: 'tool_use', tool: 'Read', input: { path: 'file.ts' }, timestamp: '...' })
122
+ * // Returns: "[Tool: Read file.ts]"
123
+ */
124
+ function formatMessage(message) {
125
+ if (!message || !message.type) {
126
+ return '';
127
+ }
128
+
129
+ const time = message.timestamp ? formatTime(message.timestamp) : '';
130
+
131
+ switch (message.type) {
132
+ case 'user': {
133
+ const content = extractContent(message);
134
+ return `[User @ ${time}] ${content}`;
135
+ }
136
+
137
+ case 'assistant': {
138
+ const content = extractContent(message);
139
+ return `[Assistant @ ${time}] ${content}`;
140
+ }
141
+
142
+ case 'tool_use': {
143
+ const toolName = message.tool || 'Unknown';
144
+ const path = message.input?.path || '';
145
+ return path ? `[Tool: ${toolName} ${path}]` : `[Tool: ${toolName}]`;
146
+ }
147
+
148
+ default:
149
+ return '';
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Format an array of messages into a context string
155
+ * Spec Reference: §5.3 Context Format
156
+ *
157
+ * @param {object[]} messages - Array of parsed message objects
158
+ * @returns {string} Formatted context string with messages separated by double newlines
159
+ *
160
+ * @example
161
+ * // Format: [User @ 10:30] message\n\n[Assistant @ 10:31] response\n\n[Tool: Read path/file]
162
+ */
163
+ function formatContext(messages) {
164
+ if (!messages || messages.length === 0) {
165
+ return '';
166
+ }
167
+
168
+ return messages
169
+ .map(msg => formatMessage(msg))
170
+ .filter(Boolean)
171
+ .join('\n\n');
172
+ }
173
+
174
+ module.exports = {
175
+ parseJSONLLine,
176
+ readJSONL,
177
+ extractTimestamp,
178
+ formatMessage,
179
+ formatContext
180
+ };