@gamaze/hicortex 0.19.5 → 0.20.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/README.md +13 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -1
- package/dist/consolidate.d.ts +21 -0
- package/dist/consolidate.js +37 -0
- package/dist/dashboard.js +5 -2
- package/dist/identity-store.d.ts +3 -3
- package/dist/identity-store.js +2 -2
- package/dist/init.d.ts +3 -1
- package/dist/init.js +93 -1
- package/dist/llm-flight.d.ts +30 -0
- package/dist/llm-flight.js +190 -0
- package/dist/llm.d.ts +75 -0
- package/dist/llm.js +227 -13
- package/dist/mcp-server.d.ts +16 -0
- package/dist/mcp-server.js +38 -0
- package/dist/nightly.d.ts +41 -0
- package/dist/nightly.js +203 -91
- package/dist/opencode-transcript-reader.d.ts +63 -0
- package/dist/opencode-transcript-reader.js +181 -0
- package/dist/status.js +23 -0
- package/dist/telemetry.d.ts +12 -8
- package/dist/telemetry.js +5 -5
- package/dist/types.d.ts +42 -0
- package/dist/uninstall.js +34 -0
- package/opencode-plugin/hicortex/index.ts +981 -0
- package/package.json +8 -5
- package/pi-extension/hicortex/README.md +56 -0
- package/pi-extension/hicortex/index.ts +888 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* opencode transcript reader — the nightly capture path for the opencode
|
|
4
|
+
* coding agent (#347).
|
|
5
|
+
*
|
|
6
|
+
* opencode persists every session in ONE SQLite store:
|
|
7
|
+
* ~/.local/share/opencode/opencode.db
|
|
8
|
+
*
|
|
9
|
+
* Schema (relevant columns, verified live on opencode 1.18.20/1.18.23):
|
|
10
|
+
* session(id TEXT PK, directory TEXT, parent_id TEXT NULL,
|
|
11
|
+
* time_created INTEGER, time_updated INTEGER) — epoch MILLISECONDS
|
|
12
|
+
* message(id TEXT PK, session_id TEXT, time_created INTEGER,
|
|
13
|
+
* time_updated INTEGER, data TEXT) — data = {"role",…}
|
|
14
|
+
* part(id TEXT PK, message_id TEXT, time_created INTEGER, data TEXT)
|
|
15
|
+
* — data = {"type","text",…}
|
|
16
|
+
*
|
|
17
|
+
* The message row holds METADATA ONLY (role, model, cost — no text); the
|
|
18
|
+
* conversation text lives in the message's part rows as
|
|
19
|
+
* {"type":"text","text":…}. part types tool/reasoning/step-start/step-finish
|
|
20
|
+
* are plumbing, not conversation, and are excluded by the `$.type`='text'
|
|
21
|
+
* filter. A message left with no text yields no entry.
|
|
22
|
+
*
|
|
23
|
+
* Cursor: message.time_created (epoch ms) — deliberately NOT rowid. message
|
|
24
|
+
* has a TEXT primary key, so rowid is implicit, reusable after the
|
|
25
|
+
* session-delete cascade and renumbered by VACUUM — a stored rowid cursor can
|
|
26
|
+
* silently skip rows (loss, breaking the dup-over-loss invariant). Unlike the
|
|
27
|
+
* Hermes cursor column (INTEGER PRIMARY KEY AUTOINCREMENT — strictly
|
|
28
|
+
* increasing, never reused), time_created is never rewritten. The delta is
|
|
29
|
+
* EXCLUSIVE (`time_created > cursor`, ORDER BY time_created, id — the id
|
|
30
|
+
* tie-break makes the order total) so a rediscovered session with no new
|
|
31
|
+
* messages produces an empty delta and posts nothing; segment ids stay
|
|
32
|
+
* byte-stable for the server's segment-exact dedup. Residual gap: two
|
|
33
|
+
* messages written in the same millisecond with the capture boundary inside
|
|
34
|
+
* that group — bounded by that group's size, not observed on real data
|
|
35
|
+
* (smallest observed inter-message gap 13 ms).
|
|
36
|
+
*
|
|
37
|
+
* Sub-agent sessions (session.parent_id set) are skipped, mirroring the CC
|
|
38
|
+
* reader's isSidechain drop. Written defensively: parent_id was NULL for
|
|
39
|
+
* every session on the verified installs, so whether opencode populates it
|
|
40
|
+
* is unconfirmed — the skip costs nothing if the column stays empty.
|
|
41
|
+
*
|
|
42
|
+
* Marker-fenced text parts (Hicortex injection echo) are skipped so memory
|
|
43
|
+
* never re-enters itself — defense in depth: the recall channel the opencode
|
|
44
|
+
* plugin uses (experimental.chat.messages.transform) is verified NOT to
|
|
45
|
+
* persist its output, but the reader guards regardless.
|
|
46
|
+
*
|
|
47
|
+
* No-ops (returns []) when the database, a table, or a column is absent —
|
|
48
|
+
* the schema is young (migration tables present), so the reader
|
|
49
|
+
* shape-guards and never crashes the nightly run.
|
|
50
|
+
*/
|
|
51
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
52
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
53
|
+
};
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.readOpencodeSessions = readOpencodeSessions;
|
|
56
|
+
const node_fs_1 = require("node:fs");
|
|
57
|
+
const node_path_1 = require("node:path");
|
|
58
|
+
const node_os_1 = require("node:os");
|
|
59
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
60
|
+
const OPENCODE_DATA_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".local", "share", "opencode");
|
|
61
|
+
/**
|
|
62
|
+
* Fence around every block the opencode plugin injects (opencode-plugin/
|
|
63
|
+
* hicortex/index.ts CONTEXT_START/END). Duplicated here because the plugin
|
|
64
|
+
* is dependency-free and outside the package — keep the strings in sync.
|
|
65
|
+
*/
|
|
66
|
+
const FENCE_START = "<!-- hicortex-context-start -->";
|
|
67
|
+
const FENCE_END = "<!-- hicortex-context-end -->";
|
|
68
|
+
/**
|
|
69
|
+
* Read opencode sessions updated since `since` (the bulk watermark; opencode
|
|
70
|
+
* times are epoch milliseconds, so `since.getTime()` compares directly —
|
|
71
|
+
* NO *1000, unlike the Hermes reader's unix-seconds store). Returns one
|
|
72
|
+
* batch per session, parallel to readHermesSessions().
|
|
73
|
+
*
|
|
74
|
+
* @param opencodeHome opencode's data dir (default
|
|
75
|
+
* ~/.local/share/opencode); injectable for tests.
|
|
76
|
+
* @param cursors Per-session capture cursors (#189), keyed
|
|
77
|
+
* `opencode:<sessionId>`. The cursor value is the highest captured
|
|
78
|
+
* message.time_created; the delta is exclusive (`time_created > cursor`).
|
|
79
|
+
*/
|
|
80
|
+
function readOpencodeSessions(since, opencodeHome = OPENCODE_DATA_HOME, cursors = {}) {
|
|
81
|
+
const dbPath = (0, node_path_1.join)(opencodeHome, "opencode.db");
|
|
82
|
+
if (!(0, node_fs_1.existsSync)(dbPath))
|
|
83
|
+
return []; // no opencode on this machine
|
|
84
|
+
let db;
|
|
85
|
+
try {
|
|
86
|
+
db = new better_sqlite3_1.default(dbPath, { readonly: true, fileMustExist: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return []; // locked / unreadable — skip, retry next run
|
|
90
|
+
}
|
|
91
|
+
const batches = [];
|
|
92
|
+
try {
|
|
93
|
+
// Discovery: sessions touched since the watermark. parent_id IS NULL
|
|
94
|
+
// drops sub-agent sessions (the CC isSidechain equivalent).
|
|
95
|
+
const sessions = db
|
|
96
|
+
.prepare(`SELECT id, directory, parent_id FROM session
|
|
97
|
+
WHERE time_updated > ? AND parent_id IS NULL
|
|
98
|
+
ORDER BY time_created`)
|
|
99
|
+
.all(since.getTime());
|
|
100
|
+
// Delta rows for one session. EXCLUSIVE on time_created; the id
|
|
101
|
+
// tie-break makes the read order total when two messages share a
|
|
102
|
+
// millisecond. json_extract pulls the role out of the JSON data column.
|
|
103
|
+
const msgStmt = db.prepare(`SELECT id, time_created, json_extract(data, '$.role') AS role
|
|
104
|
+
FROM message
|
|
105
|
+
WHERE session_id = ? AND time_created > ?
|
|
106
|
+
ORDER BY time_created, id`);
|
|
107
|
+
// The message's text parts in part.time_created order (distinct within a
|
|
108
|
+
// message on real data, so the join order is deterministic). Non-text
|
|
109
|
+
// part types (tool/reasoning/step-start/step-finish) are excluded here.
|
|
110
|
+
const partStmt = db.prepare(`SELECT json_extract(data, '$.text') AS text
|
|
111
|
+
FROM part
|
|
112
|
+
WHERE message_id = ? AND json_extract(data, '$.type') = 'text'
|
|
113
|
+
ORDER BY time_created`);
|
|
114
|
+
// Highest time_created in the session — used only for the shrink guard.
|
|
115
|
+
const maxStmt = db.prepare("SELECT MAX(time_created) AS m FROM message WHERE session_id = ?");
|
|
116
|
+
for (const s of sessions) {
|
|
117
|
+
const cursorKey = `opencode:${s.id}`;
|
|
118
|
+
const pos = cursors[cursorKey] ?? { cursor: 0, gen: 0 };
|
|
119
|
+
let startCursor = pos.cursor;
|
|
120
|
+
let gen = pos.gen;
|
|
121
|
+
// Shrink guard (the Hermes fix-8 pattern): a stored cursor above the
|
|
122
|
+
// session's max time_created means the DB was reset/restored — re-read
|
|
123
|
+
// from 0 and bump the generation so post-reset segment ids can't
|
|
124
|
+
// collide with pre-reset ones on the server's content-blind dedup.
|
|
125
|
+
if (startCursor > 0) {
|
|
126
|
+
const max = maxStmt.get(s.id).m ?? 0;
|
|
127
|
+
if (startCursor > max) {
|
|
128
|
+
startCursor = 0;
|
|
129
|
+
gen = pos.gen + 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const rows = msgStmt.all(s.id, startCursor);
|
|
133
|
+
if (rows.length === 0)
|
|
134
|
+
continue; // empty delta — nothing to post
|
|
135
|
+
const entries = [];
|
|
136
|
+
const entryCursors = [];
|
|
137
|
+
for (const r of rows) {
|
|
138
|
+
const parts = partStmt.all(r.id);
|
|
139
|
+
const content = parts
|
|
140
|
+
.map((p) => (typeof p.text === "string" ? p.text : ""))
|
|
141
|
+
// Fenced parts are Hicortex injection echo, not conversation.
|
|
142
|
+
.filter((t) => t !== "" && !t.includes(FENCE_START) && !t.includes(FENCE_END))
|
|
143
|
+
.join("\n");
|
|
144
|
+
// A message with no surviving text (tool-only / fenced-only) yields
|
|
145
|
+
// no entry. It also stays unconsumed (the cursor advances only to the
|
|
146
|
+
// last ENTRY), so it is re-scanned next run — dup-over-loss, and the
|
|
147
|
+
// entryless tail costs one no-op query until a text message lands.
|
|
148
|
+
if (content.trim() === "")
|
|
149
|
+
continue;
|
|
150
|
+
// A NULL role passes through as "" — extractConversationText renders
|
|
151
|
+
// anything not "user" as ASSISTANT, so no turn is fabricated.
|
|
152
|
+
entries.push({ role: r.role ?? "", content });
|
|
153
|
+
entryCursors.push(r.time_created); // one end-cursor per entry
|
|
154
|
+
}
|
|
155
|
+
if (entries.length === 0)
|
|
156
|
+
continue; // entryless delta posts nothing
|
|
157
|
+
// date = the delta's LAST message time — already milliseconds, so
|
|
158
|
+
// unlike the Hermes reader there is no *1000 (copying that line would
|
|
159
|
+
// put the date ~57,000 years out).
|
|
160
|
+
batches.push({
|
|
161
|
+
sessionId: s.id,
|
|
162
|
+
projectName: (0, node_path_1.basename)(s.directory ?? "") || "opencode",
|
|
163
|
+
sourceAgent: "opencode",
|
|
164
|
+
date: new Date(rows[rows.length - 1].time_created).toISOString().slice(0, 10),
|
|
165
|
+
entries,
|
|
166
|
+
cursorKey,
|
|
167
|
+
startCursor,
|
|
168
|
+
generation: gen,
|
|
169
|
+
entryCursors,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Query failed (schema drift on an opencode upgrade) — no-op, don't crash the run.
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
db.close();
|
|
179
|
+
}
|
|
180
|
+
return batches;
|
|
181
|
+
}
|
package/dist/status.js
CHANGED
|
@@ -132,6 +132,29 @@ async function runStatus() {
|
|
|
132
132
|
}
|
|
133
133
|
catch { /* no CC settings */ }
|
|
134
134
|
console.log(` CC MCP: ${ccRegistered ? `registered → ${ccUrl}` : "not registered"}`);
|
|
135
|
+
// Pi (#348): the bundled extension at ~/.pi/agent/extensions/hicortex.ts.
|
|
136
|
+
// "not found" = no Pi on this machine; "not installed" = Pi present, run
|
|
137
|
+
// init to place the extension.
|
|
138
|
+
const piAgentDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "agent");
|
|
139
|
+
if ((0, node_fs_1.existsSync)(piAgentDir)) {
|
|
140
|
+
const piInstalled = (0, node_fs_1.existsSync)((0, node_path_1.join)(piAgentDir, "extensions", "hicortex.ts"));
|
|
141
|
+
console.log(` Pi plugin: ${piInstalled ? "installed" : "not installed (run: npx @gamaze/hicortex init)"}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(" Pi plugin: not found");
|
|
145
|
+
}
|
|
146
|
+
// opencode (#347): the bundled plugin at ~/.config/opencode/plugins/hicortex.ts.
|
|
147
|
+
// "not found" = no opencode on this machine; "not installed" = opencode
|
|
148
|
+
// present, run init to place the plugin.
|
|
149
|
+
const opencodeConfigDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "opencode");
|
|
150
|
+
const opencodeDataDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".local", "share", "opencode");
|
|
151
|
+
if ((0, node_fs_1.existsSync)(opencodeConfigDir) || (0, node_fs_1.existsSync)(opencodeDataDir)) {
|
|
152
|
+
const openCodeInstalled = (0, node_fs_1.existsSync)((0, node_path_1.join)(opencodeConfigDir, "plugins", "hicortex.ts"));
|
|
153
|
+
console.log(` opencode: ${openCodeInstalled ? "installed" : "not installed (run: npx @gamaze/hicortex init)"}`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(" opencode: not found");
|
|
157
|
+
}
|
|
135
158
|
console.log();
|
|
136
159
|
// Server status. /health/detail carries the diagnostics (version, memories,
|
|
137
160
|
// llm) — /health itself is the public minimal {status:"ok"} probe (#253).
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* v — package version
|
|
7
7
|
* pv — payload schema version
|
|
8
8
|
* mode — server or client
|
|
9
|
-
* agent — cc, pi, oc, or mixed (detected from session
|
|
10
|
-
* when the agent type is genuinely unknown
|
|
11
|
-
* transcripts read yet). The admin summary
|
|
12
|
-
* as "?", distinct from any real type, so
|
|
13
|
-
* client is never miscounted as "cc".
|
|
9
|
+
* agent — cc, hermes, pi, oc, opencode, or mixed (detected from session
|
|
10
|
+
* sources); OMITTED when the agent type is genuinely unknown
|
|
11
|
+
* (pre-flight abort — no transcripts read yet). The admin summary
|
|
12
|
+
* buckets a missing agent as "?", distinct from any real type, so
|
|
13
|
+
* an aborting Hermes/OC client is never miscounted as "cc".
|
|
14
14
|
* mem — total memory count
|
|
15
15
|
* lessons — total lesson count
|
|
16
16
|
* lessonsGenerated — lessons created THIS run (server mode only; the per-run
|
|
@@ -89,15 +89,19 @@ export interface TelemetryPayload {
|
|
|
89
89
|
* Consolidation outcome for THIS full nightly (server mode only —
|
|
90
90
|
* capture-only runs send no nightly ping, so the field is absent there).
|
|
91
91
|
* `runConsolidation`'s status: "completed" | "skipped" | "failed", plus
|
|
92
|
-
* "no_llm" when consolidation was skipped because no LLM was configured,
|
|
92
|
+
* "no_llm" when consolidation was skipped because no LLM was configured,
|
|
93
93
|
* "throttled" (#246) when the run was skipped because the
|
|
94
|
-
* `llmTokensPerMonth` fair-use cap was projected to be exceeded
|
|
94
|
+
* `llmTokensPerMonth` fair-use cap was projected to be exceeded, and
|
|
95
|
+
* "endpoint_down" (#337) when the pre-consolidation readiness probe failed
|
|
96
|
+
* or the LLM circuit breaker was open after the run — a TRANSIENT state
|
|
97
|
+
* (retried next run), never reported as "completed" even though the stages
|
|
98
|
+
* fail soft.
|
|
95
99
|
* "skipped" = the built-in nothing-to-do short-circuit (no new + no unscored
|
|
96
100
|
* memories → zero LLM calls), NOT a failure. Lets the fleet aggregate tell a
|
|
97
101
|
* real consolidation run from a no-op without repurposing `ok` (which is the
|
|
98
102
|
* capture-health signal). 0.17+.
|
|
99
103
|
*/
|
|
100
|
-
consolidation?: "completed" | "skipped" | "failed" | "no_llm" | "throttled";
|
|
104
|
+
consolidation?: "completed" | "skipped" | "failed" | "no_llm" | "throttled" | "endpoint_down";
|
|
101
105
|
/**
|
|
102
106
|
* Total LLM tokens consumed by THIS nightly's consolidation (#246) — the
|
|
103
107
|
* BudgetTracker total. Server-mode only (capture-only + client runs make no
|
package/dist/telemetry.js
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* v — package version
|
|
8
8
|
* pv — payload schema version
|
|
9
9
|
* mode — server or client
|
|
10
|
-
* agent — cc, pi, oc, or mixed (detected from session
|
|
11
|
-
* when the agent type is genuinely unknown
|
|
12
|
-
* transcripts read yet). The admin summary
|
|
13
|
-
* as "?", distinct from any real type, so
|
|
14
|
-
* client is never miscounted as "cc".
|
|
10
|
+
* agent — cc, hermes, pi, oc, opencode, or mixed (detected from session
|
|
11
|
+
* sources); OMITTED when the agent type is genuinely unknown
|
|
12
|
+
* (pre-flight abort — no transcripts read yet). The admin summary
|
|
13
|
+
* buckets a missing agent as "?", distinct from any real type, so
|
|
14
|
+
* an aborting Hermes/OC client is never miscounted as "cc".
|
|
15
15
|
* mem — total memory count
|
|
16
16
|
* lessons — total lesson count
|
|
17
17
|
* lessonsGenerated — lessons created THIS run (server mode only; the per-run
|
package/dist/types.d.ts
CHANGED
|
@@ -392,6 +392,48 @@ export interface HicortexConfig {
|
|
|
392
392
|
* doubled for margin). Only relevant when `ollamaFlushEvery` > 0.
|
|
393
393
|
*/
|
|
394
394
|
ollamaFlushWaitMs?: number;
|
|
395
|
+
/**
|
|
396
|
+
* ONE per-attempt timeout ceiling (ms) for every LLM phase — distill,
|
|
397
|
+
* reflect, classify, and scoring alike (#337). Default 900000 (15 min). The
|
|
398
|
+
* openai-compat and anthropic requests fetch through an undici dispatcher
|
|
399
|
+
* with undici's hidden 5-minute header/body timers disabled, so this knob is
|
|
400
|
+
* the ONLY ceiling: a legitimate long generation is no longer abandoned
|
|
401
|
+
* client-side at 5 min while the server keeps generating for the dead
|
|
402
|
+
* client (the 2026-08-23/24 incident's amplification mechanism). Before
|
|
403
|
+
* #337, scoring used a 600 s ceiling and the other phases 900 s; one knob
|
|
404
|
+
* now covers all four. No effect on the ollama path (already streams) or
|
|
405
|
+
* claude-cli (subprocess timeout).
|
|
406
|
+
*/
|
|
407
|
+
llmTimeoutMs?: number;
|
|
408
|
+
/**
|
|
409
|
+
* Consecutive ladder-exhausted TOTAL failures (fetch-failed / ECONNREFUSED /
|
|
410
|
+
* timeout / "Headers Timeout" class) after which the per-endpoint circuit
|
|
411
|
+
* breaker opens (#337). Default 3; `0` disables. While open, calls throw
|
|
412
|
+
* `LlmCircuitOpenError` immediately with NO network I/O. HTTP error statuses
|
|
413
|
+
* with a response, parse errors, and rate limits never count (they throw
|
|
414
|
+
* before the retry ladder can be exhausted). Any success resets the counter.
|
|
415
|
+
*/
|
|
416
|
+
llmBreakerThreshold?: number;
|
|
417
|
+
/**
|
|
418
|
+
* How long (ms) an open circuit breaker stays open before the next call
|
|
419
|
+
* becomes a half-open trial (#337). Default 600000 (10 min). A trial failure
|
|
420
|
+
* re-opens the breaker; a trial success resets it.
|
|
421
|
+
*/
|
|
422
|
+
llmBreakerCooldownMs?: number;
|
|
423
|
+
/**
|
|
424
|
+
* Timeout (ms) for the readiness probe's single 1-token generation attempt
|
|
425
|
+
* (#337). Default 60000. The probe asks "can this endpoint GENERATE", which
|
|
426
|
+
* /health-style liveness checks cannot answer (a wedged gateway keeps
|
|
427
|
+
* answering /v1/models). Read by the nightly before consolidation and by the
|
|
428
|
+
* daemon before distilling.
|
|
429
|
+
*/
|
|
430
|
+
llmProbeTimeoutMs?: number;
|
|
431
|
+
/**
|
|
432
|
+
* How long (ms) the daemon caches a /distill probe outcome before probing
|
|
433
|
+
* again (#337). Default 300000 — a healthy capture cadence pays at most one
|
|
434
|
+
* probe per window. Nightly runs are single-shot and never cache.
|
|
435
|
+
*/
|
|
436
|
+
llmProbeTtlMs?: number;
|
|
395
437
|
/**
|
|
396
438
|
* Max lessons injected into an agent's session-start context (default 10).
|
|
397
439
|
* Lessons are ranked per-session by project/domain affinity + recency +
|
package/dist/uninstall.js
CHANGED
|
@@ -234,6 +234,40 @@ async function runUninstall() {
|
|
|
234
234
|
if ((0, claude_md_js_1.removeLessonsBlock)(CLAUDE_MD)) {
|
|
235
235
|
console.log(" ✓ Removed Hicortex Learnings block from CLAUDE.md");
|
|
236
236
|
}
|
|
237
|
+
// 6. Remove the Pi extension (#348). Guarded on the "hicortex" marker the
|
|
238
|
+
// installer always ships — "hicortex.ts" is a plausible user filename,
|
|
239
|
+
// and uninstall never deletes a file we did not write. Fail-soft when
|
|
240
|
+
// absent (no Pi on the machine) or unreadable.
|
|
241
|
+
const piExtension = (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "agent", "extensions", "hicortex.ts");
|
|
242
|
+
if ((0, node_fs_1.existsSync)(piExtension)) {
|
|
243
|
+
try {
|
|
244
|
+
if ((0, node_fs_1.readFileSync)(piExtension, "utf-8").toLowerCase().includes("hicortex")) {
|
|
245
|
+
(0, node_fs_1.unlinkSync)(piExtension);
|
|
246
|
+
console.log(" ✓ Removed Pi extension (~/.pi/agent/extensions/hicortex.ts)");
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log(" ⚠ Skipping ~/.pi/agent/extensions/hicortex.ts — not a Hicortex file, left untouched");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch { /* unreadable — leave it */ }
|
|
253
|
+
}
|
|
254
|
+
// 7. Remove the opencode plugin (#347). Same marker guard — the plugins
|
|
255
|
+
// directory is shared (third-party files live there too), and uninstall
|
|
256
|
+
// never deletes a file we did not write. Fail-soft when absent (no
|
|
257
|
+
// opencode on the machine) or unreadable.
|
|
258
|
+
const openCodePlugin = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "opencode", "plugins", "hicortex.ts");
|
|
259
|
+
if ((0, node_fs_1.existsSync)(openCodePlugin)) {
|
|
260
|
+
try {
|
|
261
|
+
if ((0, node_fs_1.readFileSync)(openCodePlugin, "utf-8").toLowerCase().includes("hicortex")) {
|
|
262
|
+
(0, node_fs_1.unlinkSync)(openCodePlugin);
|
|
263
|
+
console.log(" ✓ Removed opencode plugin (~/.config/opencode/plugins/hicortex.ts)");
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(" ⚠ Skipping ~/.config/opencode/plugins/hicortex.ts — not a Hicortex file, left untouched");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch { /* unreadable — leave it */ }
|
|
270
|
+
}
|
|
237
271
|
console.log(`\n✓ Uninstalled. Database preserved at ${HICORTEX_HOME}/hicortex.db`);
|
|
238
272
|
console.log(" To remove all data: rm -rf ~/.hicortex");
|
|
239
273
|
}
|