@vectorize-io/hindsight-openclaw 0.5.1 → 0.6.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 +147 -18
- package/dist/backfill-lib.d.ts +63 -0
- package/dist/backfill-lib.js +201 -0
- package/dist/backfill.d.ts +22 -0
- package/dist/backfill.js +473 -0
- package/dist/index.d.ts +49 -2
- package/dist/index.js +612 -344
- package/dist/retain-queue.d.ts +54 -0
- package/dist/retain-queue.js +105 -0
- package/dist/session-patterns.d.ts +10 -0
- package/dist/session-patterns.js +21 -0
- package/dist/setup-lib.d.ts +80 -0
- package/dist/setup-lib.js +134 -0
- package/dist/setup.d.ts +34 -0
- package/dist/setup.js +425 -0
- package/dist/types.d.ts +40 -40
- package/openclaw.plugin.json +110 -10
- package/package.json +13 -5
- package/dist/client.d.ts +0 -34
- package/dist/client.js +0 -215
- package/dist/embed-manager.d.ts +0 -27
- package/dist/embed-manager.js +0 -210
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL-backed retain queue for buffering failed HTTP retains.
|
|
3
|
+
*
|
|
4
|
+
* When the remote Hindsight API is unreachable, retain requests are stashed
|
|
5
|
+
* in a local JSONL file and flushed later. Only used in external API mode —
|
|
6
|
+
* the local daemon handles its own persistence.
|
|
7
|
+
*
|
|
8
|
+
* Zero runtime dependencies; uses only Node built-ins.
|
|
9
|
+
*/
|
|
10
|
+
/** The subset of a retain payload the queue needs to persist and replay. */
|
|
11
|
+
export interface QueuedRetainPayload {
|
|
12
|
+
content: string;
|
|
13
|
+
documentId?: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface QueuedRetain {
|
|
18
|
+
id: string;
|
|
19
|
+
bankId: string;
|
|
20
|
+
content: string;
|
|
21
|
+
documentId: string;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
tags?: string[];
|
|
24
|
+
createdAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface RetainQueueOptions {
|
|
27
|
+
/** Path to the JSONL queue file. The parent directory must already exist. */
|
|
28
|
+
filePath: string;
|
|
29
|
+
/** Max age in ms for queued items. `-1` (default) keeps items forever. */
|
|
30
|
+
maxAgeMs?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class RetainQueue {
|
|
33
|
+
private readonly filePath;
|
|
34
|
+
private readonly maxAgeMs;
|
|
35
|
+
private cachedSize;
|
|
36
|
+
constructor(opts: RetainQueueOptions);
|
|
37
|
+
/** Append a failed retain for later delivery. */
|
|
38
|
+
enqueue(bankId: string, request: QueuedRetainPayload, metadata?: Record<string, unknown>): void;
|
|
39
|
+
/** Get up to `limit` oldest pending items (FIFO). */
|
|
40
|
+
peek(limit?: number): QueuedRetain[];
|
|
41
|
+
/** Remove a single item by id. */
|
|
42
|
+
remove(id: string): void;
|
|
43
|
+
/** Remove multiple items by id in a single file rewrite. */
|
|
44
|
+
removeMany(ids: string[]): void;
|
|
45
|
+
/** Number of items waiting (cached, O(1)). */
|
|
46
|
+
size(): number;
|
|
47
|
+
/** Drop items older than `maxAgeMs`. No-op when `maxAgeMs < 0`. */
|
|
48
|
+
cleanup(): number;
|
|
49
|
+
/** No-op — kept for API symmetry with DB-backed queues. */
|
|
50
|
+
close(): void;
|
|
51
|
+
private readAll;
|
|
52
|
+
/** Atomically rewrite the file with the given items. */
|
|
53
|
+
private writeAll;
|
|
54
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL-backed retain queue for buffering failed HTTP retains.
|
|
3
|
+
*
|
|
4
|
+
* When the remote Hindsight API is unreachable, retain requests are stashed
|
|
5
|
+
* in a local JSONL file and flushed later. Only used in external API mode —
|
|
6
|
+
* the local daemon handles its own persistence.
|
|
7
|
+
*
|
|
8
|
+
* Zero runtime dependencies; uses only Node built-ins.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, appendFileSync, existsSync, renameSync, unlinkSync, } from 'fs';
|
|
11
|
+
import { randomBytes } from 'crypto';
|
|
12
|
+
export class RetainQueue {
|
|
13
|
+
filePath;
|
|
14
|
+
maxAgeMs;
|
|
15
|
+
cachedSize;
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.filePath = opts.filePath;
|
|
18
|
+
this.maxAgeMs = opts.maxAgeMs ?? -1;
|
|
19
|
+
this.cachedSize = this.readAll().length;
|
|
20
|
+
}
|
|
21
|
+
/** Append a failed retain for later delivery. */
|
|
22
|
+
enqueue(bankId, request, metadata) {
|
|
23
|
+
const item = {
|
|
24
|
+
id: `${Date.now()}-${randomBytes(4).toString('hex')}`,
|
|
25
|
+
bankId,
|
|
26
|
+
content: request.content,
|
|
27
|
+
documentId: request.documentId || 'conversation',
|
|
28
|
+
metadata: metadata || request.metadata || {},
|
|
29
|
+
tags: request.tags,
|
|
30
|
+
createdAt: new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
appendFileSync(this.filePath, JSON.stringify(item) + '\n', 'utf8');
|
|
33
|
+
this.cachedSize++;
|
|
34
|
+
}
|
|
35
|
+
/** Get up to `limit` oldest pending items (FIFO). */
|
|
36
|
+
peek(limit = 50) {
|
|
37
|
+
return this.readAll().slice(0, limit);
|
|
38
|
+
}
|
|
39
|
+
/** Remove a single item by id. */
|
|
40
|
+
remove(id) {
|
|
41
|
+
const items = this.readAll().filter((i) => i.id !== id);
|
|
42
|
+
this.writeAll(items);
|
|
43
|
+
}
|
|
44
|
+
/** Remove multiple items by id in a single file rewrite. */
|
|
45
|
+
removeMany(ids) {
|
|
46
|
+
const idSet = new Set(ids);
|
|
47
|
+
const items = this.readAll().filter((i) => !idSet.has(i.id));
|
|
48
|
+
this.writeAll(items);
|
|
49
|
+
}
|
|
50
|
+
/** Number of items waiting (cached, O(1)). */
|
|
51
|
+
size() {
|
|
52
|
+
return this.cachedSize;
|
|
53
|
+
}
|
|
54
|
+
/** Drop items older than `maxAgeMs`. No-op when `maxAgeMs < 0`. */
|
|
55
|
+
cleanup() {
|
|
56
|
+
if (this.maxAgeMs < 0)
|
|
57
|
+
return 0;
|
|
58
|
+
const cutoff = Date.now() - this.maxAgeMs;
|
|
59
|
+
const items = this.readAll();
|
|
60
|
+
const kept = items.filter((i) => new Date(i.createdAt).getTime() >= cutoff);
|
|
61
|
+
const removed = items.length - kept.length;
|
|
62
|
+
if (removed > 0)
|
|
63
|
+
this.writeAll(kept);
|
|
64
|
+
return removed;
|
|
65
|
+
}
|
|
66
|
+
/** No-op — kept for API symmetry with DB-backed queues. */
|
|
67
|
+
close() {
|
|
68
|
+
/* nothing to close */
|
|
69
|
+
}
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
readAll() {
|
|
72
|
+
if (!existsSync(this.filePath))
|
|
73
|
+
return [];
|
|
74
|
+
const content = readFileSync(this.filePath, 'utf8').trim();
|
|
75
|
+
if (!content)
|
|
76
|
+
return [];
|
|
77
|
+
const items = [];
|
|
78
|
+
for (const line of content.split('\n')) {
|
|
79
|
+
try {
|
|
80
|
+
items.push(JSON.parse(line));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// skip malformed lines
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return items;
|
|
87
|
+
}
|
|
88
|
+
/** Atomically rewrite the file with the given items. */
|
|
89
|
+
writeAll(items) {
|
|
90
|
+
if (items.length === 0) {
|
|
91
|
+
try {
|
|
92
|
+
unlinkSync(this.filePath);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* already gone */
|
|
96
|
+
}
|
|
97
|
+
this.cachedSize = 0;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const tmpPath = this.filePath + '.tmp';
|
|
101
|
+
writeFileSync(tmpPath, items.map((i) => JSON.stringify(i)).join('\n') + '\n', 'utf8');
|
|
102
|
+
renameSync(tmpPath, this.filePath);
|
|
103
|
+
this.cachedSize = items.length;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a session glob into a regex.
|
|
3
|
+
*
|
|
4
|
+
* `*` matches any non-colon characters, while `**` can span colons.
|
|
5
|
+
*/
|
|
6
|
+
export declare function compileSessionPattern(pattern: string): RegExp;
|
|
7
|
+
/** Compile all configured ignore patterns once at startup. */
|
|
8
|
+
export declare function compileSessionPatterns(patterns: string[]): RegExp[];
|
|
9
|
+
/** Check whether a session key matches any compiled ignore pattern. */
|
|
10
|
+
export declare function matchesSessionPattern(sessionKey: string, patterns: RegExp[]): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a session glob into a regex.
|
|
3
|
+
*
|
|
4
|
+
* `*` matches any non-colon characters, while `**` can span colons.
|
|
5
|
+
*/
|
|
6
|
+
export function compileSessionPattern(pattern) {
|
|
7
|
+
const escaped = pattern
|
|
8
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
9
|
+
.replace(/\*\*/g, "\u0000")
|
|
10
|
+
.replace(/\*/g, "[^:]*")
|
|
11
|
+
.replace(/\u0000/g, ".*");
|
|
12
|
+
return new RegExp(`^${escaped}$`);
|
|
13
|
+
}
|
|
14
|
+
/** Compile all configured ignore patterns once at startup. */
|
|
15
|
+
export function compileSessionPatterns(patterns) {
|
|
16
|
+
return patterns.map((pattern) => compileSessionPattern(pattern));
|
|
17
|
+
}
|
|
18
|
+
/** Check whether a session key matches any compiled ignore pattern. */
|
|
19
|
+
export function matchesSessionPattern(sessionKey, patterns) {
|
|
20
|
+
return patterns.some((pattern) => pattern.test(sessionKey));
|
|
21
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind the Hindsight OpenClaw setup wizard. Kept separate from
|
|
3
|
+
* setup.ts (the @clack/prompts entry point) so the mechanical bits are easy to
|
|
4
|
+
* unit test without simulating an interactive terminal.
|
|
5
|
+
*
|
|
6
|
+
* Scanner-safe: imports no subprocess APIs and does not read any environment
|
|
7
|
+
* variable. All config writing is an atomic rename over the OpenClaw config JSON.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PLUGIN_ID = "hindsight-openclaw";
|
|
10
|
+
/**
|
|
11
|
+
* Default Hindsight Cloud endpoint. Update this when the hosted service URL is
|
|
12
|
+
* finalized, or users can override it at the prompt.
|
|
13
|
+
*/
|
|
14
|
+
export declare const HINDSIGHT_CLOUD_URL = "https://api.hindsight.vectorize.io";
|
|
15
|
+
export declare const DEFAULT_OPENCLAW_CONFIG_PATH: string;
|
|
16
|
+
export interface SecretRef {
|
|
17
|
+
source: 'env' | 'file' | 'exec';
|
|
18
|
+
provider: string;
|
|
19
|
+
id: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PluginEntry {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
config?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface OpenClawConfigShape {
|
|
26
|
+
plugins?: {
|
|
27
|
+
entries?: Record<string, PluginEntry>;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
export type SetupMode = 'cloud' | 'api' | 'embedded';
|
|
33
|
+
export declare const NO_KEY_PROVIDERS: ReadonlySet<string>;
|
|
34
|
+
export declare function loadConfig(path: string): Promise<OpenClawConfigShape>;
|
|
35
|
+
export declare function saveConfig(path: string, cfg: OpenClawConfigShape): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Ensure a `plugins.entries["hindsight-openclaw"].config` object exists, set
|
|
38
|
+
* `enabled: true`, and return the mutable config record. Idempotent — safe to
|
|
39
|
+
* call against a fresh or already-configured OpenClaw config.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ensurePluginConfig(cfg: OpenClawConfigShape): Record<string, unknown>;
|
|
42
|
+
export declare function envSecretRef(id: string): SecretRef;
|
|
43
|
+
export declare function clearCloudFields(pluginConfig: Record<string, unknown>): void;
|
|
44
|
+
export declare function clearLocalLlmFields(pluginConfig: Record<string, unknown>): void;
|
|
45
|
+
export declare function isValidEnvVarName(value: string | undefined): boolean;
|
|
46
|
+
export declare function defaultApiKeyEnvVar(provider: string): string;
|
|
47
|
+
export interface CloudSetupInput {
|
|
48
|
+
apiUrl?: string;
|
|
49
|
+
tokenEnvVar: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ApiSetupInput {
|
|
52
|
+
apiUrl: string;
|
|
53
|
+
tokenEnvVar?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface EmbeddedSetupInput {
|
|
56
|
+
llmProvider: string;
|
|
57
|
+
apiKeyEnvVar?: string;
|
|
58
|
+
llmModel?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Apply the Cloud mode to a plugin config in place: sets `hindsightApiUrl` and
|
|
62
|
+
* a `hindsightApiToken` SecretRef, strips any leftover local-LLM fields so we
|
|
63
|
+
* don't carry stale credentials across mode switches.
|
|
64
|
+
*/
|
|
65
|
+
export declare function applyCloudMode(pluginConfig: Record<string, unknown>, input: CloudSetupInput): void;
|
|
66
|
+
/**
|
|
67
|
+
* Apply the external-API mode to a plugin config in place: sets a required
|
|
68
|
+
* `hindsightApiUrl`, optional `hindsightApiToken` SecretRef, and strips any
|
|
69
|
+
* leftover local-LLM fields so mode switches don't carry stale state.
|
|
70
|
+
*/
|
|
71
|
+
export declare function applyApiMode(pluginConfig: Record<string, unknown>, input: ApiSetupInput): void;
|
|
72
|
+
/**
|
|
73
|
+
* Apply the embedded-daemon mode to a plugin config in place: sets
|
|
74
|
+
* `llmProvider`, optional `llmApiKey` SecretRef, optional `llmModel`, and
|
|
75
|
+
* strips any external-API settings so mode switches don't carry stale state.
|
|
76
|
+
*/
|
|
77
|
+
export declare function applyEmbeddedMode(pluginConfig: Record<string, unknown>, input: EmbeddedSetupInput): void;
|
|
78
|
+
export declare function summarizeCloud(input: CloudSetupInput): string;
|
|
79
|
+
export declare function summarizeApi(input: ApiSetupInput): string;
|
|
80
|
+
export declare function summarizeEmbedded(input: EmbeddedSetupInput): string;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers behind the Hindsight OpenClaw setup wizard. Kept separate from
|
|
3
|
+
* setup.ts (the @clack/prompts entry point) so the mechanical bits are easy to
|
|
4
|
+
* unit test without simulating an interactive terminal.
|
|
5
|
+
*
|
|
6
|
+
* Scanner-safe: imports no subprocess APIs and does not read any environment
|
|
7
|
+
* variable. All config writing is an atomic rename over the OpenClaw config JSON.
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, writeFile, mkdir, rename } from 'fs/promises';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
export const PLUGIN_ID = 'hindsight-openclaw';
|
|
13
|
+
/**
|
|
14
|
+
* Default Hindsight Cloud endpoint. Update this when the hosted service URL is
|
|
15
|
+
* finalized, or users can override it at the prompt.
|
|
16
|
+
*/
|
|
17
|
+
export const HINDSIGHT_CLOUD_URL = 'https://api.hindsight.vectorize.io';
|
|
18
|
+
export const DEFAULT_OPENCLAW_CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json');
|
|
19
|
+
export const NO_KEY_PROVIDERS = new Set([
|
|
20
|
+
'claude-code',
|
|
21
|
+
'openai-codex',
|
|
22
|
+
'ollama',
|
|
23
|
+
]);
|
|
24
|
+
export async function loadConfig(path) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(path, 'utf8');
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err?.code === 'ENOENT')
|
|
31
|
+
return {};
|
|
32
|
+
throw new Error(`Failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function saveConfig(path, cfg) {
|
|
36
|
+
await mkdir(dirname(path), { recursive: true });
|
|
37
|
+
const serialized = `${JSON.stringify(cfg, null, 2)}\n`;
|
|
38
|
+
const tmpPath = `${path}.tmp-${Date.now()}`;
|
|
39
|
+
await writeFile(tmpPath, serialized, 'utf8');
|
|
40
|
+
await rename(tmpPath, path);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ensure a `plugins.entries["hindsight-openclaw"].config` object exists, set
|
|
44
|
+
* `enabled: true`, and return the mutable config record. Idempotent — safe to
|
|
45
|
+
* call against a fresh or already-configured OpenClaw config.
|
|
46
|
+
*/
|
|
47
|
+
export function ensurePluginConfig(cfg) {
|
|
48
|
+
const plugins = (cfg.plugins ??= {});
|
|
49
|
+
const entries = (plugins.entries ??= {});
|
|
50
|
+
const entry = (entries[PLUGIN_ID] ??= { enabled: true });
|
|
51
|
+
entry.enabled = true;
|
|
52
|
+
return (entry.config ??= {});
|
|
53
|
+
}
|
|
54
|
+
export function envSecretRef(id) {
|
|
55
|
+
return { source: 'env', provider: 'default', id };
|
|
56
|
+
}
|
|
57
|
+
export function clearCloudFields(pluginConfig) {
|
|
58
|
+
delete pluginConfig.hindsightApiUrl;
|
|
59
|
+
delete pluginConfig.hindsightApiToken;
|
|
60
|
+
}
|
|
61
|
+
export function clearLocalLlmFields(pluginConfig) {
|
|
62
|
+
delete pluginConfig.llmProvider;
|
|
63
|
+
delete pluginConfig.llmModel;
|
|
64
|
+
delete pluginConfig.llmApiKey;
|
|
65
|
+
delete pluginConfig.llmBaseUrl;
|
|
66
|
+
}
|
|
67
|
+
const ENV_VAR_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
68
|
+
export function isValidEnvVarName(value) {
|
|
69
|
+
return !!value && ENV_VAR_RE.test(value.trim());
|
|
70
|
+
}
|
|
71
|
+
export function defaultApiKeyEnvVar(provider) {
|
|
72
|
+
return `${provider.toUpperCase().replace(/-/g, '_')}_API_KEY`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply the Cloud mode to a plugin config in place: sets `hindsightApiUrl` and
|
|
76
|
+
* a `hindsightApiToken` SecretRef, strips any leftover local-LLM fields so we
|
|
77
|
+
* don't carry stale credentials across mode switches.
|
|
78
|
+
*/
|
|
79
|
+
export function applyCloudMode(pluginConfig, input) {
|
|
80
|
+
clearLocalLlmFields(pluginConfig);
|
|
81
|
+
pluginConfig.hindsightApiUrl = (input.apiUrl ?? HINDSIGHT_CLOUD_URL).trim();
|
|
82
|
+
pluginConfig.hindsightApiToken = envSecretRef(input.tokenEnvVar.trim());
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Apply the external-API mode to a plugin config in place: sets a required
|
|
86
|
+
* `hindsightApiUrl`, optional `hindsightApiToken` SecretRef, and strips any
|
|
87
|
+
* leftover local-LLM fields so mode switches don't carry stale state.
|
|
88
|
+
*/
|
|
89
|
+
export function applyApiMode(pluginConfig, input) {
|
|
90
|
+
clearLocalLlmFields(pluginConfig);
|
|
91
|
+
pluginConfig.hindsightApiUrl = input.apiUrl.trim();
|
|
92
|
+
if (input.tokenEnvVar && input.tokenEnvVar.trim().length > 0) {
|
|
93
|
+
pluginConfig.hindsightApiToken = envSecretRef(input.tokenEnvVar.trim());
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
delete pluginConfig.hindsightApiToken;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Apply the embedded-daemon mode to a plugin config in place: sets
|
|
101
|
+
* `llmProvider`, optional `llmApiKey` SecretRef, optional `llmModel`, and
|
|
102
|
+
* strips any external-API settings so mode switches don't carry stale state.
|
|
103
|
+
*/
|
|
104
|
+
export function applyEmbeddedMode(pluginConfig, input) {
|
|
105
|
+
clearCloudFields(pluginConfig);
|
|
106
|
+
pluginConfig.llmProvider = input.llmProvider;
|
|
107
|
+
if (NO_KEY_PROVIDERS.has(input.llmProvider)) {
|
|
108
|
+
delete pluginConfig.llmApiKey;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
if (!input.apiKeyEnvVar) {
|
|
112
|
+
throw new Error(`llmProvider "${input.llmProvider}" requires an apiKeyEnvVar`);
|
|
113
|
+
}
|
|
114
|
+
pluginConfig.llmApiKey = envSecretRef(input.apiKeyEnvVar.trim());
|
|
115
|
+
}
|
|
116
|
+
if (input.llmModel && input.llmModel.trim().length > 0) {
|
|
117
|
+
pluginConfig.llmModel = input.llmModel.trim();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
delete pluginConfig.llmModel;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function summarizeCloud(input) {
|
|
124
|
+
const url = (input.apiUrl ?? HINDSIGHT_CLOUD_URL).trim();
|
|
125
|
+
return `Cloud → ${url} (token from \${${input.tokenEnvVar.trim()}})`;
|
|
126
|
+
}
|
|
127
|
+
export function summarizeApi(input) {
|
|
128
|
+
const suffix = input.tokenEnvVar ? ' (authenticated)' : ' (no auth)';
|
|
129
|
+
return `External API → ${input.apiUrl.trim()}${suffix}`;
|
|
130
|
+
}
|
|
131
|
+
export function summarizeEmbedded(input) {
|
|
132
|
+
const keyHint = NO_KEY_PROVIDERS.has(input.llmProvider) ? '' : ' (key via SecretRef)';
|
|
133
|
+
return `Embedded daemon → ${input.llmProvider}${keyHint}`;
|
|
134
|
+
}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Setup wizard for the Hindsight OpenClaw plugin.
|
|
4
|
+
*
|
|
5
|
+
* Two modes of operation:
|
|
6
|
+
*
|
|
7
|
+
* 1. Interactive — no flags, just run `hindsight-openclaw-setup`. Walks the
|
|
8
|
+
* user through picking a mode (Cloud / External API / Embedded daemon)
|
|
9
|
+
* via @clack/prompts and writes openclaw.json.
|
|
10
|
+
*
|
|
11
|
+
* 2. Non-interactive — pass `--mode cloud|api|embedded` plus the relevant
|
|
12
|
+
* flags for that mode. No prompts, intended for CI and scripted installs.
|
|
13
|
+
*
|
|
14
|
+
* Scanner-safe: does not import subprocess APIs and does not read environment
|
|
15
|
+
* variables directly. Pure config manipulation lives in setup-lib.ts.
|
|
16
|
+
*/
|
|
17
|
+
import { type SetupMode } from './setup-lib.js';
|
|
18
|
+
export interface ParsedCliArgs {
|
|
19
|
+
help: boolean;
|
|
20
|
+
configPath?: string;
|
|
21
|
+
mode?: SetupMode;
|
|
22
|
+
apiUrl?: string;
|
|
23
|
+
tokenEnv?: string;
|
|
24
|
+
noToken: boolean;
|
|
25
|
+
provider?: string;
|
|
26
|
+
apiKeyEnv?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
positional?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
|
|
31
|
+
export declare function runNonInteractive(args: ParsedCliArgs, configPath: string): Promise<{
|
|
32
|
+
summary: string;
|
|
33
|
+
configPath: string;
|
|
34
|
+
}>;
|