@soederpop/luca 0.0.2
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/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Client,
|
|
3
|
+
type ClientOptions,
|
|
4
|
+
type ClientsInterface,
|
|
5
|
+
clients,
|
|
6
|
+
} from "@soederpop/luca/client";
|
|
7
|
+
import type { Container, ContainerContext } from "@soederpop/luca/container";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import {
|
|
10
|
+
ClientStateSchema,
|
|
11
|
+
ClientOptionsSchema,
|
|
12
|
+
ClientEventsSchema,
|
|
13
|
+
} from "@soederpop/luca/schemas/base.js";
|
|
14
|
+
import {
|
|
15
|
+
createClient,
|
|
16
|
+
type SupabaseClient as SupabaseSDKClient,
|
|
17
|
+
type SupabaseClientOptions as SupabaseSDKOptions,
|
|
18
|
+
type RealtimeChannel,
|
|
19
|
+
} from "@supabase/supabase-js";
|
|
20
|
+
|
|
21
|
+
declare module "@soederpop/luca/client" {
|
|
22
|
+
interface AvailableClients {
|
|
23
|
+
supabase: typeof SupabaseClient;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Schemas
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export const SupabaseClientOptionsSchema = ClientOptionsSchema.extend({
|
|
32
|
+
supabaseUrl: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe("The Supabase project URL (e.g. https://xyz.supabase.co)"),
|
|
35
|
+
supabaseKey: z
|
|
36
|
+
.string()
|
|
37
|
+
.describe("The Supabase anon or service-role key"),
|
|
38
|
+
clientOptions: z
|
|
39
|
+
.record(z.string(), z.any())
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(
|
|
42
|
+
"Pass-through options forwarded directly to the Supabase SDK createClient()"
|
|
43
|
+
),
|
|
44
|
+
}).describe("Options for creating a Supabase client");
|
|
45
|
+
|
|
46
|
+
export const SupabaseClientStateSchema = ClientStateSchema.extend({
|
|
47
|
+
authenticated: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.default(false)
|
|
50
|
+
.describe("Whether a user session is currently active"),
|
|
51
|
+
userId: z
|
|
52
|
+
.string()
|
|
53
|
+
.nullable()
|
|
54
|
+
.default(null)
|
|
55
|
+
.describe("The authenticated user's ID, if any"),
|
|
56
|
+
userEmail: z
|
|
57
|
+
.string()
|
|
58
|
+
.nullable()
|
|
59
|
+
.default(null)
|
|
60
|
+
.describe("The authenticated user's email, if any"),
|
|
61
|
+
realtimeChannels: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.default([])
|
|
64
|
+
.describe("Names of currently subscribed realtime channels"),
|
|
65
|
+
lastError: z
|
|
66
|
+
.string()
|
|
67
|
+
.nullable()
|
|
68
|
+
.default(null)
|
|
69
|
+
.describe("The most recent error message, if any"),
|
|
70
|
+
}).describe("Supabase client state");
|
|
71
|
+
|
|
72
|
+
export const SupabaseClientEventsSchema = ClientEventsSchema.extend({
|
|
73
|
+
authStateChange: z
|
|
74
|
+
.tuple([
|
|
75
|
+
z.string().describe("The auth event name (e.g. SIGNED_IN, SIGNED_OUT)"),
|
|
76
|
+
z.any().describe("The session object"),
|
|
77
|
+
])
|
|
78
|
+
.describe("Emitted when the auth state changes"),
|
|
79
|
+
realtimeMessage: z
|
|
80
|
+
.tuple([
|
|
81
|
+
z.string().describe("The channel name"),
|
|
82
|
+
z.any().describe("The payload"),
|
|
83
|
+
])
|
|
84
|
+
.describe("Emitted when a realtime message is received"),
|
|
85
|
+
realtimeStatus: z
|
|
86
|
+
.tuple([
|
|
87
|
+
z.string().describe("The channel name"),
|
|
88
|
+
z.string().describe("The status (e.g. SUBSCRIBED, CLOSED)"),
|
|
89
|
+
])
|
|
90
|
+
.describe("Emitted when a realtime channel status changes"),
|
|
91
|
+
error: z
|
|
92
|
+
.tuple([z.any().describe("The error object")])
|
|
93
|
+
.describe("Emitted on any Supabase error"),
|
|
94
|
+
}).describe("Supabase client events");
|
|
95
|
+
|
|
96
|
+
export type SupabaseClientOptions = z.infer<typeof SupabaseClientOptionsSchema>;
|
|
97
|
+
export type SupabaseClientState = z.infer<typeof SupabaseClientStateSchema>;
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Client
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Supabase client for the Luca container system.
|
|
105
|
+
*
|
|
106
|
+
* Wraps the official `@supabase/supabase-js` SDK and exposes it through Luca's
|
|
107
|
+
* typed state, events, and introspection system. The SDK is isomorphic so this
|
|
108
|
+
* single implementation works in both Node and browser containers.
|
|
109
|
+
*
|
|
110
|
+
* Use `client.sdk` for full SDK access, or use the convenience wrappers for
|
|
111
|
+
* common operations (auth, database queries, storage, edge functions, realtime).
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const supabase = container.client('supabase', {
|
|
116
|
+
* supabaseUrl: 'https://xyz.supabase.co',
|
|
117
|
+
* supabaseKey: 'your-anon-key',
|
|
118
|
+
* })
|
|
119
|
+
*
|
|
120
|
+
* // Query data
|
|
121
|
+
* const { data } = await supabase.from('users').select('*')
|
|
122
|
+
*
|
|
123
|
+
* // Auth
|
|
124
|
+
* await supabase.signInWithPassword('user@example.com', 'password')
|
|
125
|
+
*
|
|
126
|
+
* // Realtime
|
|
127
|
+
* supabase.subscribe('changes', 'users', (payload) => {
|
|
128
|
+
* console.log('Change:', payload)
|
|
129
|
+
* })
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export class SupabaseClient extends Client<
|
|
133
|
+
SupabaseClientState,
|
|
134
|
+
SupabaseClientOptions
|
|
135
|
+
> {
|
|
136
|
+
static override shortcut = "clients.supabase" as const;
|
|
137
|
+
static override description =
|
|
138
|
+
"Supabase client wrapping the official SDK with typed state, events, and realtime channel management";
|
|
139
|
+
|
|
140
|
+
static override stateSchema = SupabaseClientStateSchema;
|
|
141
|
+
static override optionsSchema = SupabaseClientOptionsSchema;
|
|
142
|
+
static override eventsSchema = SupabaseClientEventsSchema;
|
|
143
|
+
|
|
144
|
+
private _sdk!: SupabaseSDKClient<any, any>;
|
|
145
|
+
private _channels = new Map<string, RealtimeChannel>();
|
|
146
|
+
|
|
147
|
+
// @ts-ignore - required options (supabaseUrl, supabaseKey) widen beyond base ClientOptions
|
|
148
|
+
static attach(container: Container & ClientsInterface, options?: any) {
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
container.clients.register("supabase", SupabaseClient);
|
|
151
|
+
return container;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
constructor(options: SupabaseClientOptions, context: ContainerContext) {
|
|
155
|
+
super(options, context);
|
|
156
|
+
|
|
157
|
+
const sdkOptions = (options.clientOptions ?? {}) as SupabaseSDKOptions<string>;
|
|
158
|
+
this._sdk = createClient(options.supabaseUrl, options.supabaseKey, sdkOptions);
|
|
159
|
+
|
|
160
|
+
this._sdk.auth.onAuthStateChange((event, session) => {
|
|
161
|
+
const user = session?.user;
|
|
162
|
+
this.state.set("authenticated", !!session);
|
|
163
|
+
this.state.set("userId", user?.id ?? null);
|
|
164
|
+
this.state.set("userEmail", user?.email ?? null);
|
|
165
|
+
this.emit("authStateChange" as any, event, session);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.state.set("connected", true);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// SDK access
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
/** Returns the raw Supabase SDK client for full access to all SDK methods. */
|
|
176
|
+
get sdk(): SupabaseSDKClient<any, any> {
|
|
177
|
+
return this._sdk;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Database
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Start a query on a Postgres table or view.
|
|
186
|
+
* @param table - The table or view name to query
|
|
187
|
+
*/
|
|
188
|
+
from(table: string) {
|
|
189
|
+
return this._sdk.from(table);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Call a Postgres function (RPC).
|
|
194
|
+
* @param fn - The function name
|
|
195
|
+
* @param params - Arguments to pass to the function
|
|
196
|
+
* @param options - Optional settings (head, get, count)
|
|
197
|
+
*/
|
|
198
|
+
rpc(fn: string, params?: Record<string, unknown>, options?: { head?: boolean; get?: boolean; count?: "exact" | "planned" | "estimated" }) {
|
|
199
|
+
return this._sdk.rpc(fn, params, options);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Auth
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/** Sign in with email and password. */
|
|
207
|
+
async signInWithPassword(email: string, password: string) {
|
|
208
|
+
const result = await this._sdk.auth.signInWithPassword({ email, password });
|
|
209
|
+
if (result.error) {
|
|
210
|
+
this._setError(result.error.message);
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Create a new user account with email and password. */
|
|
216
|
+
async signUp(email: string, password: string) {
|
|
217
|
+
const result = await this._sdk.auth.signUp({ email, password });
|
|
218
|
+
if (result.error) {
|
|
219
|
+
this._setError(result.error.message);
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Sign the current user out. */
|
|
225
|
+
async signOut() {
|
|
226
|
+
const result = await this._sdk.auth.signOut();
|
|
227
|
+
if (result.error) {
|
|
228
|
+
this._setError(result.error.message);
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Get the current session, if any. */
|
|
234
|
+
async getSession() {
|
|
235
|
+
return this._sdk.auth.getSession();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Get the current user, if any. */
|
|
239
|
+
async getUser() {
|
|
240
|
+
return this._sdk.auth.getUser();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Storage
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
/** Returns the Supabase Storage client for managing buckets and files. */
|
|
248
|
+
get storage() {
|
|
249
|
+
return this._sdk.storage;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Edge Functions
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
/** Returns the Supabase Functions client. */
|
|
257
|
+
get functions() {
|
|
258
|
+
return this._sdk.functions;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Invoke a Supabase Edge Function by name. */
|
|
262
|
+
async invoke(name: string, body?: any) {
|
|
263
|
+
const result = await this._sdk.functions.invoke(name, { body });
|
|
264
|
+
if (result.error) {
|
|
265
|
+
this._setError(result.error.message);
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Realtime
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Subscribe to realtime changes on a Postgres table.
|
|
276
|
+
* @param channelName - A name for this subscription channel
|
|
277
|
+
* @param table - The table to listen to
|
|
278
|
+
* @param callback - Called with the payload on each change
|
|
279
|
+
* @param event - The event type to listen for (default: all changes)
|
|
280
|
+
*/
|
|
281
|
+
subscribe(
|
|
282
|
+
channelName: string,
|
|
283
|
+
table: string,
|
|
284
|
+
callback: (payload: any) => void,
|
|
285
|
+
event: "INSERT" | "UPDATE" | "DELETE" | "*" = "*"
|
|
286
|
+
): RealtimeChannel {
|
|
287
|
+
const channel = this._sdk
|
|
288
|
+
.channel(channelName)
|
|
289
|
+
.on(
|
|
290
|
+
"postgres_changes" as any,
|
|
291
|
+
{ event, schema: "public", table },
|
|
292
|
+
(payload: any) => {
|
|
293
|
+
this.emit("realtimeMessage" as any, channelName, payload);
|
|
294
|
+
callback(payload);
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
.subscribe((status: string) => {
|
|
298
|
+
this.emit("realtimeStatus" as any, channelName, status);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
this._channels.set(channelName, channel);
|
|
302
|
+
this._syncChannelState();
|
|
303
|
+
return channel;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Unsubscribe and remove a realtime channel by name.
|
|
308
|
+
* @param channelName - The channel name to remove
|
|
309
|
+
*/
|
|
310
|
+
async unsubscribe(channelName: string) {
|
|
311
|
+
const channel = this._channels.get(channelName);
|
|
312
|
+
if (channel) {
|
|
313
|
+
await this._sdk.removeChannel(channel);
|
|
314
|
+
this._channels.delete(channelName);
|
|
315
|
+
this._syncChannelState();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Unsubscribe and remove all realtime channels. */
|
|
320
|
+
async unsubscribeAll() {
|
|
321
|
+
await this._sdk.removeAllChannels();
|
|
322
|
+
this._channels.clear();
|
|
323
|
+
this._syncChannelState();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// Lifecycle
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Connect is a no-op since the Supabase SDK initializes on construction.
|
|
332
|
+
* The client is ready to use immediately after creation.
|
|
333
|
+
*/
|
|
334
|
+
override async connect() {
|
|
335
|
+
this.state.set("connected", true);
|
|
336
|
+
return this;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Disconnect by signing out and removing all realtime channels.
|
|
341
|
+
*/
|
|
342
|
+
async disconnect() {
|
|
343
|
+
await this.unsubscribeAll();
|
|
344
|
+
await this._sdk.auth.signOut();
|
|
345
|
+
this.state.set("connected", false);
|
|
346
|
+
this.state.set("authenticated", false);
|
|
347
|
+
this.state.set("userId", null);
|
|
348
|
+
this.state.set("userEmail", null);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// Internal
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
private _syncChannelState() {
|
|
356
|
+
this.state.set("realtimeChannels", Array.from(this._channels.keys()));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private _setError(message: string) {
|
|
360
|
+
this.state.set("lastError", message);
|
|
361
|
+
this.emit("error" as any, new Error(message));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// @ts-ignore
|
|
366
|
+
clients.register("supabase", SupabaseClient);
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Helper } from './helper.js'
|
|
2
|
+
import type { Container, ContainerContext } from './container.js'
|
|
3
|
+
import { Registry } from './registry.js'
|
|
4
|
+
import { CommandStateSchema, CommandOptionsSchema, CommandEventsSchema } from './schemas/base.js'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
|
|
8
|
+
export type CommandState = z.infer<typeof CommandStateSchema>
|
|
9
|
+
export type CommandOptions = z.infer<typeof CommandOptionsSchema>
|
|
10
|
+
|
|
11
|
+
export interface AvailableCommands {}
|
|
12
|
+
|
|
13
|
+
export type CommandFactory = <T extends keyof AvailableCommands>(
|
|
14
|
+
key: T,
|
|
15
|
+
options?: ConstructorParameters<AvailableCommands[T]>[0]
|
|
16
|
+
) => NonNullable<InstanceType<AvailableCommands[T]>>
|
|
17
|
+
|
|
18
|
+
export interface CommandsInterface {
|
|
19
|
+
commands: CommandsRegistry
|
|
20
|
+
command: CommandFactory
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CommandHandler<T = any> = (options: T, context: ContainerContext) => Promise<void>
|
|
24
|
+
|
|
25
|
+
export class Command<
|
|
26
|
+
T extends CommandState = CommandState,
|
|
27
|
+
K extends CommandOptions = CommandOptions
|
|
28
|
+
> extends Helper<T, K> {
|
|
29
|
+
static override shortcut = 'commands.base'
|
|
30
|
+
static override description = 'Base command'
|
|
31
|
+
static override stateSchema = CommandStateSchema
|
|
32
|
+
static override optionsSchema = CommandOptionsSchema
|
|
33
|
+
static override eventsSchema = CommandEventsSchema
|
|
34
|
+
|
|
35
|
+
static commandDescription: string = ''
|
|
36
|
+
static argsSchema: z.ZodType = CommandOptionsSchema
|
|
37
|
+
|
|
38
|
+
override get initialState(): T {
|
|
39
|
+
return ({ running: false } as unknown) as T
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
parseArgs(): any {
|
|
43
|
+
const schema = (this.constructor as typeof Command).argsSchema
|
|
44
|
+
return schema.parse(this.container.options)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async execute(): Promise<void> {
|
|
48
|
+
// override in subclass
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async run(): Promise<void> {
|
|
52
|
+
// Intercept --help before the command executes
|
|
53
|
+
if (this.container.argv.help) {
|
|
54
|
+
const { formatCommandHelp } = await import('./commands/help.js')
|
|
55
|
+
const ui = (this.container as any).feature('ui')
|
|
56
|
+
const name = (this.constructor as typeof Command).shortcut?.replace('commands.', '') || 'unknown'
|
|
57
|
+
console.log(formatCommandHelp(name, this.constructor, ui.colors))
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.state.set('running', true)
|
|
62
|
+
this.emit('started')
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await this.execute()
|
|
66
|
+
this.state.set('running', false)
|
|
67
|
+
this.state.set('exitCode', 0)
|
|
68
|
+
this.emit('completed', 0)
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
this.state.set('running', false)
|
|
71
|
+
this.state.set('exitCode', 1)
|
|
72
|
+
this.emit('failed', err)
|
|
73
|
+
throw err
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static attach(container: Container<any> & CommandsInterface) {
|
|
78
|
+
container.commands = commands
|
|
79
|
+
|
|
80
|
+
Object.assign(container, {
|
|
81
|
+
command<T extends keyof AvailableCommands>(
|
|
82
|
+
id: T,
|
|
83
|
+
options?: ConstructorParameters<AvailableCommands[T]>[0]
|
|
84
|
+
): NonNullable<InstanceType<AvailableCommands[T]>> {
|
|
85
|
+
const BaseClass = commands.lookup(id as string) as any
|
|
86
|
+
|
|
87
|
+
return container.createHelperInstance({
|
|
88
|
+
cache: helperCache,
|
|
89
|
+
type: 'command',
|
|
90
|
+
id: String(id),
|
|
91
|
+
BaseClass,
|
|
92
|
+
options,
|
|
93
|
+
fallbackName: String(id),
|
|
94
|
+
}) as NonNullable<InstanceType<AvailableCommands[T]>>
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
container.registerHelperType('commands', 'command')
|
|
99
|
+
return container
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class CommandsRegistry extends Registry<Command<any>> {
|
|
104
|
+
override scope = 'commands'
|
|
105
|
+
override baseClass = Command as any
|
|
106
|
+
|
|
107
|
+
registerHandler<T = any>(
|
|
108
|
+
name: string,
|
|
109
|
+
opts: {
|
|
110
|
+
description?: string
|
|
111
|
+
argsSchema?: z.ZodType
|
|
112
|
+
handler: CommandHandler<T>
|
|
113
|
+
},
|
|
114
|
+
) {
|
|
115
|
+
const handler = opts.handler
|
|
116
|
+
const argsSchema = opts.argsSchema || CommandOptionsSchema
|
|
117
|
+
const desc = opts.description || ''
|
|
118
|
+
|
|
119
|
+
const CommandClass = class extends Command {
|
|
120
|
+
static override shortcut = `commands.${name}` as const
|
|
121
|
+
static override description = desc
|
|
122
|
+
static override commandDescription = desc
|
|
123
|
+
static override optionsSchema = argsSchema as any
|
|
124
|
+
static override argsSchema = argsSchema
|
|
125
|
+
|
|
126
|
+
override async execute() {
|
|
127
|
+
await handler(this.parseArgs(), this.context)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Object.defineProperty(CommandClass, 'name', { value: `${name}Command` })
|
|
132
|
+
|
|
133
|
+
return this.register(name, CommandClass as any)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async discover(options: { directory: string }) {
|
|
137
|
+
const { Glob } = globalThis.Bun || (await import('bun'))
|
|
138
|
+
const glob = new Glob('*.ts')
|
|
139
|
+
|
|
140
|
+
for await (const file of glob.scan({ cwd: options.directory })) {
|
|
141
|
+
if (file === 'index.ts') continue
|
|
142
|
+
|
|
143
|
+
const mod = await import(join(options.directory, file))
|
|
144
|
+
const commandModule = mod.default || mod
|
|
145
|
+
|
|
146
|
+
// Support export-based command files (like endpoints).
|
|
147
|
+
// If the module exports a handler function, register it
|
|
148
|
+
// using the filename as the command name.
|
|
149
|
+
if (typeof commandModule.handler === 'function' && !this.has(file.replace(/\.ts$/, ''))) {
|
|
150
|
+
const name = file.replace(/\.ts$/, '')
|
|
151
|
+
this.registerHandler(name, {
|
|
152
|
+
description: commandModule.description || '',
|
|
153
|
+
argsSchema: commandModule.argsSchema,
|
|
154
|
+
handler: commandModule.handler,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const commands = new CommandsRegistry()
|
|
162
|
+
export const helperCache = new Map()
|
|
163
|
+
|
|
164
|
+
export default Command
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import * as readline from 'readline'
|
|
3
|
+
import { commands } from '../command'
|
|
4
|
+
import { CommandOptionsSchema } from '../schemas/base'
|
|
5
|
+
import type { ContainerContext } from '../container'
|
|
6
|
+
|
|
7
|
+
declare module '../command.js' {
|
|
8
|
+
interface AvailableCommands {
|
|
9
|
+
chat: ReturnType<typeof commands.registerHandler>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
14
|
+
model: z.string().optional().describe('Override the LLM model for the assistant'),
|
|
15
|
+
folder: z.string().default('assistants').describe('Directory containing assistant definitions'),
|
|
16
|
+
resume: z.string().optional().describe('Thread ID or conversation ID to resume'),
|
|
17
|
+
list: z.boolean().optional().describe('List recent conversations and exit'),
|
|
18
|
+
historyMode: z.enum(['lifecycle', 'daily', 'persistent', 'session']).optional().describe('Override history persistence mode'),
|
|
19
|
+
offRecord: z.boolean().optional().describe('Alias for --history-mode lifecycle (ephemeral, no persistence)'),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export default async function chat(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
23
|
+
const container = context.container as any
|
|
24
|
+
const ui = container.feature('ui')
|
|
25
|
+
|
|
26
|
+
const manager = container.feature('assistantsManager', { folder: options.folder })
|
|
27
|
+
manager.discover()
|
|
28
|
+
|
|
29
|
+
const entries = manager.list()
|
|
30
|
+
|
|
31
|
+
if (entries.length === 0) {
|
|
32
|
+
console.error(ui.colors.red('No assistants found.'))
|
|
33
|
+
console.error(ui.colors.dim(` Create an assistant directory in "${options.folder}/" with a CORE.md file.`))
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const requestedName = container.argv._[1] as string | undefined
|
|
38
|
+
let name: string
|
|
39
|
+
|
|
40
|
+
if (requestedName) {
|
|
41
|
+
const entry = manager.get(requestedName)
|
|
42
|
+
if (!entry) {
|
|
43
|
+
const available = entries.map((e: any) => e.name).join(', ')
|
|
44
|
+
console.error(ui.colors.red(`Assistant "${requestedName}" not found.`))
|
|
45
|
+
console.error(ui.colors.dim(` Available: ${available}`))
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
name = requestedName
|
|
49
|
+
} else if (entries.length === 1) {
|
|
50
|
+
name = entries[0].name
|
|
51
|
+
} else {
|
|
52
|
+
const answers = await ui.wizard([
|
|
53
|
+
{
|
|
54
|
+
type: 'list',
|
|
55
|
+
name: 'assistant',
|
|
56
|
+
message: 'Choose an assistant',
|
|
57
|
+
choices: entries.map((e: any) => ({ name: e.name, value: e.name })),
|
|
58
|
+
},
|
|
59
|
+
])
|
|
60
|
+
name = answers.assistant
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Resolve history mode: --off-record overrides everything to lifecycle
|
|
64
|
+
// CLI defaults to 'daily' for interactive persistence
|
|
65
|
+
const historyMode = options.offRecord
|
|
66
|
+
? 'lifecycle'
|
|
67
|
+
: (options.historyMode || 'daily')
|
|
68
|
+
|
|
69
|
+
const createOptions: Record<string, any> = { historyMode }
|
|
70
|
+
if (options.model) createOptions.model = options.model
|
|
71
|
+
|
|
72
|
+
const assistant = manager.create(name, createOptions)
|
|
73
|
+
|
|
74
|
+
// --list: show recent conversations and exit
|
|
75
|
+
if (options.list) {
|
|
76
|
+
const history = await assistant.listHistory({ limit: 20 })
|
|
77
|
+
if (history.length === 0) {
|
|
78
|
+
console.log(ui.colors.dim(' No saved conversations.'))
|
|
79
|
+
} else {
|
|
80
|
+
console.log()
|
|
81
|
+
console.log(ui.colors.dim(' Recent conversations:'))
|
|
82
|
+
console.log()
|
|
83
|
+
for (const meta of history) {
|
|
84
|
+
const date = new Date(meta.updatedAt).toLocaleString()
|
|
85
|
+
const msgs = ui.colors.dim(`(${meta.messageCount} messages)`)
|
|
86
|
+
console.log(` ${ui.colors.cyan(meta.thread)} ${msgs}`)
|
|
87
|
+
console.log(` ${ui.colors.dim(date)} - ${meta.title}`)
|
|
88
|
+
}
|
|
89
|
+
console.log()
|
|
90
|
+
console.log(ui.colors.dim(` Resume with: luca chat ${name} --resume <thread-id>`))
|
|
91
|
+
}
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --resume: set thread override before start
|
|
96
|
+
if (options.resume) {
|
|
97
|
+
assistant.resumeThread(options.resume)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let isFirstChunk = true
|
|
101
|
+
|
|
102
|
+
assistant.on('chunk', (text: string) => {
|
|
103
|
+
if (isFirstChunk) {
|
|
104
|
+
process.stdout.write('\n')
|
|
105
|
+
isFirstChunk = false
|
|
106
|
+
}
|
|
107
|
+
process.stdout.write(text)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
assistant.on('toolCall', (toolName: string, args: any) => {
|
|
111
|
+
const argsStr = JSON.stringify(args).slice(0, 120)
|
|
112
|
+
process.stdout.write(ui.colors.dim(`\n ⟳ ${toolName}`) + ui.colors.dim(`(${argsStr})\n`))
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
assistant.on('toolResult', (toolName: string, result: any) => {
|
|
116
|
+
const preview = typeof result === 'string' ? result.slice(0, 100) : JSON.stringify(result).slice(0, 100)
|
|
117
|
+
process.stdout.write(ui.colors.green(` ✓ ${toolName}`) + ui.colors.dim(` → ${preview}${preview.length >= 100 ? '…' : ''}\n`))
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
assistant.on('toolError', (toolName: string, error: any) => {
|
|
121
|
+
const msg = error?.message || String(error)
|
|
122
|
+
process.stdout.write(ui.colors.red(` ✗ ${toolName}: ${msg}\n`))
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
assistant.on('response', () => {
|
|
126
|
+
process.stdout.write('\n')
|
|
127
|
+
isFirstChunk = true
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Start the assistant (loads history if applicable)
|
|
131
|
+
await assistant.start()
|
|
132
|
+
|
|
133
|
+
const messageCount = assistant.messages?.length || 0
|
|
134
|
+
const isResuming = historyMode !== 'lifecycle' && messageCount > 1
|
|
135
|
+
|
|
136
|
+
const rl = readline.createInterface({
|
|
137
|
+
input: process.stdin,
|
|
138
|
+
output: process.stdout,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
function prompt(): Promise<string> {
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
rl.question(ui.colors.dim(`\n${name} > `), (answer: string) => resolve(answer.trim()))
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log()
|
|
148
|
+
if (isResuming) {
|
|
149
|
+
console.log(ui.colors.dim(` Resuming conversation with ${ui.colors.cyan(name)} (${messageCount} messages). Type .exit to quit.`))
|
|
150
|
+
} else {
|
|
151
|
+
console.log(ui.colors.dim(` Chatting with ${ui.colors.cyan(name)}. Type .exit to quit.`))
|
|
152
|
+
}
|
|
153
|
+
if (historyMode !== 'lifecycle') {
|
|
154
|
+
console.log(ui.colors.dim(` Mode: ${historyMode}`))
|
|
155
|
+
}
|
|
156
|
+
console.log()
|
|
157
|
+
|
|
158
|
+
while (true) {
|
|
159
|
+
const question = await prompt()
|
|
160
|
+
|
|
161
|
+
if (!question) continue
|
|
162
|
+
if (question === '.exit') break
|
|
163
|
+
|
|
164
|
+
await assistant.ask(question)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
rl.close()
|
|
168
|
+
|
|
169
|
+
// Show resume instruction for non-lifecycle modes
|
|
170
|
+
if (historyMode !== 'lifecycle' && assistant.currentThreadId) {
|
|
171
|
+
console.log()
|
|
172
|
+
console.log(ui.colors.dim(` Session saved. To resume this conversation:`))
|
|
173
|
+
console.log(ui.colors.dim(` luca chat ${name} --resume ${assistant.currentThreadId}`))
|
|
174
|
+
console.log()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
commands.registerHandler('chat', {
|
|
179
|
+
description: 'Start an interactive chat session with a local assistant',
|
|
180
|
+
argsSchema,
|
|
181
|
+
handler: chat,
|
|
182
|
+
})
|