greprag 5.76.0 → 5.78.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.
- package/app/app.css +342 -0
- package/app/app.js +360 -0
- package/app/favicon.svg +4 -0
- package/app/index.html +54 -0
- package/dist/app-settings.js +162 -0
- package/dist/commands/app-model.js +308 -0
- package/dist/commands/app.js +310 -0
- package/dist/commands/codex-doctor.js +1 -0
- package/dist/commands/delivery-reminder.js +5 -5
- package/dist/commands/init.js +18 -3
- package/dist/commands/opencode-interrupt.js +11 -2
- package/dist/commands/persona-reminder.js +9 -0
- package/dist/commands/persona.js +191 -0
- package/dist/commands/reminder-registry.js +2 -0
- package/dist/commands/status.js +1 -0
- package/dist/hook.js +30 -4
- package/dist/index.js +23 -1
- package/dist/opencode-plugin.bundle.js +360 -279
- package/dist/opencode-plugin.js +45 -2
- package/dist/reminder-injector.js +2 -1
- package/package.json +2 -1
- package/scripts/postinstall.js +2 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Read model for the local GrepRAG customer app.
|
|
3
|
+
* adr: adr/local-app-control-plane.md
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.discoverLocalProjects = discoverLocalProjects;
|
|
40
|
+
exports.buildAppModel = buildAppModel;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const app_settings_1 = require("../app-settings");
|
|
45
|
+
const project_anchor_1 = require("../project-anchor");
|
|
46
|
+
const native_skill_mirror_1 = require("../native-skill-mirror");
|
|
47
|
+
const status_1 = require("./status");
|
|
48
|
+
const config_1 = require("./config");
|
|
49
|
+
const reminder_registry_1 = require("./reminder-registry");
|
|
50
|
+
const reminder_injector_1 = require("../reminder-injector");
|
|
51
|
+
const INTERNAL_STORE_KINDS = new Set([
|
|
52
|
+
'memory', 'inbox', 'fix', 'docptr', 'skillgain', 'skill', 'parity', 'doctrine',
|
|
53
|
+
]);
|
|
54
|
+
const INTERRUPT_COPY = {
|
|
55
|
+
'os-primer': { label: 'Operating guidance', description: 'Teaches agents GrepRAG’s recovery and coordination rules.', group: 'Core guidance' },
|
|
56
|
+
'inbox-primer': { label: 'Inbox guidance', description: 'Explains live cross-agent messages and watcher setup.', group: 'Communication' },
|
|
57
|
+
'load-primer': { label: 'Feature discovery', description: 'Shows agents how to load GrepRAG capabilities on demand.', group: 'Core guidance' },
|
|
58
|
+
'chip-spawn-pointer': { label: 'Claude delegation', description: 'Guides Claude Code when work should move to another task.', group: 'Delegation' },
|
|
59
|
+
'codex-chip-spawn-pointer': { label: 'Codex delegation', description: 'Guides Codex when work should move to another task.', group: 'Delegation' },
|
|
60
|
+
'email-primer': { label: 'Agent email', description: 'Explains real email separately from internal agent messages.', group: 'Communication' },
|
|
61
|
+
'corpus-announce': { label: 'Knowledge reminders', description: 'Points agents to relevant reference collections.', group: 'Knowledge' },
|
|
62
|
+
'doc-pointer-announce': { label: 'Project documents', description: 'Shows the project’s core reference documents at startup.', group: 'Knowledge' },
|
|
63
|
+
'skill-gain-announce': { label: 'Skill improvements', description: 'Surfaces newly learned improvements for installed skills.', group: 'Skills' },
|
|
64
|
+
'skill-mirror-announce': { label: 'Available skills', description: 'Makes mirrored skills discoverable to each agent.', group: 'Skills' },
|
|
65
|
+
'procedure-announce': { label: 'Saved procedures', description: 'Reminds agents about active project procedures.', group: 'Automation' },
|
|
66
|
+
'delivery-control': { label: 'Delivery guidance', description: 'Keeps code delivery and production verification consistent.', group: 'Automation' },
|
|
67
|
+
'persona-announce': { label: 'Speaking style', description: 'Applies the selected GrepRAG persona across agents.', group: 'Personalization' },
|
|
68
|
+
'loadout-registrar': { label: 'Equipped loadouts', description: 'Makes equipped skill collections available when relevant.', group: 'Skills' },
|
|
69
|
+
'setup-warning': { label: 'Setup warnings', description: 'Calls out missing configuration that prevents GrepRAG from working.', group: 'Health' },
|
|
70
|
+
'version-upgrade': { label: 'Update notices', description: 'Warns when the installed CLI is behind the current release.', group: 'Health' },
|
|
71
|
+
'enrichment-health': { label: 'Knowledge health', description: 'Warns when knowledge enrichment is unavailable.', group: 'Health' },
|
|
72
|
+
'assistant-doctrine': { label: 'Assistant role', description: 'Loads the executive-assistant behavior for its assigned project.', group: 'Roles' },
|
|
73
|
+
'memory-primer': { label: 'Memory guidance', description: 'Teaches agents how and when to retrieve project memory.', group: 'Memory' },
|
|
74
|
+
'watcher-arm': { label: 'Live inbox checks', description: 'Reminds Claude Code to keep live message delivery connected.', group: 'Communication' },
|
|
75
|
+
'mechanic-friction': { label: 'Automatic repair prompts', description: 'Surfaces repeated harness friction for durable repair.', group: 'Automation' },
|
|
76
|
+
collision: { label: 'Collision warnings', description: 'Warns before shared Git actions when another session is active.', group: 'Delegation' },
|
|
77
|
+
};
|
|
78
|
+
function readObject(file) {
|
|
79
|
+
try {
|
|
80
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
81
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
82
|
+
? value
|
|
83
|
+
: {};
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function countDirectories(root) {
|
|
90
|
+
let names = [];
|
|
91
|
+
try {
|
|
92
|
+
names = fs.readdirSync(root, { withFileTypes: true })
|
|
93
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
94
|
+
.map(entry => entry.name)
|
|
95
|
+
.sort();
|
|
96
|
+
}
|
|
97
|
+
catch { /* missing root */ }
|
|
98
|
+
const state = readObject(path.join(root, '.greprag-mirror.json'));
|
|
99
|
+
const skills = state.skills && typeof state.skills === 'object' && !Array.isArray(state.skills)
|
|
100
|
+
? state.skills
|
|
101
|
+
: {};
|
|
102
|
+
return { count: names.length, names, managed: Object.keys(skills).length };
|
|
103
|
+
}
|
|
104
|
+
function discoverLocalProjects(cwd, homeDir = os.homedir()) {
|
|
105
|
+
const candidates = [path.resolve(cwd)];
|
|
106
|
+
const registry = readObject(path.join(homeDir, '.greprag', 'projects.json'));
|
|
107
|
+
for (const value of Object.values(registry)) {
|
|
108
|
+
if (typeof value === 'string' && value.trim())
|
|
109
|
+
candidates.push(path.resolve(value));
|
|
110
|
+
}
|
|
111
|
+
const byId = new Map();
|
|
112
|
+
for (const candidate of [...new Set(candidates)]) {
|
|
113
|
+
if (!fs.existsSync(candidate))
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
const anchor = (0, project_anchor_1.readAnchor)(candidate);
|
|
117
|
+
if (!byId.has(anchor.projectId)) {
|
|
118
|
+
byId.set(anchor.projectId, {
|
|
119
|
+
id: anchor.projectId,
|
|
120
|
+
name: anchor.projectName,
|
|
121
|
+
path: candidate,
|
|
122
|
+
initialized: anchor.initialized,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch { /* an unreadable registered path should not break the app */ }
|
|
127
|
+
}
|
|
128
|
+
return [...byId.values()];
|
|
129
|
+
}
|
|
130
|
+
function asRefresh(store) {
|
|
131
|
+
const metadata = store.metadata;
|
|
132
|
+
if (!metadata || typeof metadata !== 'object')
|
|
133
|
+
return null;
|
|
134
|
+
const refresh = metadata.refresh;
|
|
135
|
+
return refresh && typeof refresh === 'object' && !Array.isArray(refresh)
|
|
136
|
+
? refresh
|
|
137
|
+
: null;
|
|
138
|
+
}
|
|
139
|
+
function corpusStoreView(store) {
|
|
140
|
+
const refresh = asRefresh(store);
|
|
141
|
+
const failures = typeof refresh?.consecutiveFailures === 'number'
|
|
142
|
+
? refresh.consecutiveFailures
|
|
143
|
+
: 0;
|
|
144
|
+
const policy = typeof refresh?.policy === 'string' ? refresh.policy : 'off';
|
|
145
|
+
const hasSource = typeof refresh?.url === 'string' && refresh.url.length > 0;
|
|
146
|
+
return {
|
|
147
|
+
id: store.id,
|
|
148
|
+
name: store.name,
|
|
149
|
+
kind: store.kind,
|
|
150
|
+
tags: store.tags || [],
|
|
151
|
+
nodes: store.nodeCount || 0,
|
|
152
|
+
words: store.wordCount || 0,
|
|
153
|
+
policy: hasSource ? policy : 'frozen',
|
|
154
|
+
lastCheckedAt: typeof refresh?.lastFetchedAt === 'string'
|
|
155
|
+
? refresh.lastFetchedAt
|
|
156
|
+
: store.updatedAt || store.createdAt,
|
|
157
|
+
nextCheckAt: typeof refresh?.nextCheckAt === 'string' ? refresh.nextCheckAt : null,
|
|
158
|
+
failures,
|
|
159
|
+
health: failures > 0 ? 'attention' : 'healthy',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function fetchJson(url, apiKey) {
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const timer = setTimeout(() => controller.abort(), 7_000);
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(url, {
|
|
167
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
168
|
+
signal: controller.signal,
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok)
|
|
171
|
+
throw new Error(`HTTP ${response.status}`);
|
|
172
|
+
return response.json();
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function readCloud(apiUrl, apiKey) {
|
|
179
|
+
if (!apiKey)
|
|
180
|
+
return { status: 'unconfigured', stores: [], skills: [], projects: [], error: null };
|
|
181
|
+
const [storesResult, skillsResult, projectsResult] = await Promise.allSettled([
|
|
182
|
+
fetchJson(`${apiUrl}/v1/stores`, apiKey),
|
|
183
|
+
fetchJson(`${apiUrl}/v1/skillmirror`, apiKey),
|
|
184
|
+
fetchJson(`${apiUrl}/v1/projects/discover`, apiKey),
|
|
185
|
+
]);
|
|
186
|
+
const stores = storesResult.status === 'fulfilled' ? storesResult.value.stores || [] : [];
|
|
187
|
+
const skills = skillsResult.status === 'fulfilled' ? skillsResult.value.skills || [] : [];
|
|
188
|
+
const projects = projectsResult.status === 'fulfilled' ? projectsResult.value.projects || [] : [];
|
|
189
|
+
const errors = [storesResult, skillsResult, projectsResult]
|
|
190
|
+
.filter((result) => result.status === 'rejected')
|
|
191
|
+
.map(result => result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
192
|
+
return {
|
|
193
|
+
status: errors.length === 3 ? 'offline' : errors.length ? 'partial' : 'connected',
|
|
194
|
+
stores,
|
|
195
|
+
skills,
|
|
196
|
+
projects,
|
|
197
|
+
error: errors.length ? errors.join('; ') : null,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function platformLabel(key) {
|
|
201
|
+
return ({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', grok: 'Grok Build' })[key];
|
|
202
|
+
}
|
|
203
|
+
function platformSkillRoots(key, projectPath, homeDir) {
|
|
204
|
+
if (key === 'grok')
|
|
205
|
+
return [path.join(homeDir, '.grok', 'skills')];
|
|
206
|
+
const native = key === 'claude' ? 'claude-code' : key;
|
|
207
|
+
return [(0, native_skill_mirror_1.nativeSkillRoot)(native, homeDir), (0, native_skill_mirror_1.nativeProjectSkillRoot)(native, projectPath)];
|
|
208
|
+
}
|
|
209
|
+
async function buildAppModel(cwd, requestedProjectId, homeDir = os.homedir()) {
|
|
210
|
+
const projects = discoverLocalProjects(cwd, homeDir);
|
|
211
|
+
const selected = projects.find(project => project.id === requestedProjectId) || projects[0];
|
|
212
|
+
const selectedPath = selected?.path || path.resolve(cwd);
|
|
213
|
+
const status = (0, status_1.buildStatus)(selectedPath);
|
|
214
|
+
const config = (0, config_1.getConfig)();
|
|
215
|
+
const cloud = await readCloud(config.apiUrl, config.apiKey);
|
|
216
|
+
const localSettings = (0, app_settings_1.readLocalAppSettings)(homeDir);
|
|
217
|
+
const disabled = new Set(localSettings.interrupts.disabled);
|
|
218
|
+
const platforms = Object.keys(status.platforms).map(key => {
|
|
219
|
+
const roots = platformSkillRoots(key, selectedPath, homeDir).map(countDirectories);
|
|
220
|
+
const names = [...new Set(roots.flatMap(root => root.names))].sort();
|
|
221
|
+
return {
|
|
222
|
+
id: key,
|
|
223
|
+
label: platformLabel(key),
|
|
224
|
+
configured: status.platforms[key].configured,
|
|
225
|
+
installed: status.platforms[key].config_present,
|
|
226
|
+
skillCount: names.length,
|
|
227
|
+
managedSkillCount: roots.reduce((sum, root) => sum + root.managed, 0),
|
|
228
|
+
skills: names,
|
|
229
|
+
note: status.platforms[key].note || '',
|
|
230
|
+
canToggleSkillAdapter: key === 'codex' || key === 'opencode',
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
const corpusStores = cloud.stores.filter(store => !INTERNAL_STORE_KINDS.has(store.kind)).map(corpusStoreView);
|
|
234
|
+
const projectMemory = cloud.projects.find(project => project.id === selected?.id);
|
|
235
|
+
const memoryRows = projectMemory
|
|
236
|
+
? Object.values(projectMemory.row_counts || {}).reduce((sum, count) => sum + count, 0)
|
|
237
|
+
: 0;
|
|
238
|
+
const corpusFailures = corpusStores.reduce((sum, store) => sum + store.failures, 0);
|
|
239
|
+
const configuredAgents = platforms.filter(platform => platform.configured).length;
|
|
240
|
+
const issues = [];
|
|
241
|
+
if (!status.auth.api_key_present)
|
|
242
|
+
issues.push('Connect a GrepRAG account to enable cloud memory and knowledge.');
|
|
243
|
+
if (configuredAgents === 0)
|
|
244
|
+
issues.push('No agent integration is fully connected.');
|
|
245
|
+
if (status.windows_shims?.mismatch)
|
|
246
|
+
issues.push('An older GrepRAG command is still active on this machine.');
|
|
247
|
+
if (cloud.status === 'offline')
|
|
248
|
+
issues.push('Cloud metrics are unavailable right now. Local settings still work.');
|
|
249
|
+
if (corpusFailures > 0)
|
|
250
|
+
issues.push(`${corpusFailures} knowledge refresh failure${corpusFailures === 1 ? '' : 's'} need attention.`);
|
|
251
|
+
return {
|
|
252
|
+
app: { version: status.version, cwd: path.resolve(cwd) },
|
|
253
|
+
account: {
|
|
254
|
+
connected: status.auth.api_key_present,
|
|
255
|
+
handle: status.identity.handle,
|
|
256
|
+
tenantId: status.identity.tenant_id,
|
|
257
|
+
cloudStatus: cloud.status,
|
|
258
|
+
cloudError: cloud.error,
|
|
259
|
+
},
|
|
260
|
+
health: { state: issues.length ? 'attention' : 'healthy', issues },
|
|
261
|
+
projects,
|
|
262
|
+
selectedProject: selected ? {
|
|
263
|
+
...selected,
|
|
264
|
+
settings: (0, app_settings_1.readProjectAppSettings)(selectedPath),
|
|
265
|
+
} : null,
|
|
266
|
+
agents: platforms,
|
|
267
|
+
memory: {
|
|
268
|
+
nodes: memoryRows,
|
|
269
|
+
stores: projectMemory ? 1 : 0,
|
|
270
|
+
firstActivity: projectMemory?.first_activity || null,
|
|
271
|
+
lastActivity: projectMemory?.last_activity || null,
|
|
272
|
+
captureEnabled: status.project.memory_capture,
|
|
273
|
+
recapEnabled: status.project.session_start_recap,
|
|
274
|
+
},
|
|
275
|
+
skills: {
|
|
276
|
+
mirrored: cloud.skills.length,
|
|
277
|
+
installedUnique: new Set(platforms.flatMap(platform => platform.skills)).size,
|
|
278
|
+
adaptersActive: platforms.filter(platform => platform.managedSkillCount > 0).length,
|
|
279
|
+
},
|
|
280
|
+
corpus: {
|
|
281
|
+
status: cloud.status,
|
|
282
|
+
stores: corpusStores,
|
|
283
|
+
count: corpusStores.length,
|
|
284
|
+
nodes: corpusStores.reduce((sum, store) => sum + store.nodes, 0),
|
|
285
|
+
words: corpusStores.reduce((sum, store) => sum + store.words, 0),
|
|
286
|
+
failures: corpusFailures,
|
|
287
|
+
},
|
|
288
|
+
notifications: {
|
|
289
|
+
modules: reminder_registry_1.REGISTRY.map(module => ({
|
|
290
|
+
id: module.id,
|
|
291
|
+
...(INTERRUPT_COPY[module.id] || {
|
|
292
|
+
label: module.id,
|
|
293
|
+
description: 'Controls this GrepRAG startup or just-in-time guidance.',
|
|
294
|
+
group: 'Other',
|
|
295
|
+
}),
|
|
296
|
+
enabled: !disabled.has(module.id),
|
|
297
|
+
surface: module.source === 'command' ? 'Before sensitive actions' : 'Agent guidance',
|
|
298
|
+
harnesses: module.harnesses || ['claude-code', 'codex', 'opencode', 'grok'],
|
|
299
|
+
})),
|
|
300
|
+
},
|
|
301
|
+
advanced: {
|
|
302
|
+
mechanicEnabled: !(0, reminder_injector_1.mechanicKilled)(),
|
|
303
|
+
settingsPath: path.join(homeDir, '.greprag', 'settings.json'),
|
|
304
|
+
projectSettingsPath: status.project.anchor_path,
|
|
305
|
+
apiUrl: config.apiUrl,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** `greprag` local customer app launcher and loopback server.
|
|
3
|
+
* adr: adr/local-app-control-plane.md
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.browserLaunchCommand = browserLaunchCommand;
|
|
40
|
+
exports.openDefaultBrowser = openDefaultBrowser;
|
|
41
|
+
exports.startAppServer = startAppServer;
|
|
42
|
+
exports.runApp = runApp;
|
|
43
|
+
const crypto = __importStar(require("node:crypto"));
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const http = __importStar(require("node:http"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const node_child_process_1 = require("node:child_process");
|
|
48
|
+
const app_model_1 = require("./app-model");
|
|
49
|
+
const app_settings_1 = require("../app-settings");
|
|
50
|
+
const reminder_registry_1 = require("./reminder-registry");
|
|
51
|
+
const config_1 = require("./config");
|
|
52
|
+
const native_skill_mirror_1 = require("../native-skill-mirror");
|
|
53
|
+
const LOOPBACK = '127.0.0.1';
|
|
54
|
+
const BODY_LIMIT = 64 * 1024;
|
|
55
|
+
function appAssetRoot() {
|
|
56
|
+
return path.join(__dirname, '..', '..', 'app');
|
|
57
|
+
}
|
|
58
|
+
function mimeType(file) {
|
|
59
|
+
if (file.endsWith('.html'))
|
|
60
|
+
return 'text/html; charset=utf-8';
|
|
61
|
+
if (file.endsWith('.css'))
|
|
62
|
+
return 'text/css; charset=utf-8';
|
|
63
|
+
if (file.endsWith('.js'))
|
|
64
|
+
return 'text/javascript; charset=utf-8';
|
|
65
|
+
if (file.endsWith('.svg'))
|
|
66
|
+
return 'image/svg+xml';
|
|
67
|
+
return 'application/octet-stream';
|
|
68
|
+
}
|
|
69
|
+
function sendJson(res, status, body) {
|
|
70
|
+
const payload = JSON.stringify(body);
|
|
71
|
+
res.writeHead(status, {
|
|
72
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
73
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
74
|
+
'Cache-Control': 'no-store',
|
|
75
|
+
});
|
|
76
|
+
res.end(payload);
|
|
77
|
+
}
|
|
78
|
+
function safeEqual(a, b) {
|
|
79
|
+
const left = Buffer.from(a);
|
|
80
|
+
const right = Buffer.from(b);
|
|
81
|
+
return left.length === right.length && crypto.timingSafeEqual(left, right);
|
|
82
|
+
}
|
|
83
|
+
function cookieValue(req, name) {
|
|
84
|
+
for (const part of String(req.headers.cookie || '').split(';')) {
|
|
85
|
+
const [key, ...value] = part.trim().split('=');
|
|
86
|
+
if (key === name)
|
|
87
|
+
return decodeURIComponent(value.join('='));
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
async function readBody(req) {
|
|
92
|
+
const chunks = [];
|
|
93
|
+
let size = 0;
|
|
94
|
+
for await (const chunk of req) {
|
|
95
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
96
|
+
size += buffer.length;
|
|
97
|
+
if (size > BODY_LIMIT)
|
|
98
|
+
throw new Error('Request is too large.');
|
|
99
|
+
chunks.push(buffer);
|
|
100
|
+
}
|
|
101
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
102
|
+
if (!raw)
|
|
103
|
+
return {};
|
|
104
|
+
const parsed = JSON.parse(raw);
|
|
105
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
106
|
+
throw new Error('Expected a JSON object.');
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
function resolveProject(cwd, projectId) {
|
|
110
|
+
if (typeof projectId !== 'string')
|
|
111
|
+
throw new Error('Project id is required.');
|
|
112
|
+
const project = (0, app_model_1.discoverLocalProjects)(cwd).find(candidate => candidate.id === projectId);
|
|
113
|
+
if (!project)
|
|
114
|
+
throw new Error('That project is not registered on this machine.');
|
|
115
|
+
return project.path;
|
|
116
|
+
}
|
|
117
|
+
function projectPatch(body) {
|
|
118
|
+
const allowed = new Set([
|
|
119
|
+
'memoryCapture', 'sessionStartRecap', 'inboxNotify', 'crushWrap',
|
|
120
|
+
'contextGovernor', 'contextThreshold', 'pngArchive', 'emailAutosave',
|
|
121
|
+
]);
|
|
122
|
+
const patch = body.patch;
|
|
123
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch))
|
|
124
|
+
throw new Error('Settings patch is required.');
|
|
125
|
+
for (const key of Object.keys(patch))
|
|
126
|
+
if (!allowed.has(key))
|
|
127
|
+
throw new Error(`Unknown setting: ${key}`);
|
|
128
|
+
return patch;
|
|
129
|
+
}
|
|
130
|
+
async function setAdapter(body, cwd) {
|
|
131
|
+
const platform = body.platform;
|
|
132
|
+
if (platform !== 'codex' && platform !== 'opencode')
|
|
133
|
+
throw new Error('Only Codex and OpenCode use optional skill adapters.');
|
|
134
|
+
if (typeof body.enabled !== 'boolean')
|
|
135
|
+
throw new Error('enabled must be true or false.');
|
|
136
|
+
const projectDir = resolveProject(cwd, body.projectId);
|
|
137
|
+
const project = (0, app_model_1.discoverLocalProjects)(cwd).find(candidate => candidate.path === projectDir);
|
|
138
|
+
const nativePlatform = platform;
|
|
139
|
+
if (!body.enabled) {
|
|
140
|
+
return (0, native_skill_mirror_1.applyNativeSkillMirror)({
|
|
141
|
+
platform: nativePlatform,
|
|
142
|
+
skills: [],
|
|
143
|
+
projectDir,
|
|
144
|
+
prune: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const config = (0, config_1.getConfig)();
|
|
148
|
+
if (!config.apiKey)
|
|
149
|
+
throw new Error('Connect GrepRAG before enabling skill adapters.');
|
|
150
|
+
return (0, native_skill_mirror_1.syncNativeSkillMirror)({
|
|
151
|
+
platform: nativePlatform,
|
|
152
|
+
apiUrl: config.apiUrl,
|
|
153
|
+
apiKey: config.apiKey,
|
|
154
|
+
projectId: project.id,
|
|
155
|
+
projectDir,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function browserLaunchCommand(url, platform = process.platform) {
|
|
159
|
+
if (platform === 'win32')
|
|
160
|
+
return { command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
|
|
161
|
+
if (platform === 'darwin')
|
|
162
|
+
return { command: 'open', args: [url] };
|
|
163
|
+
return { command: 'xdg-open', args: [url] };
|
|
164
|
+
}
|
|
165
|
+
function openDefaultBrowser(url) {
|
|
166
|
+
const launch = browserLaunchCommand(url);
|
|
167
|
+
const child = (0, node_child_process_1.spawn)(launch.command, launch.args, { detached: true, stdio: 'ignore', windowsHide: true });
|
|
168
|
+
child.unref();
|
|
169
|
+
}
|
|
170
|
+
async function startAppServer(options = {}) {
|
|
171
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
172
|
+
const token = crypto.randomBytes(24).toString('base64url');
|
|
173
|
+
const idleTimeoutMs = options.idleTimeoutMs === undefined ? 90_000 : options.idleTimeoutMs;
|
|
174
|
+
const startedAt = Date.now();
|
|
175
|
+
let lastHeartbeat = Date.now();
|
|
176
|
+
let authenticatedOnce = false;
|
|
177
|
+
let origin = '';
|
|
178
|
+
const server = http.createServer(async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
const requestUrl = new URL(req.url || '/', origin || `http://${LOOPBACK}`);
|
|
181
|
+
if (requestUrl.pathname === '/health')
|
|
182
|
+
return sendJson(res, 200, { ok: true });
|
|
183
|
+
const queryToken = requestUrl.searchParams.get('token');
|
|
184
|
+
if (requestUrl.pathname === '/' && queryToken && safeEqual(queryToken, token)) {
|
|
185
|
+
authenticatedOnce = true;
|
|
186
|
+
lastHeartbeat = Date.now();
|
|
187
|
+
res.writeHead(303, {
|
|
188
|
+
Location: '/',
|
|
189
|
+
'Set-Cookie': `greprag_session=${encodeURIComponent(token)}; HttpOnly; SameSite=Strict; Path=/`,
|
|
190
|
+
'Cache-Control': 'no-store',
|
|
191
|
+
});
|
|
192
|
+
res.end();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const authenticated = safeEqual(cookieValue(req, 'greprag_session') || '', token);
|
|
196
|
+
if (!authenticated)
|
|
197
|
+
return sendJson(res, 401, { ok: false, error: 'Open GrepRAG from the CLI again.' });
|
|
198
|
+
lastHeartbeat = Date.now();
|
|
199
|
+
if (req.method !== 'GET' && req.headers.origin !== origin) {
|
|
200
|
+
return sendJson(res, 403, { ok: false, error: 'Request origin was rejected.' });
|
|
201
|
+
}
|
|
202
|
+
if (requestUrl.pathname === '/api/heartbeat' && req.method === 'POST') {
|
|
203
|
+
return sendJson(res, 200, { ok: true });
|
|
204
|
+
}
|
|
205
|
+
if (requestUrl.pathname === '/api/model' && req.method === 'GET') {
|
|
206
|
+
const model = await (0, app_model_1.buildAppModel)(cwd, requestUrl.searchParams.get('project') || undefined);
|
|
207
|
+
return sendJson(res, 200, { ok: true, model });
|
|
208
|
+
}
|
|
209
|
+
if (requestUrl.pathname === '/api/project-settings' && req.method === 'PATCH') {
|
|
210
|
+
const body = await readBody(req);
|
|
211
|
+
const projectDir = resolveProject(cwd, body.projectId);
|
|
212
|
+
const settings = (0, app_settings_1.updateProjectAppSettings)(projectDir, projectPatch(body));
|
|
213
|
+
return sendJson(res, 200, { ok: true, settings });
|
|
214
|
+
}
|
|
215
|
+
if (requestUrl.pathname === '/api/interrupts' && req.method === 'PATCH') {
|
|
216
|
+
const body = await readBody(req);
|
|
217
|
+
if (typeof body.id !== 'string' || !reminder_registry_1.REGISTRY.some(module => module.id === body.id)) {
|
|
218
|
+
throw new Error('Unknown guidance feature.');
|
|
219
|
+
}
|
|
220
|
+
if (typeof body.enabled !== 'boolean')
|
|
221
|
+
throw new Error('enabled must be true or false.');
|
|
222
|
+
const settings = (0, app_settings_1.setInterruptEnabled)(body.id, body.enabled);
|
|
223
|
+
return sendJson(res, 200, { ok: true, settings });
|
|
224
|
+
}
|
|
225
|
+
if (requestUrl.pathname === '/api/skill-adapter' && req.method === 'POST') {
|
|
226
|
+
const body = await readBody(req);
|
|
227
|
+
const result = await setAdapter(body, cwd);
|
|
228
|
+
return sendJson(res, 200, { ok: true, result });
|
|
229
|
+
}
|
|
230
|
+
const assetName = requestUrl.pathname === '/' ? 'index.html' : requestUrl.pathname.slice(1);
|
|
231
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(assetName))
|
|
232
|
+
return sendJson(res, 404, { ok: false, error: 'Not found.' });
|
|
233
|
+
const file = path.join(appAssetRoot(), assetName);
|
|
234
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
235
|
+
return sendJson(res, 404, { ok: false, error: 'Not found.' });
|
|
236
|
+
const payload = fs.readFileSync(file);
|
|
237
|
+
res.writeHead(200, {
|
|
238
|
+
'Content-Type': mimeType(file),
|
|
239
|
+
'Content-Length': payload.length,
|
|
240
|
+
'Cache-Control': assetName === 'index.html' ? 'no-store' : 'private, max-age=300',
|
|
241
|
+
'Content-Security-Policy': "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'",
|
|
242
|
+
'X-Content-Type-Options': 'nosniff',
|
|
243
|
+
'Referrer-Policy': 'no-referrer',
|
|
244
|
+
});
|
|
245
|
+
res.end(payload);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
const message = error instanceof Error ? error.message : 'Request failed.';
|
|
249
|
+
sendJson(res, 400, { ok: false, error: message });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
await new Promise((resolve, reject) => {
|
|
253
|
+
server.once('error', reject);
|
|
254
|
+
server.listen(options.port || 0, LOOPBACK, () => resolve());
|
|
255
|
+
});
|
|
256
|
+
const address = server.address();
|
|
257
|
+
if (!address || typeof address === 'string')
|
|
258
|
+
throw new Error('Could not resolve the local app port.');
|
|
259
|
+
origin = `http://${LOOPBACK}:${address.port}`;
|
|
260
|
+
const idleTimer = idleTimeoutMs > 0 ? setInterval(() => {
|
|
261
|
+
const idle = Date.now() - lastHeartbeat > idleTimeoutMs;
|
|
262
|
+
const neverOpened = !authenticatedOnce && Date.now() - startedAt > Math.max(idleTimeoutMs, 300_000);
|
|
263
|
+
if ((authenticatedOnce && idle) || neverOpened)
|
|
264
|
+
server.close();
|
|
265
|
+
}, Math.min(15_000, Math.max(1_000, Math.floor(idleTimeoutMs / 3)))) : null;
|
|
266
|
+
idleTimer?.unref();
|
|
267
|
+
const close = async () => {
|
|
268
|
+
if (idleTimer)
|
|
269
|
+
clearInterval(idleTimer);
|
|
270
|
+
if (!server.listening)
|
|
271
|
+
return;
|
|
272
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
273
|
+
};
|
|
274
|
+
const launchUrl = `${origin}/?token=${encodeURIComponent(token)}`;
|
|
275
|
+
if (options.openBrowser !== false)
|
|
276
|
+
openDefaultBrowser(launchUrl);
|
|
277
|
+
return { url: origin, launchUrl, token, close };
|
|
278
|
+
}
|
|
279
|
+
function flagValue(args, flag) {
|
|
280
|
+
const index = args.indexOf(flag);
|
|
281
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
282
|
+
}
|
|
283
|
+
async function runApp(args = []) {
|
|
284
|
+
if (args.includes('--help') || args.includes('-h') || args.includes('help')) {
|
|
285
|
+
console.log(`greprag — open the local GrepRAG app\n\n greprag\n greprag app [--port <number>] [--no-open]\n greprag dashboard [--port <number>] [--no-open]\n\nThe app binds only to 127.0.0.1 and stops after the browser disconnects.`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const portRaw = flagValue(args, '--port');
|
|
289
|
+
const port = portRaw === undefined ? 0 : Number(portRaw);
|
|
290
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535)
|
|
291
|
+
throw new Error('--port must be an integer from 0 to 65535.');
|
|
292
|
+
const openBrowser = !args.includes('--no-open');
|
|
293
|
+
const running = await startAppServer({ port, openBrowser });
|
|
294
|
+
console.log(`GrepRAG is open at ${running.url}`);
|
|
295
|
+
if (!openBrowser)
|
|
296
|
+
console.log(`Open this one-time link: ${running.launchUrl}`);
|
|
297
|
+
console.log('Keep this window open while using the app. It closes automatically after the browser disconnects.');
|
|
298
|
+
await new Promise(resolve => {
|
|
299
|
+
const stop = () => { void running.close().then(resolve); };
|
|
300
|
+
process.once('SIGINT', stop);
|
|
301
|
+
process.once('SIGTERM', stop);
|
|
302
|
+
const poll = setInterval(() => {
|
|
303
|
+
fetch(`${running.url}/health`).catch(() => {
|
|
304
|
+
clearInterval(poll);
|
|
305
|
+
resolve();
|
|
306
|
+
});
|
|
307
|
+
}, 2_000);
|
|
308
|
+
poll.unref();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -113,5 +113,6 @@ async function doctorCodex(args, deps) {
|
|
|
113
113
|
console.log(` PermissionRequest ctx: ${hasHook(hooks, 'PermissionRequest', 'codex-permission-context') ? 'yes' : 'no'}`);
|
|
114
114
|
console.log(` SubagentStart metadata: ${hasHook(hooks, 'SubagentStart', 'codex-subagent-start') ? 'yes' : 'no'}`);
|
|
115
115
|
console.log(` Stop store: ${hasHook(hooks, 'Stop', 'codex-store') ? 'yes' : 'no'}`);
|
|
116
|
+
console.log(` PostCompact re-announce: ${hasHook(hooks, 'PostCompact', 'recompact') ? 'yes' : 'no'}`);
|
|
116
117
|
console.log(` PostCompact session-id: ${hasHook(hooks, 'PostCompact', 'session-id') ? 'yes' : 'no'}`);
|
|
117
118
|
}
|
|
@@ -12,15 +12,15 @@ exports.buildDeliveryAnnounce = buildDeliveryAnnounce;
|
|
|
12
12
|
// adr: adr/delivery-announce-pilot.md
|
|
13
13
|
function coordinationLine(platform) {
|
|
14
14
|
if (platform === 'codex') {
|
|
15
|
-
return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo,
|
|
15
|
+
return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, and inspect their current summaries/status. Use codex_app.send_message_to_thread only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups. Delivery owner: rename this task `DEPLOY: <existing title>` for operator visibility only; transfer the prefix on handoff; remove it before reporting delivery complete or abandoned.';
|
|
16
16
|
}
|
|
17
17
|
if (platform === 'claude-code') {
|
|
18
|
-
return 'Claude Code: at the merge/deploy gate,
|
|
18
|
+
return 'Claude Code: at the merge/deploy gate, inspect peers in the same repo and send one concise delivery notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
19
19
|
}
|
|
20
20
|
if (platform === 'opencode') {
|
|
21
|
-
return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise
|
|
21
|
+
return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
22
22
|
}
|
|
23
|
-
return 'At the merge/deploy gate,
|
|
23
|
+
return 'At the merge/deploy gate, inspect peers in the same repo and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
24
24
|
}
|
|
25
25
|
function buildDeliveryAnnounce(env) {
|
|
26
26
|
if (!env.deliveryRepo)
|
|
@@ -30,7 +30,7 @@ function buildDeliveryAnnounce(env) {
|
|
|
30
30
|
`[DELIVERY — ${project}]`,
|
|
31
31
|
'Commit useful, passing work.',
|
|
32
32
|
coordinationLine(env.platform),
|
|
33
|
-
'Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately.
|
|
33
|
+
'Include visible ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Silence means nothing actionable and does not block delivery.',
|
|
34
34
|
'Use this repo\'s delivery profile and verify production.',
|
|
35
35
|
].join('\n');
|
|
36
36
|
}
|