@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,391 @@
|
|
|
1
|
+
import container from '@soederpop/luca/node'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
|
|
4
|
+
const { ui, fs, argv } = container
|
|
5
|
+
|
|
6
|
+
type HelperType = 'feature' | 'client' | 'server' | 'endpoint'
|
|
7
|
+
|
|
8
|
+
interface ScaffoldAnswers {
|
|
9
|
+
type: HelperType
|
|
10
|
+
name: string
|
|
11
|
+
description: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const toPascalCase = (str: string) =>
|
|
15
|
+
str
|
|
16
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
|
|
17
|
+
.replace(/^(.)/, (_, c) => c.toUpperCase())
|
|
18
|
+
|
|
19
|
+
const toCamelCase = (str: string) => {
|
|
20
|
+
const pascal = toPascalCase(str)
|
|
21
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toKebabCase = (str: string) =>
|
|
25
|
+
str
|
|
26
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
27
|
+
.replace(/[\s_]+/g, '-')
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
|
|
30
|
+
function generateFeature(name: string, description: string): string {
|
|
31
|
+
const className = toPascalCase(name)
|
|
32
|
+
const shortcut = toCamelCase(name)
|
|
33
|
+
|
|
34
|
+
return ui.endent`
|
|
35
|
+
import { z } from 'zod'
|
|
36
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
37
|
+
import { Feature, features } from '../feature.js'
|
|
38
|
+
|
|
39
|
+
export const ${className}StateSchema = FeatureStateSchema.extend({
|
|
40
|
+
// TODO: add state properties
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const ${className}OptionsSchema = FeatureOptionsSchema.extend({
|
|
44
|
+
// TODO: add option properties
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export type ${className}State = z.infer<typeof ${className}StateSchema>
|
|
48
|
+
export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* ${description}
|
|
52
|
+
*
|
|
53
|
+
* @extends Feature
|
|
54
|
+
*/
|
|
55
|
+
export class ${className} extends Feature<${className}State, ${className}Options> {
|
|
56
|
+
static override shortcut = 'features.${shortcut}' as const
|
|
57
|
+
static override stateSchema = ${className}StateSchema
|
|
58
|
+
static override optionsSchema = ${className}OptionsSchema
|
|
59
|
+
|
|
60
|
+
override get initialState(): ${className}State {
|
|
61
|
+
return {
|
|
62
|
+
...super.initialState,
|
|
63
|
+
// TODO: add default state values
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* TODO: describe this method.
|
|
69
|
+
*
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async doSomething(): Promise<void> {
|
|
73
|
+
// TODO: implement
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default features.register('${shortcut}', ${className})
|
|
78
|
+
`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function generateClient(name: string, description: string): string {
|
|
82
|
+
const className = toPascalCase(name) + 'Client'
|
|
83
|
+
const registryKey = toCamelCase(name)
|
|
84
|
+
|
|
85
|
+
return ui.endent`
|
|
86
|
+
import { z } from 'zod'
|
|
87
|
+
import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca/schemas/base.js'
|
|
88
|
+
import {
|
|
89
|
+
type ClientsInterface,
|
|
90
|
+
clients,
|
|
91
|
+
RestClient,
|
|
92
|
+
} from '@soederpop/luca/client'
|
|
93
|
+
import { type ContainerContext } from '@soederpop/luca/container'
|
|
94
|
+
|
|
95
|
+
declare module '@soederpop/luca/client' {
|
|
96
|
+
interface AvailableClients {
|
|
97
|
+
${registryKey}: typeof ${className}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const ${className}StateSchema = ClientStateSchema.extend({
|
|
102
|
+
// TODO: add state properties
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
export const ${className}OptionsSchema = ClientOptionsSchema.extend({
|
|
106
|
+
// TODO: add option properties
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
export type ${className}State = z.infer<typeof ${className}StateSchema>
|
|
110
|
+
export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ${description}
|
|
114
|
+
*
|
|
115
|
+
* @extends RestClient
|
|
116
|
+
*/
|
|
117
|
+
export class ${className}<T extends ${className}State = ${className}State> extends RestClient<T> {
|
|
118
|
+
static override shortcut = 'clients.${registryKey}' as const
|
|
119
|
+
static override stateSchema = ${className}StateSchema
|
|
120
|
+
static override optionsSchema = ${className}OptionsSchema
|
|
121
|
+
|
|
122
|
+
constructor(options: ${className}Options, context: ContainerContext) {
|
|
123
|
+
options = {
|
|
124
|
+
...options,
|
|
125
|
+
baseURL: options.baseURL || 'https://api.example.com',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
super(options as any, context)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* TODO: describe this method.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<any>}
|
|
135
|
+
*/
|
|
136
|
+
async fetchSomething(): Promise<any> {
|
|
137
|
+
const response = await this.get('/endpoint')
|
|
138
|
+
return response.data
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default clients.register('${registryKey}', ${className})
|
|
143
|
+
`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function generateServer(name: string, description: string): string {
|
|
147
|
+
const className = toPascalCase(name) + 'Server'
|
|
148
|
+
const registryKey = toCamelCase(name)
|
|
149
|
+
|
|
150
|
+
return ui.endent`
|
|
151
|
+
import { z } from 'zod'
|
|
152
|
+
import { ServerStateSchema, ServerOptionsSchema } from '../schemas/base.js'
|
|
153
|
+
import type { NodeContainer } from '../node/container.js'
|
|
154
|
+
import { servers, Server, type ServersInterface, type ServerState, type StartOptions } from '../server.js'
|
|
155
|
+
|
|
156
|
+
declare module '../server' {
|
|
157
|
+
interface AvailableServers {
|
|
158
|
+
${registryKey}: typeof ${className}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const ${className}OptionsSchema = ServerOptionsSchema.extend({
|
|
163
|
+
// TODO: add option properties
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ${description}
|
|
170
|
+
*
|
|
171
|
+
* @extends Server
|
|
172
|
+
*/
|
|
173
|
+
export class ${className}<T extends ServerState = ServerState, K extends ${className}Options = ${className}Options> extends Server<T, K> {
|
|
174
|
+
static override shortcut = 'servers.${registryKey}' as const
|
|
175
|
+
static override optionsSchema = ${className}OptionsSchema
|
|
176
|
+
|
|
177
|
+
override async start(options?: StartOptions) {
|
|
178
|
+
if (this.isListening) {
|
|
179
|
+
return this
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// TODO: implement server start logic
|
|
183
|
+
|
|
184
|
+
this.state.set('listening', true)
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override async stop() {
|
|
189
|
+
if (this.isStopped) {
|
|
190
|
+
return this
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// TODO: implement server stop logic
|
|
194
|
+
|
|
195
|
+
this.state.set('stopped', true)
|
|
196
|
+
return this
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
override async configure() {
|
|
200
|
+
// TODO: implement server configuration
|
|
201
|
+
|
|
202
|
+
this.state.set('configured', true)
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default servers.register('${registryKey}', ${className})
|
|
208
|
+
`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function generateEndpoint(name: string, description: string): string {
|
|
212
|
+
const kebab = toKebabCase(name)
|
|
213
|
+
const path = `/${kebab}`
|
|
214
|
+
|
|
215
|
+
return ui.endent`
|
|
216
|
+
import { z } from 'zod'
|
|
217
|
+
import type { EndpointContext } from '../../endpoint.js'
|
|
218
|
+
|
|
219
|
+
export const path = '${path}'
|
|
220
|
+
export const description = '${description}'
|
|
221
|
+
export const tags = ['${kebab}']
|
|
222
|
+
|
|
223
|
+
export const postSchema = z.object({
|
|
224
|
+
// TODO: define your parameters
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
export async function post(parameters: z.infer<typeof postSchema>, ctx: EndpointContext) {
|
|
228
|
+
const { container } = ctx
|
|
229
|
+
// TODO: implement
|
|
230
|
+
return { ok: true }
|
|
231
|
+
}
|
|
232
|
+
`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const generators: Record<HelperType, (name: string, desc: string) => string> = {
|
|
236
|
+
feature: generateFeature,
|
|
237
|
+
client: generateClient,
|
|
238
|
+
server: generateServer,
|
|
239
|
+
endpoint: generateEndpoint,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const outputPaths: Record<HelperType, (name: string) => string> = {
|
|
243
|
+
feature: (name) => join('src', 'node', 'features', `${toKebabCase(name)}.ts`),
|
|
244
|
+
client: (name) => join('src', 'clients', toKebabCase(name), 'index.ts'),
|
|
245
|
+
server: (name) => join('src', 'servers', `${toKebabCase(name)}.ts`),
|
|
246
|
+
endpoint: (name) => join('src', 'agi', 'endpoints', `${toKebabCase(name)}.ts`),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function main() {
|
|
250
|
+
console.log(ui.banner('Scaffold', { font: 'Small', colors: ['cyan', 'blue', 'magenta'] }))
|
|
251
|
+
console.log()
|
|
252
|
+
|
|
253
|
+
// Support passing type and name as positional args: bun scripts/scaffold.ts feature myThing
|
|
254
|
+
const positionalType = argv._?.[0] as HelperType | undefined
|
|
255
|
+
const positionalName = argv._?.[1] as string | undefined
|
|
256
|
+
|
|
257
|
+
const answers = await ui.wizard([
|
|
258
|
+
{
|
|
259
|
+
type: 'list',
|
|
260
|
+
name: 'type',
|
|
261
|
+
message: 'What do you want to scaffold?',
|
|
262
|
+
choices: [
|
|
263
|
+
{ name: 'Feature - registered in FeaturesRegistry, enable/disable lifecycle', value: 'feature' },
|
|
264
|
+
{ name: 'Client - registered in ClientsRegistry, connect/configure lifecycle', value: 'client' },
|
|
265
|
+
{ name: 'Server - registered in ServersRegistry, start/stop/configure lifecycle', value: 'server' },
|
|
266
|
+
{ name: 'Endpoint - file-based HTTP endpoint, Remix-like DX', value: 'endpoint' },
|
|
267
|
+
],
|
|
268
|
+
when: () => !positionalType,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: 'input',
|
|
272
|
+
name: 'name',
|
|
273
|
+
message: 'Name (camelCase or kebab-case):',
|
|
274
|
+
validate: (input: string) => (input.trim().length > 0 ? true : 'Name is required'),
|
|
275
|
+
when: () => !positionalName,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: 'input',
|
|
279
|
+
name: 'description',
|
|
280
|
+
message: 'Short description:',
|
|
281
|
+
default: 'TODO: describe this helper.',
|
|
282
|
+
},
|
|
283
|
+
]) as Partial<ScaffoldAnswers>
|
|
284
|
+
|
|
285
|
+
const type = positionalType || answers.type!
|
|
286
|
+
const name = positionalName || answers.name!
|
|
287
|
+
const description = answers.description || 'TODO: describe this helper.'
|
|
288
|
+
|
|
289
|
+
if (!['feature', 'client', 'server', 'endpoint'].includes(type)) {
|
|
290
|
+
ui.print.red(`Invalid type "${type}". Must be feature, client, server, or endpoint.`)
|
|
291
|
+
process.exit(1)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const code = generators[type](name, description)
|
|
295
|
+
const relativePath = outputPaths[type](name)
|
|
296
|
+
const fullPath = container.paths.resolve(relativePath)
|
|
297
|
+
|
|
298
|
+
if (await fs.existsAsync(fullPath)) {
|
|
299
|
+
ui.print.red(`File already exists: ${relativePath}`)
|
|
300
|
+
process.exit(1)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const dir = container.paths.dirname(fullPath)
|
|
304
|
+
await fs.ensureFolder(dir)
|
|
305
|
+
await fs.writeFileAsync(fullPath, code + '\n')
|
|
306
|
+
|
|
307
|
+
console.log()
|
|
308
|
+
ui.print.green(`Created ${relativePath}`)
|
|
309
|
+
console.log()
|
|
310
|
+
|
|
311
|
+
if (type === 'feature') {
|
|
312
|
+
const className = toPascalCase(name)
|
|
313
|
+
const shortcut = toCamelCase(name)
|
|
314
|
+
const kebab = toKebabCase(name)
|
|
315
|
+
|
|
316
|
+
ui.print.yellow('Next steps for your new Feature:')
|
|
317
|
+
console.log()
|
|
318
|
+
console.log(ui.endent`
|
|
319
|
+
1. Add the side-effect import in src/node/container.ts:
|
|
320
|
+
${ui.colors.cyan(`import './features/${kebab}'`)}
|
|
321
|
+
|
|
322
|
+
2. Add the type import:
|
|
323
|
+
${ui.colors.cyan(`import type { ${className} } from './features/${kebab}'`)}
|
|
324
|
+
|
|
325
|
+
3. Add to NodeFeatures interface:
|
|
326
|
+
${ui.colors.cyan(`${shortcut}: typeof ${className}`)}
|
|
327
|
+
|
|
328
|
+
4. Add to the export block:
|
|
329
|
+
${ui.colors.cyan(`type ${className}`)}
|
|
330
|
+
|
|
331
|
+
5. Optionally add as a property on NodeContainer:
|
|
332
|
+
${ui.colors.cyan(`${shortcut}?: ${className}`)}
|
|
333
|
+
`)
|
|
334
|
+
} else if (type === 'client') {
|
|
335
|
+
const className = toPascalCase(name) + 'Client'
|
|
336
|
+
const kebab = toKebabCase(name)
|
|
337
|
+
|
|
338
|
+
ui.print.yellow('Next steps for your new Client:')
|
|
339
|
+
console.log()
|
|
340
|
+
console.log(ui.endent`
|
|
341
|
+
1. Update the baseURL in src/clients/${kebab}/index.ts
|
|
342
|
+
|
|
343
|
+
2. Import and register in your script or container setup:
|
|
344
|
+
${ui.colors.cyan(`import '@/clients/${kebab}'`)}
|
|
345
|
+
|
|
346
|
+
3. Use it:
|
|
347
|
+
${ui.colors.cyan(`const client = container.client('${toCamelCase(name)}', { ... })`)}
|
|
348
|
+
`)
|
|
349
|
+
} else if (type === 'server') {
|
|
350
|
+
const className = toPascalCase(name) + 'Server'
|
|
351
|
+
const kebab = toKebabCase(name)
|
|
352
|
+
|
|
353
|
+
ui.print.yellow('Next steps for your new Server:')
|
|
354
|
+
console.log()
|
|
355
|
+
console.log(ui.endent`
|
|
356
|
+
1. Import and register the server in src/server.ts:
|
|
357
|
+
${ui.colors.cyan(`import { ${className} } from './servers/${kebab}.js'`)}
|
|
358
|
+
${ui.colors.cyan(`servers.register('${toCamelCase(name)}', ${className})`)}
|
|
359
|
+
|
|
360
|
+
2. Use it:
|
|
361
|
+
${ui.colors.cyan(`const server = container.server('${toCamelCase(name)}', { port: 3000 })`)}
|
|
362
|
+
${ui.colors.cyan(`await server.configure()`)}
|
|
363
|
+
${ui.colors.cyan(`await server.start()`)}
|
|
364
|
+
`)
|
|
365
|
+
} else if (type === 'endpoint') {
|
|
366
|
+
const kebab = toKebabCase(name)
|
|
367
|
+
|
|
368
|
+
ui.print.yellow('Next steps for your new Endpoint:')
|
|
369
|
+
console.log()
|
|
370
|
+
console.log(ui.endent`
|
|
371
|
+
1. Edit the endpoint file at src/agi/endpoints/${kebab}.ts
|
|
372
|
+
|
|
373
|
+
2. Define your Zod schemas and handler functions (get, post, put, patch, delete)
|
|
374
|
+
|
|
375
|
+
3. It will be auto-loaded when useEndpoints() scans the endpoints directory:
|
|
376
|
+
${ui.colors.cyan(`await expressServer.useEndpoints('src/agi/endpoints')`)}
|
|
377
|
+
|
|
378
|
+
4. Or load it manually:
|
|
379
|
+
${ui.colors.cyan(`const ep = new Endpoint({ path: '/${kebab}', filePath: '...' }, container.context)`)}
|
|
380
|
+
${ui.colors.cyan(`await ep.load()`)}
|
|
381
|
+
${ui.colors.cyan(`expressServer.useEndpoint(ep)`)}
|
|
382
|
+
`)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
main().catch((err) => {
|
|
389
|
+
ui.print.red(err.message)
|
|
390
|
+
process.exit(1)
|
|
391
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import container from '@soederpop/luca/agi'
|
|
2
|
+
|
|
3
|
+
const fs = container.feature('fs')
|
|
4
|
+
|
|
5
|
+
const data = await fs.readJson('package.json') as { name: string, version: string }
|
|
6
|
+
|
|
7
|
+
console.log(data.name)
|
|
8
|
+
|
|
9
|
+
const info = container.feature('git').branch
|
|
10
|
+
|
|
11
|
+
console.log(info)
|
|
12
|
+
|
|
13
|
+
const opener = container.feature('opener')
|
|
14
|
+
|
|
15
|
+
await opener.open('https://www.tiktok.com')
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { NodeContainer } from '../src/node/container'
|
|
2
|
+
|
|
3
|
+
const container = new NodeContainer({ cwd: process.cwd() })
|
|
4
|
+
const listener = container.feature('launcherAppCommandListener', {
|
|
5
|
+
autoListen: true,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const windowManager = container.feature('windowManager')
|
|
9
|
+
|
|
10
|
+
console.log('Listening on:', listener.state.get('socketPath'))
|
|
11
|
+
console.log('Waiting for native app to connect...\n')
|
|
12
|
+
|
|
13
|
+
listener.enable()
|
|
14
|
+
|
|
15
|
+
listener.on('clientConnected', () => {
|
|
16
|
+
console.log('[connected] Native app connected')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
listener.on('clientDisconnected', () => {
|
|
20
|
+
console.log('[disconnected] Native app disconnected')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
listener.on('command', async (cmd) => {
|
|
24
|
+
console.log(`[command] "${cmd.text}" (source: ${cmd.source}, id: ${cmd.id})`)
|
|
25
|
+
|
|
26
|
+
const normalizedText = String(cmd.text).toLowerCase()
|
|
27
|
+
|
|
28
|
+
if (normalizedText.includes('terminal')) {
|
|
29
|
+
await container.sleep(1000)
|
|
30
|
+
cmd.ack('Sheeeeeeeit. I got you fam!')
|
|
31
|
+
await container.sleep(1000)
|
|
32
|
+
console.log('Spawning terminal')
|
|
33
|
+
const result = await windowManager.spawnTTY({
|
|
34
|
+
command: '/Users/jon/.bun/bin/bun',
|
|
35
|
+
args: ['run', '/Users/jon/@luca/src/cli/cli.ts', 'console'],
|
|
36
|
+
cwd: '/Users/jon/@soederpop',
|
|
37
|
+
title: 'The Console',
|
|
38
|
+
cols: 120,
|
|
39
|
+
rows: 40,
|
|
40
|
+
width: 1000,
|
|
41
|
+
height: 700,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await container.sleep(4000)
|
|
45
|
+
|
|
46
|
+
cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Check that shit out playboy. Fuckin terminal output.' })
|
|
47
|
+
|
|
48
|
+
return
|
|
49
|
+
} else if (normalizedText.includes('code')) {
|
|
50
|
+
await container.sleep(1000)
|
|
51
|
+
cmd.ack('Real talk, I feel for the homies we told to learn to code. Now that claude is on this shit?? I mean.')
|
|
52
|
+
await container.sleep(1000)
|
|
53
|
+
console.log('Spawning terminal')
|
|
54
|
+
const result = await windowManager.spawnTTY({
|
|
55
|
+
command: '/Users/jon/.bun/bin/claude',
|
|
56
|
+
cwd: '/Users/jon/@soederpop',
|
|
57
|
+
title: 'Claude',
|
|
58
|
+
cols: 120,
|
|
59
|
+
rows: 80,
|
|
60
|
+
width: 1000,
|
|
61
|
+
height: 700,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
await container.sleep(4000)
|
|
65
|
+
|
|
66
|
+
cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Good luck with claude bro.' })
|
|
67
|
+
|
|
68
|
+
return
|
|
69
|
+
} else if (normalizedText.includes('web') || normalizedText.includes('browser')) {
|
|
70
|
+
cmd.ack('Yo.... Fuckin check this out, twin.')
|
|
71
|
+
await container.sleep(1000)
|
|
72
|
+
const result = await windowManager.spawn({
|
|
73
|
+
url: 'https://google.com',
|
|
74
|
+
width: 1000,
|
|
75
|
+
height: 700,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
console.log('Web browser spawned', result)
|
|
79
|
+
|
|
80
|
+
await container.sleep(3000)
|
|
81
|
+
cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Motherfucker I can even launch web browsers' })
|
|
82
|
+
return
|
|
83
|
+
} else if (normalizedText.includes('write')) {
|
|
84
|
+
await container.sleep(1000)
|
|
85
|
+
cmd.ack('Aight. Sheeeit. We got a real fuckin earnest hemmingway up in here.')
|
|
86
|
+
const result = await windowManager.spawn({
|
|
87
|
+
url: 'http://localhost:3080',
|
|
88
|
+
width: 1200,
|
|
89
|
+
height: 900,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await container.sleep(4000)
|
|
93
|
+
cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Let the boy COOK' })
|
|
94
|
+
return
|
|
95
|
+
} else if (normalizedText.includes('track')) {
|
|
96
|
+
await container.sleep(1000)
|
|
97
|
+
cmd.ack('Better believe it. Aint nobody hiding from your boy.')
|
|
98
|
+
|
|
99
|
+
container.proc.spawnAndCapture('luca', ['serve', '--force', '--port', '3969', '--no-open'], {
|
|
100
|
+
cwd: '/Users/jon/@soederpop/playground/enemy-tracker'
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await container.sleep(4000)
|
|
104
|
+
|
|
105
|
+
const result = await windowManager.spawn({
|
|
106
|
+
url: 'http://localhost:3969',
|
|
107
|
+
width: 1400,
|
|
108
|
+
height: 1000,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Get em dawg. Me and the homies are ready.' })
|
|
112
|
+
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await container.sleep(4000)
|
|
117
|
+
cmd.ack('Look unc. I dont know the fuck you talmbout.')
|
|
118
|
+
cmd.finish({ result: { action: 'unknown' }})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
listener.on('message', (msg) => {
|
|
122
|
+
console.log('[message]', JSON.stringify(msg))
|
|
123
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NodeContainer } from '../src/node/container'
|
|
2
|
+
|
|
3
|
+
const args = new Set(process.argv.slice(2))
|
|
4
|
+
const socketArg = process.argv.slice(2).find((arg) => arg.startsWith('--socket='))
|
|
5
|
+
const socketPath = socketArg?.slice('--socket='.length)
|
|
6
|
+
const shouldSpawnTTY = args.has('--spawn-tty')
|
|
7
|
+
const shouldSpawnWindow = args.has('--spawn-window')
|
|
8
|
+
|
|
9
|
+
const container = new NodeContainer({ cwd: process.cwd() })
|
|
10
|
+
const wm = socketPath
|
|
11
|
+
? container.feature('windowManager', { socketPath })
|
|
12
|
+
: container.feature('windowManager')
|
|
13
|
+
|
|
14
|
+
wm.listen()
|
|
15
|
+
|
|
16
|
+
const logCount = () => {
|
|
17
|
+
console.log(`[state] windowCount=${wm.state.get('windowCount')}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
wm.on('clientConnected', () => {
|
|
21
|
+
console.log('[clientConnected] native launcher connected')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
wm.on('clientDisconnected', () => {
|
|
25
|
+
console.log('[clientDisconnected] native launcher disconnected')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
wm.on('windowAck', (msg) => {
|
|
29
|
+
console.log('[windowAck]', JSON.stringify(msg))
|
|
30
|
+
logCount()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
wm.on('windowClosed', (msg) => {
|
|
34
|
+
console.log('[windowClosed]', JSON.stringify(msg))
|
|
35
|
+
logCount()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
wm.on('terminalExited', (msg) => {
|
|
39
|
+
console.log('[terminalExited]', JSON.stringify(msg))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
wm.on('message', (msg) => {
|
|
43
|
+
if (msg?.type === 'windowAck' || msg?.type === 'windowClosed' || msg?.type === 'terminalExited') {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
console.log('[message]', JSON.stringify(msg))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let spawned = false
|
|
50
|
+
wm.on('clientConnected', async () => {
|
|
51
|
+
if (spawned) return
|
|
52
|
+
spawned = true
|
|
53
|
+
|
|
54
|
+
if (shouldSpawnWindow) {
|
|
55
|
+
const opened = await wm.spawn({
|
|
56
|
+
url: 'https://example.com',
|
|
57
|
+
width: 900,
|
|
58
|
+
height: 620,
|
|
59
|
+
})
|
|
60
|
+
console.log('[spawn-window]', opened)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (shouldSpawnTTY) {
|
|
64
|
+
const tty = await wm.spawnTTY({
|
|
65
|
+
command: 'zsh',
|
|
66
|
+
args: ['-lc', 'echo "window-manager lifecycle demo"; sleep 2; exit 7'],
|
|
67
|
+
title: 'WM Lifecycle Demo',
|
|
68
|
+
cols: 110,
|
|
69
|
+
rows: 32,
|
|
70
|
+
width: 960,
|
|
71
|
+
height: 620,
|
|
72
|
+
cwd: process.cwd(),
|
|
73
|
+
})
|
|
74
|
+
console.log('[spawn-tty]', tty)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
await container.sleep(150)
|
|
79
|
+
|
|
80
|
+
console.log(`[ready] listening=${wm.isListening} socket=${wm.state.get('socketPath') ?? '<unset>'}`)
|
|
81
|
+
if (!wm.isListening) {
|
|
82
|
+
console.log(`[error] ${wm.state.get('lastError') ?? 'windowManager failed to bind socket'}`)
|
|
83
|
+
console.log('[hint] try: bun run scripts/test-window-manager-lifecycle.ts --socket=/tmp/ipc-window.sock')
|
|
84
|
+
}
|
|
85
|
+
console.log('[usage] bun run scripts/test-window-manager-lifecycle.ts [--spawn-tty] [--spawn-window] [--socket=/path/to/ipc-window.sock]')
|
|
86
|
+
console.log('[ready] waiting for native app connection...')
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NodeContainer } from '../src/node/container'
|
|
2
|
+
|
|
3
|
+
const container = new NodeContainer({ cwd: process.cwd() })
|
|
4
|
+
const wm = container.feature('windowManager')
|
|
5
|
+
|
|
6
|
+
await wm.listen()
|
|
7
|
+
console.log('Listening:', wm.isListening)
|
|
8
|
+
console.log('Socket path:', wm.state.get('socketPath'))
|
|
9
|
+
|
|
10
|
+
wm.on('clientConnected', () => {
|
|
11
|
+
console.log('App connected')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
wm.on('message', (msg) => {
|
|
15
|
+
console.log('Message from app:', msg)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
wm.on('clientDisconnected', () => {
|
|
19
|
+
console.log('App disconnected')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Wait for the app, then spawn a TTY running luca serve in the writing assistant playground
|
|
23
|
+
wm.on('clientConnected', async () => {
|
|
24
|
+
const result = await wm.spawnTTY({
|
|
25
|
+
command: 'luca',
|
|
26
|
+
args: ['serve', '--any-port'],
|
|
27
|
+
cwd: '/Users/jon/@soederpop/playground/writing-assistant',
|
|
28
|
+
title: 'Writing Assistant Server',
|
|
29
|
+
cols: 120,
|
|
30
|
+
rows: 40,
|
|
31
|
+
width: 1000,
|
|
32
|
+
height: 700,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
console.log('Spawned TTY:', result)
|
|
36
|
+
|
|
37
|
+
if (result.windowId) {
|
|
38
|
+
console.log(`Window ID: ${result.windowId}`)
|
|
39
|
+
console.log(`PID: ${result.pid}`)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
console.log('Waiting for native app to connect...')
|