claude-smart 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +69 -27
  3. package/bin/claude-smart.js +296 -11
  4. package/package.json +11 -1
  5. package/plugin/.claude-plugin/plugin.json +17 -0
  6. package/plugin/.codex-plugin/plugin.json +35 -0
  7. package/plugin/LICENSE +202 -0
  8. package/plugin/README.md +37 -0
  9. package/plugin/bin/cs-cite +77 -0
  10. package/plugin/commands/clear-all.md +8 -0
  11. package/plugin/commands/dashboard.md +8 -0
  12. package/plugin/commands/learn.md +12 -0
  13. package/plugin/commands/restart.md +8 -0
  14. package/plugin/commands/show.md +8 -0
  15. package/plugin/dashboard/AGENTS.md +6 -0
  16. package/plugin/dashboard/app/api/claude-settings/route.ts +19 -0
  17. package/plugin/dashboard/app/api/config/route.ts +16 -0
  18. package/plugin/dashboard/app/api/health/route.ts +10 -0
  19. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +63 -0
  20. package/plugin/dashboard/app/api/sessions/[id]/route.ts +28 -0
  21. package/plugin/dashboard/app/api/sessions/route.ts +14 -0
  22. package/plugin/dashboard/app/configure/env/page.tsx +318 -0
  23. package/plugin/dashboard/app/configure/layout.tsx +47 -0
  24. package/plugin/dashboard/app/configure/page.tsx +5 -0
  25. package/plugin/dashboard/app/configure/server/page.tsx +258 -0
  26. package/plugin/dashboard/app/dashboard/page.tsx +227 -0
  27. package/plugin/dashboard/app/globals.css +129 -0
  28. package/plugin/dashboard/app/icon.png +0 -0
  29. package/plugin/dashboard/app/layout.tsx +40 -0
  30. package/plugin/dashboard/app/page.tsx +5 -0
  31. package/plugin/dashboard/app/preferences/[id]/page.tsx +531 -0
  32. package/plugin/dashboard/app/preferences/page.tsx +126 -0
  33. package/plugin/dashboard/app/providers.tsx +12 -0
  34. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +321 -0
  35. package/plugin/dashboard/app/sessions/page.tsx +186 -0
  36. package/plugin/dashboard/app/skills/page.tsx +362 -0
  37. package/plugin/dashboard/app/skills/project/[id]/page.tsx +597 -0
  38. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +830 -0
  39. package/plugin/dashboard/components/common/delete-all-button.tsx +45 -0
  40. package/plugin/dashboard/components/common/empty-state.tsx +34 -0
  41. package/plugin/dashboard/components/common/learnings-badge.tsx +34 -0
  42. package/plugin/dashboard/components/common/page-header.tsx +34 -0
  43. package/plugin/dashboard/components/common/page-tabs.tsx +115 -0
  44. package/plugin/dashboard/components/common/stat-card.tsx +38 -0
  45. package/plugin/dashboard/components/layout/nav-items.ts +22 -0
  46. package/plugin/dashboard/components/layout/sidebar.tsx +45 -0
  47. package/plugin/dashboard/components/layout/top-bar.tsx +64 -0
  48. package/plugin/dashboard/components/stall-banner.tsx +53 -0
  49. package/plugin/dashboard/components/ui/badge.tsx +52 -0
  50. package/plugin/dashboard/components/ui/button.tsx +60 -0
  51. package/plugin/dashboard/components/ui/collapsible.tsx +21 -0
  52. package/plugin/dashboard/components/ui/input.tsx +20 -0
  53. package/plugin/dashboard/components/ui/label.tsx +20 -0
  54. package/plugin/dashboard/components/ui/scroll-area.tsx +55 -0
  55. package/plugin/dashboard/components/ui/select.tsx +201 -0
  56. package/plugin/dashboard/components/ui/separator.tsx +25 -0
  57. package/plugin/dashboard/components/ui/sheet.tsx +135 -0
  58. package/plugin/dashboard/components/ui/switch.tsx +32 -0
  59. package/plugin/dashboard/components.json +25 -0
  60. package/plugin/dashboard/eslint.config.mjs +16 -0
  61. package/plugin/dashboard/hooks/use-settings.tsx +88 -0
  62. package/plugin/dashboard/hooks/use-stall-state.ts +59 -0
  63. package/plugin/dashboard/lib/claude-settings-file.ts +114 -0
  64. package/plugin/dashboard/lib/config-file.ts +131 -0
  65. package/plugin/dashboard/lib/format.ts +58 -0
  66. package/plugin/dashboard/lib/reflexio-client.ts +238 -0
  67. package/plugin/dashboard/lib/reflexio-url.ts +17 -0
  68. package/plugin/dashboard/lib/session-reader.ts +245 -0
  69. package/plugin/dashboard/lib/status.ts +24 -0
  70. package/plugin/dashboard/lib/types.ts +145 -0
  71. package/plugin/dashboard/lib/utils.ts +6 -0
  72. package/plugin/dashboard/next.config.ts +7 -0
  73. package/plugin/dashboard/package-lock.json +10275 -0
  74. package/plugin/dashboard/package.json +37 -0
  75. package/plugin/dashboard/postcss.config.mjs +7 -0
  76. package/plugin/dashboard/public/claude-smart-icon.png +0 -0
  77. package/plugin/dashboard/tsconfig.json +34 -0
  78. package/plugin/hooks/codex-hooks.json +67 -0
  79. package/plugin/hooks/hooks.json +111 -0
  80. package/plugin/pyproject.toml +49 -0
  81. package/plugin/scripts/_codex_env.sh +27 -0
  82. package/plugin/scripts/_lib.sh +325 -0
  83. package/plugin/scripts/backend-service.sh +208 -0
  84. package/plugin/scripts/cli.sh +40 -0
  85. package/plugin/scripts/dashboard-build.sh +139 -0
  86. package/plugin/scripts/dashboard-open.sh +107 -0
  87. package/plugin/scripts/dashboard-service.sh +195 -0
  88. package/plugin/scripts/ensure-plugin-root.sh +84 -0
  89. package/plugin/scripts/hook_entry.sh +70 -0
  90. package/plugin/scripts/smart-install.sh +411 -0
  91. package/plugin/src/claude_smart/__init__.py +3 -0
  92. package/plugin/src/claude_smart/cli.py +1273 -0
  93. package/plugin/src/claude_smart/context_format.py +277 -0
  94. package/plugin/src/claude_smart/context_inject.py +92 -0
  95. package/plugin/src/claude_smart/cs_cite.py +236 -0
  96. package/plugin/src/claude_smart/events/__init__.py +1 -0
  97. package/plugin/src/claude_smart/events/post_tool.py +148 -0
  98. package/plugin/src/claude_smart/events/pre_tool.py +52 -0
  99. package/plugin/src/claude_smart/events/session_end.py +20 -0
  100. package/plugin/src/claude_smart/events/session_start.py +119 -0
  101. package/plugin/src/claude_smart/events/stop.py +393 -0
  102. package/plugin/src/claude_smart/events/user_prompt.py +73 -0
  103. package/plugin/src/claude_smart/hook.py +114 -0
  104. package/plugin/src/claude_smart/ids.py +56 -0
  105. package/plugin/src/claude_smart/internal_call.py +89 -0
  106. package/plugin/src/claude_smart/optimizer_assistant.py +203 -0
  107. package/plugin/src/claude_smart/publish.py +71 -0
  108. package/plugin/src/claude_smart/query_compose.py +51 -0
  109. package/plugin/src/claude_smart/reflexio_adapter.py +403 -0
  110. package/plugin/src/claude_smart/runtime.py +52 -0
  111. package/plugin/src/claude_smart/stall_banner.py +61 -0
  112. package/plugin/src/claude_smart/state.py +276 -0
  113. package/plugin/uv.lock +3720 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Read/write Claude Code project settings used to inject hook-side env vars.
3
+ *
4
+ * This is intentionally separate from ~/.reflexio/.env: reflexio reads that
5
+ * file in the backend process, while claude-smart hooks read env from the
6
+ * Claude Code process tree.
7
+ */
8
+
9
+ import fs from "node:fs/promises";
10
+ import { existsSync } from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import type { ClaudeCodeHookConfig } from "./types";
14
+
15
+ const ENV_KEY = "CLAUDE_SMART_ENABLE_OPTIMIZER";
16
+
17
+ function settingsPath(): string {
18
+ return path.join(workspaceRoot(), ".claude", "settings.local.json");
19
+ }
20
+
21
+ function userSettingsPath(): string {
22
+ return path.join(os.homedir(), ".claude", "settings.json");
23
+ }
24
+
25
+ function workspaceRoot(): string {
26
+ const configured = process.env.CLAUDE_SMART_DASHBOARD_WORKSPACE;
27
+ const start = configured
28
+ ? path.resolve(configured)
29
+ : path.resolve(process.cwd(), "../..");
30
+ return findProjectRoot(start);
31
+ }
32
+
33
+ function findProjectRoot(start: string): string {
34
+ let current = start;
35
+ while (true) {
36
+ if (
37
+ existsSync(path.join(current, ".claude")) ||
38
+ existsSync(path.join(current, ".git"))
39
+ ) {
40
+ return current;
41
+ }
42
+ const parent = path.dirname(current);
43
+ if (parent === current) return start;
44
+ current = parent;
45
+ }
46
+ }
47
+
48
+ function asRecord(value: unknown): Record<string, unknown> {
49
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
50
+ return {};
51
+ }
52
+ return value as Record<string, unknown>;
53
+ }
54
+
55
+ function parseEnabled(value: unknown): boolean {
56
+ if (value === undefined || value === null || value === "") return true;
57
+ if (value === false || value === 0) return false;
58
+ if (typeof value === "string" && value.trim() === "0") return false;
59
+ return true;
60
+ }
61
+
62
+ function optimizerValue(settings: Record<string, unknown>): boolean | null {
63
+ const env = asRecord(settings.env);
64
+ if (!(ENV_KEY in env)) return null;
65
+ return parseEnabled(env[ENV_KEY]);
66
+ }
67
+
68
+ async function readSettingsFile(file: string): Promise<Record<string, unknown>> {
69
+ try {
70
+ const text = await fs.readFile(file, "utf-8");
71
+ return asRecord(JSON.parse(text));
72
+ } catch (error) {
73
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ export async function readClaudeCodeHookConfig(): Promise<ClaudeCodeHookConfig> {
79
+ const localPath = settingsPath();
80
+ const globalPath = userSettingsPath();
81
+ const [localSettings, userSettings] = await Promise.all([
82
+ readSettingsFile(localPath),
83
+ readSettingsFile(globalPath),
84
+ ]);
85
+ const localValue = optimizerValue(localSettings);
86
+ const userValue = optimizerValue(userSettings);
87
+ const effectiveValue = localValue ?? userValue ?? true;
88
+ return {
89
+ CLAUDE_SMART_ENABLE_OPTIMIZER: effectiveValue,
90
+ effectiveValue,
91
+ localValue,
92
+ userValue,
93
+ settingsPath: localPath,
94
+ userSettingsPath: globalPath,
95
+ };
96
+ }
97
+
98
+ export async function writeClaudeCodeHookConfig(
99
+ update: Partial<ClaudeCodeHookConfig>,
100
+ ): Promise<void> {
101
+ const file = settingsPath();
102
+ const settings = await readSettingsFile(file);
103
+ const env = asRecord(settings.env);
104
+
105
+ if (!("CLAUDE_SMART_ENABLE_OPTIMIZER" in update)) return;
106
+ env[ENV_KEY] = update.CLAUDE_SMART_ENABLE_OPTIMIZER ? "1" : "0";
107
+
108
+ settings.env = env;
109
+ await fs.mkdir(path.dirname(file), { recursive: true });
110
+ await fs.writeFile(file, `${JSON.stringify(settings, null, 2)}\n`, {
111
+ encoding: "utf-8",
112
+ mode: 0o600,
113
+ });
114
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Read/write ~/.reflexio/.env — preserving unknown keys, comments, and blank
3
+ * lines. Used by the Configure page.
4
+ */
5
+
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ import os from "node:os";
9
+ import type { ClaudeSmartConfig } from "./types";
10
+
11
+ const KNOWN_KEYS = [
12
+ "REFLEXIO_URL",
13
+ "CLAUDE_SMART_USE_LOCAL_CLI",
14
+ "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
15
+ "CLAUDE_SMART_CLI_PATH",
16
+ "CLAUDE_SMART_CLI_TIMEOUT",
17
+ "CLAUDE_SMART_STATE_DIR",
18
+ ] as const;
19
+
20
+ const KNOWN = new Set<string>(KNOWN_KEYS);
21
+
22
+ const BOOL_KEYS = new Set([
23
+ "CLAUDE_SMART_USE_LOCAL_CLI",
24
+ "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
25
+ ]);
26
+
27
+ function envPath(): string {
28
+ return path.join(os.homedir(), ".reflexio", ".env");
29
+ }
30
+
31
+ function parseLine(line: string): { key: string; value: string } | null {
32
+ const trimmed = line.trim();
33
+ if (!trimmed || trimmed.startsWith("#")) return null;
34
+ const eq = trimmed.indexOf("=");
35
+ if (eq < 0) return null;
36
+ const key = trimmed.slice(0, eq).trim();
37
+ let value = trimmed.slice(eq + 1).trim();
38
+ if (
39
+ (value.startsWith('"') && value.endsWith('"')) ||
40
+ (value.startsWith("'") && value.endsWith("'"))
41
+ ) {
42
+ value = value.slice(1, -1);
43
+ }
44
+ return { key, value };
45
+ }
46
+
47
+ export async function readConfig(): Promise<ClaudeSmartConfig> {
48
+ const defaults: ClaudeSmartConfig = {
49
+ REFLEXIO_URL: "http://localhost:8071/",
50
+ CLAUDE_SMART_USE_LOCAL_CLI: false,
51
+ CLAUDE_SMART_USE_LOCAL_EMBEDDING: false,
52
+ CLAUDE_SMART_CLI_PATH: "",
53
+ CLAUDE_SMART_CLI_TIMEOUT: "120",
54
+ CLAUDE_SMART_STATE_DIR: "",
55
+ };
56
+ let text: string;
57
+ try {
58
+ text = await fs.readFile(envPath(), "utf-8");
59
+ } catch {
60
+ return defaults;
61
+ }
62
+ const out: ClaudeSmartConfig = { ...defaults };
63
+ for (const line of text.split("\n")) {
64
+ const pair = parseLine(line);
65
+ if (!pair) continue;
66
+ if (!KNOWN.has(pair.key)) continue;
67
+ if (BOOL_KEYS.has(pair.key)) {
68
+ out[pair.key] = pair.value === "1" || pair.value.toLowerCase() === "true";
69
+ } else {
70
+ out[pair.key] = pair.value;
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+
76
+ export async function writeConfig(update: Partial<ClaudeSmartConfig>): Promise<void> {
77
+ const file = envPath();
78
+ await fs.mkdir(path.dirname(file), { recursive: true });
79
+
80
+ const safeUpdate = Object.fromEntries(
81
+ Object.entries(update).filter(([k]) => KNOWN.has(k)),
82
+ );
83
+
84
+ let existing = "";
85
+ try {
86
+ existing = await fs.readFile(file, "utf-8");
87
+ } catch {
88
+ existing = "";
89
+ }
90
+
91
+ const lines = existing.split("\n");
92
+ const seen = new Set<string>();
93
+ const outLines: string[] = [];
94
+
95
+ for (const line of lines) {
96
+ const pair = parseLine(line);
97
+ if (!pair) {
98
+ outLines.push(line);
99
+ continue;
100
+ }
101
+ if (pair.key in safeUpdate) {
102
+ seen.add(pair.key);
103
+ const raw = safeUpdate[pair.key];
104
+ outLines.push(`${pair.key}=${formatValue(pair.key, raw)}`);
105
+ } else {
106
+ outLines.push(line);
107
+ }
108
+ }
109
+
110
+ for (const key of Object.keys(safeUpdate)) {
111
+ if (seen.has(key)) continue;
112
+ const raw = safeUpdate[key];
113
+ if (raw === undefined || raw === "") continue;
114
+ outLines.push(`${key}=${formatValue(key, raw)}`);
115
+ }
116
+
117
+ const content = outLines.join("\n");
118
+ await fs.writeFile(file, content.endsWith("\n") ? content : content + "\n", {
119
+ encoding: "utf-8",
120
+ mode: 0o600,
121
+ });
122
+ }
123
+
124
+ function formatValue(key: string, raw: unknown): string {
125
+ if (BOOL_KEYS.has(key)) {
126
+ return raw === true || raw === "1" || raw === "true" ? "1" : "0";
127
+ }
128
+ return String(raw ?? "");
129
+ }
130
+
131
+ export { KNOWN_KEYS };
@@ -0,0 +1,58 @@
1
+ export function formatTimestamp(ts: number | null | undefined): string {
2
+ if (!ts) return "—";
3
+ const ms = ts < 1e12 ? ts * 1000 : ts;
4
+ const d = new Date(ms);
5
+ if (Number.isNaN(d.getTime())) return "—";
6
+ return d.toLocaleString(undefined, {
7
+ month: "short",
8
+ day: "numeric",
9
+ hour: "2-digit",
10
+ minute: "2-digit",
11
+ });
12
+ }
13
+
14
+ export function formatRelative(ts: number | null | undefined): string {
15
+ if (!ts) return "—";
16
+ const ms = ts < 1e12 ? ts * 1000 : ts;
17
+ const diff = Date.now() - ms;
18
+ if (diff < 0) return "just now";
19
+ const s = Math.floor(diff / 1000);
20
+ if (s < 60) return `${s}s ago`;
21
+ const m = Math.floor(s / 60);
22
+ if (m < 60) return `${m}m ago`;
23
+ const h = Math.floor(m / 60);
24
+ if (h < 24) return `${h}h ago`;
25
+ const days = Math.floor(h / 24);
26
+ if (days < 30) return `${days}d ago`;
27
+ return formatTimestamp(ts);
28
+ }
29
+
30
+ export function truncate(text: string, n: number): string {
31
+ if (text.length <= n) return text;
32
+ return text.slice(0, n - 1).trimEnd() + "…";
33
+ }
34
+
35
+ export function truncateId(id: string, prefix = 8, suffix = 4): string {
36
+ if (id.length <= prefix + suffix + 1) return id;
37
+ return `${id.slice(0, prefix)}…${id.slice(-suffix)}`;
38
+ }
39
+
40
+ export function dayBucket(ts: number | null | undefined): string {
41
+ if (!ts) return "Unknown";
42
+ const ms = ts < 1e12 ? ts * 1000 : ts;
43
+ const d = new Date(ms);
44
+ const now = new Date();
45
+ const startOfToday = new Date(
46
+ now.getFullYear(),
47
+ now.getMonth(),
48
+ now.getDate(),
49
+ ).getTime();
50
+ const oneDay = 86_400_000;
51
+ if (d.getTime() >= startOfToday) return "Today";
52
+ if (d.getTime() >= startOfToday - oneDay) return "Yesterday";
53
+ if (d.getTime() >= startOfToday - 7 * oneDay) return "Earlier this week";
54
+ if (d.getFullYear() === now.getFullYear()) {
55
+ return d.toLocaleDateString(undefined, { month: "long" });
56
+ }
57
+ return d.toLocaleDateString(undefined, { year: "numeric", month: "long" });
58
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Client-side wrapper for talking to reflexio through the Next.js proxy at
3
+ * /api/reflexio/*. The proxy forwards to the reflexio URL the user picked in
4
+ * Settings (see hooks/use-settings.tsx). Endpoint paths and request bodies
5
+ * mirror reflexio/reflexio/server/api.py — every call below corresponds to a
6
+ * FastAPI route mounted under the /api prefix.
7
+ */
8
+
9
+ import type {
10
+ AgentPlaybook,
11
+ AgentPlaybookStatus,
12
+ Interaction,
13
+ UserPlaybook,
14
+ UserProfile,
15
+ } from "./types";
16
+
17
+ type Json = Record<string, unknown>;
18
+
19
+ interface Opts {
20
+ reflexioUrl?: string;
21
+ }
22
+
23
+ async function request<T>(
24
+ path: string,
25
+ init: RequestInit,
26
+ reflexioUrl: string | undefined,
27
+ ): Promise<T> {
28
+ const headers = new Headers(init.headers);
29
+ headers.set("content-type", "application/json");
30
+ if (reflexioUrl) headers.set("x-reflexio-url", reflexioUrl);
31
+
32
+ const res = await fetch(`/api/reflexio/api/${path.replace(/^\/+/, "")}`, {
33
+ ...init,
34
+ headers,
35
+ cache: "no-store",
36
+ });
37
+ if (!res.ok) {
38
+ const body = await res.text().catch(() => "");
39
+ throw new Error(`reflexio ${res.status}: ${body.slice(0, 300)}`);
40
+ }
41
+ return res.json() as Promise<T>;
42
+ }
43
+
44
+ function post<T>(path: string, body: Json, reflexioUrl?: string): Promise<T> {
45
+ return request<T>(path, { method: "POST", body: JSON.stringify(body) }, reflexioUrl);
46
+ }
47
+
48
+ function get<T>(path: string, reflexioUrl?: string): Promise<T> {
49
+ return request<T>(path, { method: "GET" }, reflexioUrl);
50
+ }
51
+
52
+ export const reflexio = {
53
+ /** POST /api/get_user_playbooks — all filters are optional. */
54
+ async getUserPlaybooks(
55
+ opts: Opts & {
56
+ userId?: string;
57
+ agentVersion?: string;
58
+ playbookName?: string;
59
+ statusFilter?: (string | null)[];
60
+ limit?: number;
61
+ },
62
+ ): Promise<{ user_playbooks: UserPlaybook[] }> {
63
+ const body: Json = { limit: opts.limit ?? 100 };
64
+ if (opts.userId) body.user_id = opts.userId;
65
+ if (opts.agentVersion) body.agent_version = opts.agentVersion;
66
+ if (opts.playbookName) body.playbook_name = opts.playbookName;
67
+ if (opts.statusFilter) body.status_filter = opts.statusFilter;
68
+ return post("get_user_playbooks", body, opts.reflexioUrl);
69
+ },
70
+
71
+ /** POST /api/get_agent_playbooks — all filters are optional. */
72
+ async getAgentPlaybooks(
73
+ opts: Opts & {
74
+ agentVersion?: string;
75
+ playbookName?: string;
76
+ statusFilter?: (string | null)[];
77
+ playbookStatusFilter?: AgentPlaybookStatus;
78
+ limit?: number;
79
+ },
80
+ ): Promise<{ agent_playbooks: AgentPlaybook[] }> {
81
+ const body: Json = { limit: opts.limit ?? 100 };
82
+ if (opts.agentVersion) body.agent_version = opts.agentVersion;
83
+ if (opts.playbookName) body.playbook_name = opts.playbookName;
84
+ if (opts.statusFilter) body.status_filter = opts.statusFilter;
85
+ if (opts.playbookStatusFilter)
86
+ body.playbook_status_filter = opts.playbookStatusFilter;
87
+ return post("get_agent_playbooks", body, opts.reflexioUrl);
88
+ },
89
+
90
+ /**
91
+ * GET /api/get_all_profiles — no per-user filter required. Returns all
92
+ * preferences across sessions, which is what the dashboard wants.
93
+ */
94
+ async getAllProfiles(
95
+ opts: Opts & { limit?: number; statusFilter?: string } = {},
96
+ ): Promise<{ user_profiles: UserProfile[] }> {
97
+ const qs = new URLSearchParams();
98
+ qs.set("limit", String(opts.limit ?? 200));
99
+ if (opts.statusFilter) qs.set("status_filter", opts.statusFilter);
100
+ return get(`get_all_profiles?${qs.toString()}`, opts.reflexioUrl);
101
+ },
102
+
103
+ /** GET /api/get_all_interactions — global, unfiltered. */
104
+ async getAllInteractions(
105
+ opts: Opts & { limit?: number } = {},
106
+ ): Promise<{ interactions: Interaction[] }> {
107
+ const qs = new URLSearchParams();
108
+ qs.set("limit", String(opts.limit ?? 100));
109
+ return get(`get_all_interactions?${qs.toString()}`, opts.reflexioUrl);
110
+ },
111
+
112
+ /** PUT /api/update_user_playbook — partial update of one project-specific skill. */
113
+ async updateUserPlaybook(
114
+ update: {
115
+ user_playbook_id: number;
116
+ playbook_name?: string | null;
117
+ content?: string | null;
118
+ trigger?: string | null;
119
+ rationale?: string | null;
120
+ },
121
+ reflexioUrl?: string,
122
+ ): Promise<Json> {
123
+ return request(
124
+ "update_user_playbook",
125
+ { method: "PUT", body: JSON.stringify(update) },
126
+ reflexioUrl,
127
+ );
128
+ },
129
+
130
+ /** PUT /api/update_agent_playbook — partial update of one shared skill. */
131
+ async updateAgentPlaybook(
132
+ update: {
133
+ agent_playbook_id: number;
134
+ playbook_name?: string | null;
135
+ content?: string | null;
136
+ trigger?: string | null;
137
+ rationale?: string | null;
138
+ playbook_status?: AgentPlaybookStatus | null;
139
+ },
140
+ reflexioUrl?: string,
141
+ ): Promise<Json> {
142
+ return request(
143
+ "update_agent_playbook",
144
+ { method: "PUT", body: JSON.stringify(update) },
145
+ reflexioUrl,
146
+ );
147
+ },
148
+
149
+ /** DELETE /api/delete_user_playbook — body carries the id. */
150
+ async deleteUserPlaybook(
151
+ userPlaybookId: number,
152
+ reflexioUrl?: string,
153
+ ): Promise<Json> {
154
+ return request(
155
+ "delete_user_playbook",
156
+ {
157
+ method: "DELETE",
158
+ body: JSON.stringify({ user_playbook_id: userPlaybookId }),
159
+ },
160
+ reflexioUrl,
161
+ );
162
+ },
163
+
164
+ /** DELETE /api/delete_agent_playbook — body carries the id. */
165
+ async deleteAgentPlaybook(
166
+ agentPlaybookId: number,
167
+ reflexioUrl?: string,
168
+ ): Promise<Json> {
169
+ return request(
170
+ "delete_agent_playbook",
171
+ {
172
+ method: "DELETE",
173
+ body: JSON.stringify({ agent_playbook_id: agentPlaybookId }),
174
+ },
175
+ reflexioUrl,
176
+ );
177
+ },
178
+
179
+ /** PUT /api/update_user_profile — partial update; content is the common field. */
180
+ async updateUserProfile(
181
+ update: {
182
+ user_id: string;
183
+ profile_id: string;
184
+ content?: string | null;
185
+ },
186
+ reflexioUrl?: string,
187
+ ): Promise<Json> {
188
+ return request(
189
+ "update_user_profile",
190
+ { method: "PUT", body: JSON.stringify(update) },
191
+ reflexioUrl,
192
+ );
193
+ },
194
+
195
+ /** DELETE /api/delete_profile — needs both user_id and profile_id. */
196
+ async deleteUserProfile(
197
+ params: { user_id: string; profile_id: string },
198
+ reflexioUrl?: string,
199
+ ): Promise<Json> {
200
+ return request(
201
+ "delete_profile",
202
+ { method: "DELETE", body: JSON.stringify(params) },
203
+ reflexioUrl,
204
+ );
205
+ },
206
+
207
+ /** DELETE /api/delete_all_interactions — org-wide purge. */
208
+ async deleteAllInteractions(reflexioUrl?: string): Promise<Json> {
209
+ return request(
210
+ "delete_all_interactions",
211
+ { method: "DELETE" },
212
+ reflexioUrl,
213
+ );
214
+ },
215
+
216
+ /** DELETE /api/delete_all_profiles — org-wide purge. */
217
+ async deleteAllProfiles(reflexioUrl?: string): Promise<Json> {
218
+ return request("delete_all_profiles", { method: "DELETE" }, reflexioUrl);
219
+ },
220
+
221
+ /** DELETE /api/delete_all_user_playbooks — org-wide purge. */
222
+ async deleteAllUserPlaybooks(reflexioUrl?: string): Promise<Json> {
223
+ return request(
224
+ "delete_all_user_playbooks",
225
+ { method: "DELETE" },
226
+ reflexioUrl,
227
+ );
228
+ },
229
+
230
+ /** DELETE /api/delete_all_agent_playbooks — org-wide shared skill purge. */
231
+ async deleteAllAgentPlaybooks(reflexioUrl?: string): Promise<Json> {
232
+ return request(
233
+ "delete_all_agent_playbooks",
234
+ { method: "DELETE" },
235
+ reflexioUrl,
236
+ );
237
+ },
238
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Normalize a user-pasted Reflexio URL down to just the origin (scheme +
3
+ * host + port). The dashboard proxy appends `/api/...` itself, so if the
4
+ * user pastes `http://localhost:8071/api` we must strip the path —
5
+ * otherwise FastAPI receives `/api/api/...` and returns 404.
6
+ *
7
+ * Returns null for empty input or anything the URL parser rejects.
8
+ */
9
+ export function originOnly(raw: string): string | null {
10
+ const candidate = raw.trim();
11
+ if (!candidate) return null;
12
+ try {
13
+ return new URL(candidate).origin;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }