om-memory-system 3.5.0 → 3.6.0-next.38
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 +24 -16
- package/dist/adapters/claude-code/hook-client.d.ts +73 -0
- package/dist/adapters/claude-code/hook-client.js +285 -0
- package/dist/adapters/claude-code/hook-command.d.ts +6 -0
- package/dist/adapters/claude-code/hook-command.js +25 -0
- package/dist/cli/index.js +19 -3
- package/dist/cli/memory-command.d.ts +24 -0
- package/dist/cli/memory-command.js +180 -0
- package/dist/core/retrieval.d.ts +21 -0
- package/dist/core/retrieval.js +35 -0
- package/dist/importer/backfill-controls.d.ts +10 -0
- package/dist/importer/backfill-controls.js +40 -11
- package/dist/importer/backfill-model.d.ts +1 -1
- package/dist/importer/backfill-model.js +3 -0
- package/dist/importer/claude-conversation.d.ts +74 -0
- package/dist/importer/claude-conversation.js +216 -0
- package/dist/importer/claude-hook-api.d.ts +50 -0
- package/dist/importer/claude-hook-api.js +441 -0
- package/dist/importer/claude-import.d.ts +15 -0
- package/dist/importer/claude-import.js +62 -0
- package/dist/importer/claude-reader.d.ts +99 -0
- package/dist/importer/claude-reader.js +292 -0
- package/dist/importer/external-backfill-models.js +2 -2
- package/dist/importer/import-args.d.ts +1 -1
- package/dist/importer/import-args.js +11 -4
- package/dist/importer/import-readiness.d.ts +13 -1
- package/dist/importer/import-readiness.js +19 -0
- package/dist/importer/import-runs.js +1 -1
- package/dist/importer/import-sessions.d.ts +4 -1
- package/dist/importer/import-sessions.js +14 -2
- package/dist/importer/import-sources.d.ts +8 -2
- package/dist/importer/import-sources.js +21 -4
- package/dist/importer/map-suggestions.d.ts +1 -0
- package/dist/importer/map-suggestions.js +6 -1
- package/dist/importer/run-import.js +14 -10
- package/dist/importer/web-import-jobs.js +10 -3
- package/dist/index.js +9 -23
- package/dist/services/ai/live-model-choice.d.ts +5 -0
- package/dist/services/ai/live-model-choice.js +7 -0
- package/dist/services/backfill-state.d.ts +1 -1
- package/dist/services/capture-retry-queue.js +3 -2
- package/dist/services/claude-capture-cursor.d.ts +10 -0
- package/dist/services/claude-capture-cursor.js +44 -0
- package/dist/services/settings-snapshot.d.ts +1 -0
- package/dist/services/settings-snapshot.js +3 -1
- package/dist/services/web-server.d.ts +2 -0
- package/dist/services/web-server.js +61 -28
- package/dist/types/host-label.d.ts +3 -0
- package/dist/types/host-label.js +9 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/web/assets/index-BFiVixOK.js +124 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-DIy--9g_.js +0 -124
|
@@ -33,8 +33,9 @@ export function validateWebImportRequest(value) {
|
|
|
33
33
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
34
34
|
throw new Error("Invalid import request");
|
|
35
35
|
const input = value;
|
|
36
|
-
if (input.host !== "pi" && input.host !== "opencode")
|
|
37
|
-
throw new Error("Choose Pi or
|
|
36
|
+
if (input.host !== "pi" && input.host !== "opencode" && input.host !== "claude-code") {
|
|
37
|
+
throw new Error("Choose Pi, OpenCode, or Claude Code");
|
|
38
|
+
}
|
|
38
39
|
if (!input.options || typeof input.options !== "object" || Array.isArray(input.options))
|
|
39
40
|
throw new Error("Invalid import options");
|
|
40
41
|
const options = input.options;
|
|
@@ -72,12 +73,18 @@ export function validateWebImportRequest(value) {
|
|
|
72
73
|
(typeof input.modelChoice !== "string" ||
|
|
73
74
|
(input.modelChoice !== "external" && !/^[^/]+\/.+$/.test(input.modelChoice))))
|
|
74
75
|
throw new Error("Choose a signed-in provider/model or the external API");
|
|
76
|
+
// Claude Code has no session model: its imports always use the external API.
|
|
77
|
+
if (input.host === "claude-code" &&
|
|
78
|
+
input.modelChoice !== undefined &&
|
|
79
|
+
input.modelChoice !== "external")
|
|
80
|
+
throw new Error("Claude Code imports use the external API");
|
|
81
|
+
const modelChoice = input.host === "claude-code" ? "external" : input.modelChoice;
|
|
75
82
|
return {
|
|
76
83
|
host: input.host,
|
|
77
84
|
source: input.source,
|
|
78
85
|
selection: validateImportSelection(input.selection),
|
|
79
86
|
options: options,
|
|
80
|
-
modelChoice
|
|
87
|
+
modelChoice,
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
/** One in-memory slot per web server, shared by previews and imports. */
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
2
|
import { memoryClient } from "./services/client.js";
|
|
3
|
-
import { formatContextForPrompt } from "./services/context.js";
|
|
4
3
|
import { getTags } from "./services/tags.js";
|
|
5
4
|
import { performAutoCapture } from "./services/auto-capture.js";
|
|
6
5
|
import { createOpenCodeAutoCaptureHost } from "./adapters/opencode/auto-capture-host.js";
|
|
7
6
|
import { isInternalPrompt, isStructuredSummaryPromptMessage, recordUserPrompt, } from "./adapters/opencode/user-prompt.js";
|
|
8
7
|
import { executeMemoryOperation } from "./core/memory-operations.js";
|
|
9
8
|
import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, latestUserMessageModel, runOpencodeImportCommand, } from "./adapters/opencode/import-command.js";
|
|
10
|
-
import { formatMemoriesForCompaction } from "./core/retrieval.js";
|
|
9
|
+
import { buildRecentMemoriesSection, formatMemoriesForCompaction } from "./core/retrieval.js";
|
|
11
10
|
import { performUserProfileLearning } from "./adapters/opencode/profile-learning.js";
|
|
12
11
|
import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
|
|
13
12
|
import { startWebServer, WebServer } from "./services/web-server.js";
|
|
@@ -489,27 +488,14 @@ export const OmmsPlugin = async (ctx) => {
|
|
|
489
488
|
!m.parts.every((p) => p.type !== "text" || p.synthetic === true)).length === 1);
|
|
490
489
|
if (!shouldInject)
|
|
491
490
|
return;
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
if (memories.length === 0)
|
|
502
|
-
return;
|
|
503
|
-
const projectMemories = {
|
|
504
|
-
results: memories.map((m) => ({
|
|
505
|
-
similarity: 1.0,
|
|
506
|
-
memory: m.summary,
|
|
507
|
-
})),
|
|
508
|
-
total: memories.length,
|
|
509
|
-
timing: 0,
|
|
510
|
-
};
|
|
511
|
-
const userId = tags.user.userEmail || null;
|
|
512
|
-
const memoryContext = await formatContextForPrompt(userId, projectMemories);
|
|
491
|
+
const memoryContext = await buildRecentMemoriesSection({
|
|
492
|
+
projectTag: tags.project.tag,
|
|
493
|
+
userEmail: tags.user.userEmail,
|
|
494
|
+
sessionId: input.sessionID,
|
|
495
|
+
maxMemories: CONFIG.chatMessage.maxMemories,
|
|
496
|
+
excludeCurrentSession: CONFIG.chatMessage.excludeCurrentSession,
|
|
497
|
+
maxAgeDays: CONFIG.chatMessage.maxAgeDays,
|
|
498
|
+
});
|
|
513
499
|
if (memoryContext) {
|
|
514
500
|
const contextPart = {
|
|
515
501
|
id: `prt-memory-context-${Date.now()}`,
|
|
@@ -49,6 +49,11 @@ export declare function resolvePiLiveModel(config: AutoCaptureProviderRuntimeCon
|
|
|
49
49
|
piProvider?: string;
|
|
50
50
|
piModel?: string;
|
|
51
51
|
}): PiLiveModelChoice;
|
|
52
|
+
/**
|
|
53
|
+
* Claude Code's live model: the external API only. Its hooks cannot call the
|
|
54
|
+
* session's model, so there is no host model and no session fallback.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveClaudeCodeLiveModel(config: AutoCaptureProviderRuntimeConfig): AutoCaptureProviderStatus;
|
|
52
57
|
/** True when the external API is fully configured and can serve as a fallback. */
|
|
53
58
|
export declare function isExternalModelReady(config: AutoCaptureProviderRuntimeConfig): boolean;
|
|
54
59
|
/** Why the external API cannot be used as a host's chosen model, or an empty list. */
|
|
@@ -93,6 +93,13 @@ export function resolvePiLiveModel(config) {
|
|
|
93
93
|
return { kind: "session" };
|
|
94
94
|
return { kind: "unready", issues: manual.issues };
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Claude Code's live model: the external API only. Its hooks cannot call the
|
|
98
|
+
* session's model, so there is no host model and no session fallback.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveClaudeCodeLiveModel(config) {
|
|
101
|
+
return externalChoiceStatus(config);
|
|
102
|
+
}
|
|
96
103
|
/** True when the external API is fully configured and can serve as a fallback. */
|
|
97
104
|
export function isExternalModelReady(config) {
|
|
98
105
|
return getManualProviderStatus(config).ready;
|
|
@@ -179,11 +179,12 @@ export async function deleteCaptureRetry(id, config) {
|
|
|
179
179
|
export async function countCaptureRetries(config) {
|
|
180
180
|
const db = await database(config);
|
|
181
181
|
const rows = await db.all("SELECT host, COUNT(*) AS count FROM capture_retry_queue GROUP BY host");
|
|
182
|
-
const counts = { opencode: 0, pi: 0 };
|
|
182
|
+
const counts = { opencode: 0, pi: 0, "claude-code": 0 };
|
|
183
183
|
for (const row of rows) {
|
|
184
184
|
const host = String(row.host);
|
|
185
|
-
if (host === "opencode" || host === "pi")
|
|
185
|
+
if (host === "opencode" || host === "pi" || host === "claude-code") {
|
|
186
186
|
counts[host] = Number(row.count);
|
|
187
|
+
}
|
|
187
188
|
}
|
|
188
189
|
return counts;
|
|
189
190
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last Claude Code user entry that live capture handled, per session. It
|
|
3
|
+
* lives in `user-prompts.db` next to the capture retry queue, so a web app
|
|
4
|
+
* restart does not capture the last turn again. Callers pass `CONFIG`.
|
|
5
|
+
*/
|
|
6
|
+
export interface ClaudeCaptureCursorConfig {
|
|
7
|
+
storagePath: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function readClaudeCaptureCursor(sessionId: string, config: ClaudeCaptureCursorConfig): Promise<string | null>;
|
|
10
|
+
export declare function writeClaudeCaptureCursor(sessionId: string, userEntryId: string, config: ClaudeCaptureCursorConfig, now?: number): Promise<void>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
3
|
+
/** A session with no capture for this long loses its cursor. */
|
|
4
|
+
const CURSOR_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
5
|
+
const prepared = new Map();
|
|
6
|
+
async function database(config) {
|
|
7
|
+
const path = join(config.storagePath, "user-prompts.db");
|
|
8
|
+
const db = await tursoConnectionManager.getConnection(path);
|
|
9
|
+
let ready = prepared.get(path);
|
|
10
|
+
if (!ready) {
|
|
11
|
+
ready = db
|
|
12
|
+
.batch([
|
|
13
|
+
{
|
|
14
|
+
sql: `CREATE TABLE IF NOT EXISTS claude_capture_cursors (
|
|
15
|
+
session_id TEXT PRIMARY KEY,
|
|
16
|
+
last_user_entry_id TEXT NOT NULL,
|
|
17
|
+
updated_at INTEGER NOT NULL
|
|
18
|
+
)`,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
sql: "DELETE FROM claude_capture_cursors WHERE updated_at < ?",
|
|
22
|
+
args: [Date.now() - CURSOR_RETENTION_MS],
|
|
23
|
+
},
|
|
24
|
+
])
|
|
25
|
+
.then(() => undefined);
|
|
26
|
+
// A failed preparation is tried again on the next call.
|
|
27
|
+
ready.catch(() => prepared.delete(path));
|
|
28
|
+
prepared.set(path, ready);
|
|
29
|
+
}
|
|
30
|
+
await ready;
|
|
31
|
+
return db;
|
|
32
|
+
}
|
|
33
|
+
export async function readClaudeCaptureCursor(sessionId, config) {
|
|
34
|
+
const db = await database(config);
|
|
35
|
+
const row = await db.get("SELECT last_user_entry_id FROM claude_capture_cursors WHERE session_id = ?", [sessionId]);
|
|
36
|
+
return row ? String(row.last_user_entry_id) : null;
|
|
37
|
+
}
|
|
38
|
+
export async function writeClaudeCaptureCursor(sessionId, userEntryId, config, now = Date.now()) {
|
|
39
|
+
const db = await database(config);
|
|
40
|
+
await db.run(`INSERT INTO claude_capture_cursors (session_id, last_user_entry_id, updated_at)
|
|
41
|
+
VALUES (?, ?, ?)
|
|
42
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
43
|
+
last_user_entry_id = excluded.last_user_entry_id, updated_at = excluded.updated_at`, [sessionId, userEntryId, now]);
|
|
44
|
+
}
|
|
@@ -27,6 +27,7 @@ export declare function getSettingsSnapshot(directory: string): {
|
|
|
27
27
|
effective: {
|
|
28
28
|
opencode: import("./ai/live-model-choice.js").AutoCaptureProviderStatus;
|
|
29
29
|
pi: import("./ai/live-model-choice.js").PiLiveModelChoice;
|
|
30
|
+
"claude-code": import("./ai/live-model-choice.js").AutoCaptureProviderStatus;
|
|
30
31
|
};
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { parse } from "jsonc-parser";
|
|
4
4
|
import { CONFIG, getGlobalConfigSourcePath } from "../config.js";
|
|
5
5
|
import { readGlobalConfigRevision } from "./global-config-writer.js";
|
|
6
|
-
import { getAutoCaptureProviderStatus, isExternalModelReady, resolvePiLiveModel, } from "./ai/live-model-choice.js";
|
|
6
|
+
import { getAutoCaptureProviderStatus, isExternalModelReady, resolveClaudeCodeLiveModel, resolvePiLiveModel, } from "./ai/live-model-choice.js";
|
|
7
7
|
const editable = [
|
|
8
8
|
"opencodeProvider",
|
|
9
9
|
"opencodeModel",
|
|
@@ -92,6 +92,8 @@ export function getSettingsSnapshot(directory) {
|
|
|
92
92
|
effective: {
|
|
93
93
|
opencode: getAutoCaptureProviderStatus(CONFIG),
|
|
94
94
|
pi: resolvePiLiveModel(CONFIG),
|
|
95
|
+
// Claude Code capture uses the external API only; `issues` names each missing setting.
|
|
96
|
+
"claude-code": resolveClaudeCodeLiveModel(CONFIG),
|
|
95
97
|
},
|
|
96
98
|
};
|
|
97
99
|
}
|
|
@@ -39,6 +39,8 @@ export declare class WebServer {
|
|
|
39
39
|
private backfillControlsInstance?;
|
|
40
40
|
/** Run an import-page action; errors carry their own status and never file contents. */
|
|
41
41
|
private importResponse;
|
|
42
|
+
/** Claude Code hook requests; they reach this point only with a valid API token. */
|
|
43
|
+
private claudeHookResponse;
|
|
42
44
|
private backfillControls;
|
|
43
45
|
private importJobs;
|
|
44
46
|
constructor(config: WebServerConfig);
|
|
@@ -36,7 +36,7 @@ function serveFetch(opts) {
|
|
|
36
36
|
hostname: opts.hostname,
|
|
37
37
|
fetch: opts.fetch,
|
|
38
38
|
});
|
|
39
|
-
return { stop: () => bunHandle.stop() };
|
|
39
|
+
return Promise.resolve({ stop: () => bunHandle.stop() });
|
|
40
40
|
}
|
|
41
41
|
// Node path: wrap node:http around the fetch-style handler. The adapter
|
|
42
42
|
// converts IncomingMessage → Web Request and Web Response → ServerResponse.
|
|
@@ -95,30 +95,34 @@ function serveFetch(opts) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
|
-
// Surface EADDRINUSE synchronously so callers can detect the
|
|
99
|
-
// already-running-instance case the same way they do under Bun.
|
|
100
|
-
let listenError;
|
|
101
|
-
server.on("error", (err) => {
|
|
102
|
-
if (err.code === "EADDRINUSE") {
|
|
103
|
-
listenError = err;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
// exclusive: false disables SO_EXCLUSIVEADDRUSE on Windows, allowing
|
|
107
|
-
// rebind after a crashed predecessor left orphaned sockets behind.
|
|
108
|
-
server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true, exclusive: false });
|
|
109
98
|
server.unref();
|
|
110
99
|
server.timeout = NODE_HTTP_IDLE_TIMEOUT_MS;
|
|
111
100
|
server.keepAliveTimeout = 10000;
|
|
112
101
|
server.headersTimeout = 11000;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
// Node reports EADDRINUSE after listen() returns, so wait for the outcome.
|
|
103
|
+
// Callers then see a busy port the same way they do under Bun.
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const onError = (error) => {
|
|
106
|
+
server.off("listening", onListening);
|
|
107
|
+
reject(error);
|
|
108
|
+
};
|
|
109
|
+
const onListening = () => {
|
|
110
|
+
server.off("error", onError);
|
|
111
|
+
// Keep a listener, so a later server error is logged instead of crashing the process.
|
|
112
|
+
server.on("error", (error) => log("Web server error", { error: String(error) }));
|
|
113
|
+
resolve({
|
|
114
|
+
stop: () => {
|
|
115
|
+
server.closeAllConnections();
|
|
116
|
+
server.close();
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
server.once("error", onError);
|
|
121
|
+
server.once("listening", onListening);
|
|
122
|
+
// exclusive: false disables SO_EXCLUSIVEADDRUSE on Windows, allowing
|
|
123
|
+
// rebind after a crashed predecessor left orphaned sockets behind.
|
|
124
|
+
server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true, exclusive: false });
|
|
125
|
+
});
|
|
122
126
|
}
|
|
123
127
|
const __filename = fileURLToPath(import.meta.url);
|
|
124
128
|
const __dirname = dirname(__filename);
|
|
@@ -157,6 +161,26 @@ export class WebServer {
|
|
|
157
161
|
return this.jsonResponse({ error: error instanceof Error ? error.message : "Import request failed" }, typeof code === "number" ? code : 400);
|
|
158
162
|
}
|
|
159
163
|
}
|
|
164
|
+
/** Claude Code hook requests; they reach this point only with a valid API token. */
|
|
165
|
+
async claudeHookResponse(req, path) {
|
|
166
|
+
const api = await import("../importer/claude-hook-api.js");
|
|
167
|
+
const body = await req.json().catch(() => null);
|
|
168
|
+
try {
|
|
169
|
+
if (path === "/api/claude/capture") {
|
|
170
|
+
return this.jsonResponse(api.handleClaudeCapture(body), 202);
|
|
171
|
+
}
|
|
172
|
+
const cwd = this.config.directory ?? process.cwd();
|
|
173
|
+
return this.jsonResponse(await api.handleClaudeRetrieve(body, {
|
|
174
|
+
startBackfill: async () => (await this.backfillControls()).startAuto("claude-code", cwd),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof api.ClaudeHookRequestError) {
|
|
179
|
+
return this.jsonResponse({ error: error.message }, error.status);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
160
184
|
async backfillControls() {
|
|
161
185
|
const { BackfillControls } = await import("../importer/web-import-api.js");
|
|
162
186
|
return (this.backfillControlsInstance ??= new BackfillControls());
|
|
@@ -192,12 +216,16 @@ export class WebServer {
|
|
|
192
216
|
.catch(() => { });
|
|
193
217
|
assertWebServerNetworkAuth(this.config.host, this.config.apiToken, this.config.auth?.isEnabled() ?? false);
|
|
194
218
|
try {
|
|
195
|
-
this.server = serveFetch({
|
|
219
|
+
this.server = await serveFetch({
|
|
196
220
|
port: this.config.port,
|
|
197
221
|
hostname: this.config.host,
|
|
198
222
|
fetch: this.handleRequest.bind(this),
|
|
199
223
|
});
|
|
200
224
|
this.isOwner = true;
|
|
225
|
+
// This process serves the Claude Code hooks, so it also retries their failed captures.
|
|
226
|
+
void import("../importer/claude-hook-api.js")
|
|
227
|
+
.then(({ startClaudeCodeWorker }) => startClaudeCodeWorker())
|
|
228
|
+
.catch(() => { });
|
|
201
229
|
}
|
|
202
230
|
catch (error) {
|
|
203
231
|
const errorMsg = String(error);
|
|
@@ -426,12 +454,13 @@ export class WebServer {
|
|
|
426
454
|
return this.jsonResponse({
|
|
427
455
|
pi: await readBackfillStatus("pi"),
|
|
428
456
|
opencode: await readBackfillStatus("opencode"),
|
|
457
|
+
"claude-code": await readBackfillStatus("claude-code"),
|
|
429
458
|
});
|
|
430
459
|
}
|
|
431
460
|
if (path === "/api/settings/backfill/runs" && method === "GET") {
|
|
432
461
|
return this.jsonResponse(await (await this.backfillControls()).status());
|
|
433
462
|
}
|
|
434
|
-
const backfillAction = /^\/api\/settings\/backfill\/(pi|opencode)\/(run|pause|resume)$/.exec(path);
|
|
463
|
+
const backfillAction = /^\/api\/settings\/backfill\/(pi|opencode|claude-code)\/(run|pause|resume)$/.exec(path);
|
|
435
464
|
if (backfillAction && method === "POST") {
|
|
436
465
|
const host = backfillAction[1];
|
|
437
466
|
const controls = await this.backfillControls();
|
|
@@ -569,7 +598,7 @@ export class WebServer {
|
|
|
569
598
|
retryQueue: await countCaptureRetries(CONFIG),
|
|
570
599
|
});
|
|
571
600
|
}
|
|
572
|
-
const retryNow = /^\/api\/settings\/capture-retry\/(pi|opencode)\/run$/.exec(path);
|
|
601
|
+
const retryNow = /^\/api\/settings\/capture-retry\/(pi|opencode|claude-code)\/run$/.exec(path);
|
|
573
602
|
if (retryNow && method === "POST") {
|
|
574
603
|
const [{ CONFIG }, { requestCaptureRetryNow }] = await Promise.all([
|
|
575
604
|
import("../config.js"),
|
|
@@ -620,16 +649,16 @@ export class WebServer {
|
|
|
620
649
|
return this.jsonResponse({ error: "Browsing is available only on a loopback bind; enter a path instead" }, 403);
|
|
621
650
|
}
|
|
622
651
|
const body = (await req.json());
|
|
623
|
-
if (body?.host !== "pi" && body?.host !== "opencode") {
|
|
624
|
-
return this.jsonResponse({ error: "Choose Pi or
|
|
652
|
+
if (body?.host !== "pi" && body?.host !== "opencode" && body?.host !== "claude-code") {
|
|
653
|
+
return this.jsonResponse({ error: "Choose Pi, OpenCode, or Claude Code" }, 400);
|
|
625
654
|
}
|
|
626
655
|
const { browseImportSources } = await import("../importer/web-import-api.js");
|
|
627
656
|
return this.importResponse(() => browseImportSources(body.host, body.path));
|
|
628
657
|
}
|
|
629
658
|
if (path === "/api/settings/imports/sources/validate" && method === "POST") {
|
|
630
659
|
const body = (await req.json());
|
|
631
|
-
if (body?.host !== "pi" && body?.host !== "opencode") {
|
|
632
|
-
return this.jsonResponse({ error: "Choose Pi or
|
|
660
|
+
if (body?.host !== "pi" && body?.host !== "opencode" && body?.host !== "claude-code") {
|
|
661
|
+
return this.jsonResponse({ error: "Choose Pi, OpenCode, or Claude Code" }, 400);
|
|
633
662
|
}
|
|
634
663
|
const { validateImportSource } = await import("../importer/web-import-api.js");
|
|
635
664
|
return this.importResponse(() => validateImportSource(body.host, body.path));
|
|
@@ -718,6 +747,10 @@ export class WebServer {
|
|
|
718
747
|
const result = await handleSearch(query, tag, page, pageSize);
|
|
719
748
|
return this.jsonResponse(result);
|
|
720
749
|
}
|
|
750
|
+
if ((path === "/api/claude/retrieve" || path === "/api/claude/capture") &&
|
|
751
|
+
method === "POST") {
|
|
752
|
+
return this.claudeHookResponse(req, path);
|
|
753
|
+
}
|
|
721
754
|
if (path === "/api/stats" && method === "GET") {
|
|
722
755
|
const result = await handleStats();
|
|
723
756
|
return this.jsonResponse(result);
|
package/dist/types/index.d.ts
CHANGED