openfox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +68 -0
  2. package/dist/branch.api-WDHKZJS6.js +48 -0
  3. package/dist/branch.api-WDHKZJS6.js.map +1 -0
  4. package/dist/chunk-26NRL3HX.js +116 -0
  5. package/dist/chunk-26NRL3HX.js.map +1 -0
  6. package/dist/chunk-43SWNGYW.js +273 -0
  7. package/dist/chunk-43SWNGYW.js.map +1 -0
  8. package/dist/chunk-4Z2OZ6NX.js +132 -0
  9. package/dist/chunk-4Z2OZ6NX.js.map +1 -0
  10. package/dist/chunk-6MOGZMGX.js +218 -0
  11. package/dist/chunk-6MOGZMGX.js.map +1 -0
  12. package/dist/chunk-AF7XS4OI.js +55 -0
  13. package/dist/chunk-AF7XS4OI.js.map +1 -0
  14. package/dist/chunk-EI46G5WO.js +138 -0
  15. package/dist/chunk-EI46G5WO.js.map +1 -0
  16. package/dist/chunk-FF6Z2MC3.js +844 -0
  17. package/dist/chunk-FF6Z2MC3.js.map +1 -0
  18. package/dist/chunk-IKAWIOXM.js +5817 -0
  19. package/dist/chunk-IKAWIOXM.js.map +1 -0
  20. package/dist/chunk-IVHCPQO2.js +29 -0
  21. package/dist/chunk-IVHCPQO2.js.map +1 -0
  22. package/dist/chunk-MTX5Y3V3.js +46 -0
  23. package/dist/chunk-MTX5Y3V3.js.map +1 -0
  24. package/dist/chunk-N2XYX4ZA.js +83 -0
  25. package/dist/chunk-N2XYX4ZA.js.map +1 -0
  26. package/dist/chunk-QY7BMXWT.js +124 -0
  27. package/dist/chunk-QY7BMXWT.js.map +1 -0
  28. package/dist/chunk-U62ZECSQ.js +54 -0
  29. package/dist/chunk-U62ZECSQ.js.map +1 -0
  30. package/dist/chunk-YQ36AK5A.js +5570 -0
  31. package/dist/chunk-YQ36AK5A.js.map +1 -0
  32. package/dist/cli/dev.d.ts +1 -0
  33. package/dist/cli/dev.js +14 -0
  34. package/dist/cli/dev.js.map +1 -0
  35. package/dist/cli/index.d.ts +1 -0
  36. package/dist/cli/index.js +14 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/command-defaults/defaults/commit-push.command.md +13 -0
  39. package/dist/command-defaults/defaults/init.command.md +18 -0
  40. package/dist/command-defaults/defaults/test-ui.command.md +16 -0
  41. package/dist/config-6GJ4SVFH.js +33 -0
  42. package/dist/config-6GJ4SVFH.js.map +1 -0
  43. package/dist/init-UVZDOHMX.js +185 -0
  44. package/dist/init-UVZDOHMX.js.map +1 -0
  45. package/dist/paths-4BPXWB2W.js +17 -0
  46. package/dist/paths-4BPXWB2W.js.map +1 -0
  47. package/dist/project-creator-CWPKRIKI.js +69 -0
  48. package/dist/project-creator-CWPKRIKI.js.map +1 -0
  49. package/dist/protocol-BJsm88OW.d.ts +760 -0
  50. package/dist/provider-LCE6WZA6.js +343 -0
  51. package/dist/provider-LCE6WZA6.js.map +1 -0
  52. package/dist/serve-27DZK34K.js +177 -0
  53. package/dist/serve-27DZK34K.js.map +1 -0
  54. package/dist/server/index.d.ts +479 -0
  55. package/dist/server/index.js +20 -0
  56. package/dist/server/index.js.map +1 -0
  57. package/dist/settings-D5O3TMLD.js +17 -0
  58. package/dist/settings-D5O3TMLD.js.map +1 -0
  59. package/dist/shared/index.d.ts +19 -0
  60. package/dist/shared/index.js +17 -0
  61. package/dist/shared/index.js.map +1 -0
  62. package/dist/skill-defaults/defaults/playwright-cli.skill.md +38 -0
  63. package/dist/tools-R4YQAYUF.js +46 -0
  64. package/dist/tools-R4YQAYUF.js.map +1 -0
  65. package/dist/web/assets/html2canvas.esm-QH1iLAAe.js +22 -0
  66. package/dist/web/assets/index-BMNDCsbJ.js +120 -0
  67. package/dist/web/assets/index-BSlSWZrM.css +1 -0
  68. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  69. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  70. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  71. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  72. package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
  73. package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
  74. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  75. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  76. package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  77. package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  78. package/dist/web/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
  79. package/dist/web/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
  80. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  81. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  82. package/dist/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  83. package/dist/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  84. package/dist/web/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  85. package/dist/web/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  86. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  87. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  88. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  89. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  90. package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
  91. package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
  92. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  93. package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  94. package/dist/web/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
  95. package/dist/web/assets/openfox.png +0 -0
  96. package/dist/web/fox.svg +25 -0
  97. package/dist/web/index.html +14 -0
  98. package/dist/web/sounds/achievement.mp3 +0 -0
  99. package/dist/web/sounds/intervention.mp3 +0 -0
  100. package/dist/web/sounds/notification.mp3 +0 -0
  101. package/dist/web/sounds/waiting-for-user.mp3 +0 -0
  102. package/package.json +101 -0
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runCli
4
+ } from "../chunk-EI46G5WO.js";
5
+ import {
6
+ logger
7
+ } from "../chunk-U62ZECSQ.js";
8
+
9
+ // src/cli/dev.ts
10
+ runCli({ mode: "development" }).catch((error) => {
11
+ logger.error("CLI fatal error", { error: error instanceof Error ? error.message : String(error) });
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/dev.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runCli } from './main.js'\nimport { logger } from '../server/utils/logger.js'\nrunCli({ mode: 'development' }).catch((error) => {\n logger.error('CLI fatal error', { error: error instanceof Error ? error.message : String(error) })\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;AAGA,OAAO,EAAE,MAAM,cAAc,CAAC,EAAE,MAAM,CAAC,UAAU;AAC/C,SAAO,MAAM,mBAAmB,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AACjG,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runCli
4
+ } from "../chunk-EI46G5WO.js";
5
+ import {
6
+ logger
7
+ } from "../chunk-U62ZECSQ.js";
8
+
9
+ // src/cli/index.ts
10
+ runCli({ mode: "production" }).catch((error) => {
11
+ logger.error("CLI fatal error", { error: error instanceof Error ? error.message : String(error) });
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runCli } from './main.js'\nimport { logger } from '../server/utils/logger.js'\nrunCli({ mode: 'production' }).catch((error) => {\n logger.error('CLI fatal error', { error: error instanceof Error ? error.message : String(error) })\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;AAGA,OAAO,EAAE,MAAM,aAAa,CAAC,EAAE,MAAM,CAAC,UAAU;AAC9C,SAAO,MAAM,mBAAmB,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AACjG,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -0,0 +1,13 @@
1
+ ---
2
+ id: commit-push
3
+ name: Commit & Push
4
+ agentMode: builder
5
+ ---
6
+
7
+ Commit and push:
8
+
9
+ 0. `npm test` -> make sure the project is green
10
+ 1. `git status` -> verify what files are modified
11
+ 2. `git add` -> never do `-A`, add your files and your files only
12
+ 3. `git commit -m` -> add a clear message
13
+ 4. `git push` -> if push fails, it's probably because of an existing commit coming from origin. If that's the case, you need to rebase your commit on top of it and fix the issues that arise. Make sure `npm test` is still green after your rebase.
@@ -0,0 +1,18 @@
1
+ ---
2
+ id: init
3
+ name: Init
4
+ agentMode: builder
5
+ ---
6
+
7
+ Read the entire codebase and generate or refresh the `AGENTS.md` file at the project root.
8
+
9
+ This file should serve as a comprehensive guide for AI coding agents operating in this repository. Include:
10
+
11
+ 1. **Project Overview** - What the project does, tech stack, high-level architecture
12
+ 2. **Directory Structure** - Key directories and what they contain
13
+ 3. **Build, Lint, Test Commands** - How to build, run tests, lint, typecheck (both full suite and single file)
14
+ 4. **Code Conventions** - Naming, file organization, patterns used in the codebase
15
+ 5. **Key Abstractions** - Important classes, interfaces, and patterns an agent should understand
16
+ 6. **Common Pitfalls** - Things that are easy to get wrong
17
+
18
+ Be thorough but concise. The target audience is an AI agent, not a human developer.
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: test-ui
3
+ name: Test UI
4
+ agentMode: builder
5
+ ---
6
+
7
+ Test the feature we've been working on in the browser using the `playwright-cli` skill.
8
+
9
+ 1. Load the `playwright-cli` skill
10
+ 2. Check if the dev server is already running; if not, start it
11
+ 3. Open the app in the browser
12
+ 4. Take a snapshot to see the current state
13
+ 5. Interact with the UI to exercise the current feature
14
+ 6. Verify it works as expected - check for visual correctness, functionality, and edge cases
15
+ 7. Take screenshots throughout testing and use `read_file` on them so they appear inline in the conversation
16
+ 8. Report back with what you found
@@ -0,0 +1,33 @@
1
+ import {
2
+ activateProvider,
3
+ addProvider,
4
+ configFileExists,
5
+ getActiveProvider,
6
+ getDefaultModel,
7
+ loadGlobalConfig,
8
+ mergeConfigs,
9
+ migrateConfig,
10
+ removeProvider,
11
+ saveGlobalConfig,
12
+ setDefaultModelSelection,
13
+ trySmartDefaults
14
+ } from "./chunk-43SWNGYW.js";
15
+ import "./chunk-FF6Z2MC3.js";
16
+ import "./chunk-26NRL3HX.js";
17
+ import "./chunk-U62ZECSQ.js";
18
+ import "./chunk-AF7XS4OI.js";
19
+ export {
20
+ activateProvider,
21
+ addProvider,
22
+ configFileExists,
23
+ getActiveProvider,
24
+ getDefaultModel,
25
+ loadGlobalConfig,
26
+ mergeConfigs,
27
+ migrateConfig,
28
+ removeProvider,
29
+ saveGlobalConfig,
30
+ setDefaultModelSelection,
31
+ trySmartDefaults
32
+ };
33
+ //# sourceMappingURL=config-6GJ4SVFH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,185 @@
1
+ import {
2
+ addProvider,
3
+ saveGlobalConfig
4
+ } from "./chunk-43SWNGYW.js";
5
+ import {
6
+ detectBackend,
7
+ detectModel
8
+ } from "./chunk-FF6Z2MC3.js";
9
+ import "./chunk-26NRL3HX.js";
10
+ import "./chunk-U62ZECSQ.js";
11
+ import "./chunk-AF7XS4OI.js";
12
+
13
+ // src/cli/init.ts
14
+ import { select, text, spinner, outro, confirm } from "@clack/prompts";
15
+ var LLM_OPTIONS = [
16
+ { value: "http://localhost:8000", label: "http://localhost:8000" },
17
+ { value: "http://localhost:11434", label: "http://localhost:11434" },
18
+ { value: "http://localhost:8080", label: "http://localhost:8080" },
19
+ { value: "other", label: "Other..." }
20
+ ];
21
+ function createBaseConfig() {
22
+ return {
23
+ providers: [],
24
+ activeProviderId: void 0,
25
+ server: { port: 10369, host: "127.0.0.1", openBrowser: true },
26
+ logging: { level: "info" },
27
+ database: { path: "" },
28
+ workspace: { workdir: process.cwd() }
29
+ };
30
+ }
31
+ async function promptNetworkAccessibility() {
32
+ const networkChoice = await select({
33
+ message: "How should OpenFox be accessible?",
34
+ options: [
35
+ { value: "localhost", label: "Secure (localhost only)" },
36
+ { value: "network", label: "Accessible from local network (phone, tablet, etc.)" }
37
+ ]
38
+ });
39
+ if (networkChoice === /* @__PURE__ */ Symbol.for("clack:cancel")) {
40
+ return "127.0.0.1";
41
+ }
42
+ return networkChoice === "network" ? "0.0.0.0" : "127.0.0.1";
43
+ }
44
+ async function runInitWithSelect(mode, existingConfig) {
45
+ let preserveProviders = false;
46
+ let config;
47
+ if (existingConfig && existingConfig.providers.length > 0) {
48
+ console.log("\nCurrent configuration detected:");
49
+ console.log(` Providers: ${existingConfig.providers.length}`);
50
+ existingConfig.providers.forEach((p, i) => {
51
+ console.log(` ${i + 1}. ${p.name} (${p.url}) - ${p.model}`);
52
+ });
53
+ console.log(` Server: ${existingConfig.server.host}`);
54
+ const keepChoice = await confirm({
55
+ message: "Keep existing providers?",
56
+ initialValue: true
57
+ });
58
+ if (keepChoice === true) {
59
+ preserveProviders = true;
60
+ config = {
61
+ ...existingConfig,
62
+ server: { ...existingConfig.server }
63
+ // Copy server settings
64
+ };
65
+ console.log("\nPreserving existing providers...\n");
66
+ } else {
67
+ console.log("\nStarting fresh configuration...\n");
68
+ config = createBaseConfig();
69
+ }
70
+ } else {
71
+ config = createBaseConfig();
72
+ }
73
+ if (!preserveProviders) {
74
+ const s = spinner();
75
+ s.start("Searching for local LLM...");
76
+ const detected = await Promise.all(
77
+ LLM_OPTIONS.filter((o) => o.value !== "other").map(async ({ value: url }) => {
78
+ try {
79
+ const [backend, model] = await Promise.all([
80
+ detectBackend(url, void 0, true),
81
+ detectModel(url, 1, true)
82
+ ]);
83
+ if (backend !== "unknown" && model) {
84
+ return { url, backend, model };
85
+ }
86
+ } catch {
87
+ }
88
+ return null;
89
+ })
90
+ );
91
+ const found = detected.find((r) => r !== null);
92
+ if (found) {
93
+ s.stop(`\u2713 Found ${found.backend} (${found.model})`);
94
+ config = addProvider(config, {
95
+ name: "Default",
96
+ url: found.url,
97
+ backend: found.backend,
98
+ maxContext: 2e5,
99
+ isActive: true
100
+ });
101
+ } else {
102
+ s.stop("\u2717 No LLM server detected");
103
+ const selection = await select({
104
+ message: "Select your LLM server:",
105
+ options: LLM_OPTIONS
106
+ });
107
+ let url;
108
+ if (String(selection) === "other") {
109
+ const textValue = await text({
110
+ message: "LLM Server URL (don't include /v1)",
111
+ placeholder: "http://localhost:8000",
112
+ initialValue: "http://localhost:8000",
113
+ validate: (value) => {
114
+ if (!value || value.length === 0) return "URL is required";
115
+ if (!value.startsWith("http")) return "Must start with http://";
116
+ }
117
+ });
118
+ url = String(textValue);
119
+ } else {
120
+ url = String(selection);
121
+ }
122
+ const s2 = spinner();
123
+ s2.start(`Testing connection to ${url}...`);
124
+ try {
125
+ const backend = await detectBackend(url);
126
+ const model = await detectModel(url);
127
+ s2.stop(`Connected to ${backend}${model ? " (" + model + ")" : ""}`);
128
+ config = addProvider(config, {
129
+ name: "Default",
130
+ url,
131
+ backend,
132
+ maxContext: 2e5,
133
+ isActive: true
134
+ });
135
+ const { setDefaultModelSelection } = await import("./config-6GJ4SVFH.js");
136
+ config = setDefaultModelSelection(config, config.providers[config.providers.length - 1].id, model ?? "auto");
137
+ } catch {
138
+ s2.stop("Server isn't available");
139
+ const choice = await select({
140
+ message: "Continue with this URL or retry?",
141
+ options: [
142
+ { value: "continue", label: "Continue" },
143
+ { value: "retry", label: "Retry" },
144
+ { value: "change", label: "Select different server" }
145
+ ]
146
+ });
147
+ if (choice === "retry") {
148
+ return runInitWithSelect(mode, existingConfig);
149
+ } else if (choice === "change") {
150
+ return runInitWithSelect(mode, existingConfig);
151
+ }
152
+ config = addProvider(config, {
153
+ name: "Default",
154
+ url,
155
+ backend: "auto",
156
+ maxContext: 2e5,
157
+ isActive: true
158
+ });
159
+ const { setDefaultModelSelection } = await import("./config-6GJ4SVFH.js");
160
+ config = setDefaultModelSelection(config, config.providers[config.providers.length - 1].id, "auto");
161
+ }
162
+ }
163
+ }
164
+ const host = await promptNetworkAccessibility();
165
+ config.server.host = host;
166
+ const workdirChoice = await text({
167
+ message: "Workspace directory for new projects",
168
+ placeholder: "Directory where new projects will be created",
169
+ initialValue: config.workspace?.workdir || process.cwd(),
170
+ validate: (value) => {
171
+ if (!value || value.length === 0) return "Workspace directory is required";
172
+ }
173
+ });
174
+ config.workspace = { workdir: String(workdirChoice).replace(/\/$/, "") };
175
+ if (config.providers.length > 0 && !config.defaultModelSelection) {
176
+ const { setDefaultModelSelection } = await import("./config-6GJ4SVFH.js");
177
+ config = setDefaultModelSelection(config, config.providers[0].id, "auto");
178
+ }
179
+ await saveGlobalConfig(mode, config);
180
+ outro("Configuration saved!");
181
+ }
182
+ export {
183
+ runInitWithSelect
184
+ };
185
+ //# sourceMappingURL=init-UVZDOHMX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/init.ts"],"sourcesContent":["import { select, text, spinner, outro, confirm } from '@clack/prompts'\nimport { detectBackend, detectModel } from '../server/llm/index.js'\nimport { saveGlobalConfig, addProvider, type GlobalConfig } from './config.js'\nimport type { Mode } from './main.js'\nimport type { ProviderBackend } from '../shared/types.js'\n\nconst LLM_OPTIONS = [\n { value: 'http://localhost:8000', label: 'http://localhost:8000' },\n { value: 'http://localhost:11434', label: 'http://localhost:11434' },\n { value: 'http://localhost:8080', label: 'http://localhost:8080' },\n { value: 'other', label: 'Other...' },\n]\n\nfunction createBaseConfig(): GlobalConfig {\n return {\n providers: [],\n activeProviderId: undefined,\n server: { port: 10369, host: '127.0.0.1', openBrowser: true },\n logging: { level: 'info' as const },\n database: { path: '' },\n workspace: { workdir: process.cwd() },\n }\n}\n\n/**\n * Prompt user for network accessibility preference\n * Returns '127.0.0.1' for localhost only, '0.0.0.0' for network access\n */\nasync function promptNetworkAccessibility(): Promise<string> {\n const networkChoice = await select({\n message: 'How should OpenFox be accessible?',\n options: [\n { value: 'localhost', label: 'Secure (localhost only)' },\n { value: 'network', label: 'Accessible from local network (phone, tablet, etc.)' },\n ],\n })\n \n // Default to secure if user skips\n if (networkChoice === Symbol.for('clack:cancel')) {\n return '127.0.0.1'\n }\n \n return networkChoice === 'network' ? '0.0.0.0' : '127.0.0.1'\n}\n\n/**\n * Run the init wizard with optional existing config\n * If existingConfig is provided, offer to preserve providers\n */\nexport async function runInitWithSelect(mode: Mode, existingConfig?: GlobalConfig): Promise<void> {\n // If existing config, ask about preserving providers\n let preserveProviders = false\n let config: GlobalConfig\n \n if (existingConfig && existingConfig.providers.length > 0) {\n console.log('\\nCurrent configuration detected:')\n console.log(` Providers: ${existingConfig.providers.length}`)\n existingConfig.providers.forEach((p, i) => {\n console.log(` ${i + 1}. ${p.name} (${p.url}) - ${p.model}`)\n })\n console.log(` Server: ${existingConfig.server.host}`)\n \n const keepChoice = await confirm({\n message: 'Keep existing providers?',\n initialValue: true,\n })\n \n if (keepChoice === true) {\n preserveProviders = true\n config = {\n ...existingConfig,\n server: { ...existingConfig.server }, // Copy server settings\n }\n console.log('\\nPreserving existing providers...\\n')\n } else {\n console.log('\\nStarting fresh configuration...\\n')\n config = createBaseConfig()\n }\n } else {\n config = createBaseConfig()\n }\n \n // Only run LLM setup if not preserving providers\n if (!preserveProviders) {\n const s = spinner()\n s.start('Searching for local LLM...')\n \n // Try smart defaults in parallel while showing spinner\n const detected = await Promise.all(\n LLM_OPTIONS.filter(o => o.value !== 'other').map(async ({ value: url }) => {\n try {\n const [backend, model] = await Promise.all([\n detectBackend(url, undefined, true),\n detectModel(url, 1, true),\n ])\n if (backend !== 'unknown' && model) {\n return { url, backend, model }\n }\n } catch {\n // Silent fail\n }\n return null\n })\n )\n \n const found = detected.find(r => r !== null)\n if (found) {\n s.stop(`✓ Found ${found.backend} (${found.model})`)\n config = addProvider(config, {\n name: 'Default',\n url: found.url,\n\n backend: found.backend as ProviderBackend,\n maxContext: 200000,\n isActive: true,\n })\n } else {\n s.stop('✗ No LLM server detected')\n \n const selection = await select({\n message: 'Select your LLM server:',\n options: LLM_OPTIONS,\n })\n \n let url: string\n if (String(selection) === 'other') {\n const textValue = await text({\n message: 'LLM Server URL (don\\'t include /v1)',\n placeholder: 'http://localhost:8000',\n initialValue: 'http://localhost:8000',\n validate: (value) => {\n if (!value || value.length === 0) return 'URL is required'\n if (!value.startsWith('http')) return 'Must start with http://'\n },\n })\n url = String(textValue)\n } else {\n url = String(selection)\n }\n \n // Test connection\n const s2 = spinner()\n s2.start(`Testing connection to ${url}...`)\n \n try {\n const backend = await detectBackend(url)\n const model = await detectModel(url)\n \n s2.stop(`Connected to ${backend}${model ? ' (' + model + ')' : ''}`)\n \n config = addProvider(config, {\n name: 'Default',\n url,\n backend: backend as ProviderBackend,\n maxContext: 200000,\n isActive: true,\n })\n // Set the default model selection\n const { setDefaultModelSelection } = await import('./config.js')\n config = setDefaultModelSelection(config, config.providers[config.providers.length - 1]!.id, model ?? 'auto')\n } catch {\n s2.stop('Server isn\\'t available')\n \n const choice = await select({\n message: 'Continue with this URL or retry?',\n options: [\n { value: 'continue', label: 'Continue' },\n { value: 'retry', label: 'Retry' },\n { value: 'change', label: 'Select different server' },\n ],\n })\n \n if (choice === 'retry') {\n return runInitWithSelect(mode, existingConfig) // Recurse to retry\n } else if (choice === 'change') {\n return runInitWithSelect(mode, existingConfig) // Recurse to change selection\n }\n // choice === 'continue' - save anyway with auto backend\n config = addProvider(config, {\n name: 'Default',\n url,\n backend: 'auto',\n maxContext: 200000,\n isActive: true,\n })\n // Set the default model selection\n const { setDefaultModelSelection } = await import('./config.js')\n config = setDefaultModelSelection(config, config.providers[config.providers.length - 1]!.id, 'auto')\n }\n }\n }\n \n // Ask about network accessibility\n const host = await promptNetworkAccessibility()\n config.server.host = host\n \n // Ask about workspace directory\n const workdirChoice = await text({\n message: 'Workspace directory for new projects',\n placeholder: 'Directory where new projects will be created',\n initialValue: config.workspace?.workdir || process.cwd(),\n validate: (value) => {\n if (!value || value.length === 0) return 'Workspace directory is required'\n },\n })\n // Normalize: remove trailing slash to prevent double slashes in paths\n config.workspace = { workdir: String(workdirChoice).replace(/\\/$/, '') }\n \n // If a provider was added and no default model selection exists, set it\n if (config.providers.length > 0 && !config.defaultModelSelection) {\n const { setDefaultModelSelection } = await import('./config.js')\n config = setDefaultModelSelection(config, config.providers[0]!.id, 'auto')\n }\n \n // Save the configuration\n await saveGlobalConfig(mode, config)\n \n outro('Configuration saved!')\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,17 @@
1
+ import {
2
+ ensureDataDirExists,
3
+ getDatabasePath,
4
+ getGlobalConfigDir,
5
+ getGlobalConfigPath,
6
+ getGlobalDataDir,
7
+ getSkillsDir
8
+ } from "./chunk-AF7XS4OI.js";
9
+ export {
10
+ ensureDataDirExists,
11
+ getDatabasePath,
12
+ getGlobalConfigDir,
13
+ getGlobalConfigPath,
14
+ getGlobalDataDir,
15
+ getSkillsDir
16
+ };
17
+ //# sourceMappingURL=paths-4BPXWB2W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,69 @@
1
+ import {
2
+ createProject
3
+ } from "./chunk-N2XYX4ZA.js";
4
+ import "./chunk-4Z2OZ6NX.js";
5
+ import "./chunk-U62ZECSQ.js";
6
+
7
+ // src/server/utils/project-creator.ts
8
+ import { mkdir } from "fs/promises";
9
+ import { execSync } from "child_process";
10
+ import { join } from "path";
11
+ function validateProjectName(name) {
12
+ if (!name || name.trim() === "") {
13
+ return { valid: false, error: "Project name cannot be empty" };
14
+ }
15
+ const validPattern = /^[a-zA-Z0-9._-]+$/;
16
+ if (!validPattern.test(name)) {
17
+ return {
18
+ valid: false,
19
+ error: "Project name can only contain letters, numbers, hyphens, underscores, and dots"
20
+ };
21
+ }
22
+ if (name.includes("/") || name.includes("\\") || name.includes("..")) {
23
+ return { valid: false, error: "Project name cannot contain path separators" };
24
+ }
25
+ return { valid: true };
26
+ }
27
+ async function directoryExists(path) {
28
+ try {
29
+ const { access } = await import("fs/promises");
30
+ const { constants } = await import("fs");
31
+ await access(path, constants.F_OK);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ async function createDirectoryWithGit(projectName, workdir) {
38
+ const validation = validateProjectName(projectName);
39
+ if (!validation.valid) {
40
+ throw new Error(validation.error);
41
+ }
42
+ const fullPath = join(workdir, projectName);
43
+ const exists = await directoryExists(fullPath);
44
+ if (exists) {
45
+ throw new Error(`Directory already exists: ${fullPath}. Please choose a different name or use the existing directory from Browse Projects.`);
46
+ }
47
+ try {
48
+ await mkdir(fullPath, { recursive: true });
49
+ } catch (error) {
50
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
51
+ throw new Error(`Failed to create directory: ${errorMessage}`);
52
+ }
53
+ try {
54
+ execSync("git init", { cwd: fullPath, stdio: "pipe" });
55
+ } catch (error) {
56
+ const { rm } = await import("fs/promises");
57
+ await rm(fullPath, { recursive: true, force: true });
58
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
59
+ throw new Error(`Failed to initialize git repository: ${errorMessage}`);
60
+ }
61
+ const project = createProject(projectName, fullPath);
62
+ return project;
63
+ }
64
+ export {
65
+ createDirectoryWithGit,
66
+ directoryExists,
67
+ validateProjectName
68
+ };
69
+ //# sourceMappingURL=project-creator-CWPKRIKI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/utils/project-creator.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises'\nimport { execSync } from 'node:child_process'\nimport { join } from 'node:path'\nimport { createProject as createProjectDb } from '../db/projects.js'\nimport type { Project } from '../../shared/types.js'\n\n/**\n * Validate a project name for safe directory creation.\n * Only allows alphanumeric characters, hyphens, underscores, and dots.\n */\nexport function validateProjectName(name: string): { valid: true } | { valid: false; error: string } {\n if (!name || name.trim() === '') {\n return { valid: false, error: 'Project name cannot be empty' }\n }\n \n // Check for valid characters (alphanumeric, hyphens, underscores, dots)\n const validPattern = /^[a-zA-Z0-9._-]+$/\n if (!validPattern.test(name)) {\n return { \n valid: false, \n error: 'Project name can only contain letters, numbers, hyphens, underscores, and dots' \n }\n }\n \n // Check for path traversal attempts\n if (name.includes('/') || name.includes('\\\\') || name.includes('..')) {\n return { valid: false, error: 'Project name cannot contain path separators' }\n }\n \n return { valid: true }\n}\n\n/**\n * Check if a directory already exists at the given path\n */\nexport async function directoryExists(path: string): Promise<boolean> {\n try {\n const { access } = await import('node:fs/promises')\n const { constants } = await import('node:fs')\n await access(path, constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Create a new project directory with git initialization.\n * \n * @param projectName - The name of the project (will be prefixed with workdir)\n * @param workdir - The base directory where the project will be created\n * @returns The created project\n * @throws Error if creation fails\n */\nexport async function createDirectoryWithGit(projectName: string, workdir: string): Promise<Project> {\n // Validate project name\n const validation = validateProjectName(projectName)\n if (!validation.valid) {\n throw new Error(validation.error)\n }\n \n // Build full path\n const fullPath = join(workdir, projectName)\n \n // Check if directory already exists\n const exists = await directoryExists(fullPath)\n if (exists) {\n throw new Error(`Directory already exists: ${fullPath}. Please choose a different name or use the existing directory from Browse Projects.`)\n }\n \n // Create directory recursively\n try {\n await mkdir(fullPath, { recursive: true })\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error'\n throw new Error(`Failed to create directory: ${errorMessage}`)\n }\n \n // Initialize git repository\n try {\n execSync('git init', { cwd: fullPath, stdio: 'pipe' })\n } catch (error) {\n // Clean up the directory if git init fails\n const { rm } = await import('node:fs/promises')\n await rm(fullPath, { recursive: true, force: true })\n \n const errorMessage = error instanceof Error ? error.message : 'Unknown error'\n throw new Error(`Failed to initialize git repository: ${errorMessage}`)\n }\n \n // Register project in database\n const project = createProjectDb(projectName, fullPath)\n \n return project\n}\n"],"mappings":";;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,YAAY;AAQd,SAAS,oBAAoB,MAAiE;AACnG,MAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAAI;AAC/B,WAAO,EAAE,OAAO,OAAO,OAAO,+BAA+B;AAAA,EAC/D;AAGA,QAAM,eAAe;AACrB,MAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;AACpE,WAAO,EAAE,OAAO,OAAO,OAAO,8CAA8C;AAAA,EAC9E;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;AAKA,eAAsB,gBAAgB,MAAgC;AACpE,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,OAAO,aAAkB;AAClD,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,IAAS;AAC5C,UAAM,OAAO,MAAM,UAAU,IAAI;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,uBAAuB,aAAqB,SAAmC;AAEnG,QAAM,aAAa,oBAAoB,WAAW;AAClD,MAAI,CAAC,WAAW,OAAO;AACrB,UAAM,IAAI,MAAM,WAAW,KAAK;AAAA,EAClC;AAGA,QAAM,WAAW,KAAK,SAAS,WAAW;AAG1C,QAAM,SAAS,MAAM,gBAAgB,QAAQ;AAC7C,MAAI,QAAQ;AACV,UAAM,IAAI,MAAM,6BAA6B,QAAQ,sFAAsF;AAAA,EAC7I;AAGA,MAAI;AACF,UAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,UAAM,IAAI,MAAM,+BAA+B,YAAY,EAAE;AAAA,EAC/D;AAGA,MAAI;AACF,aAAS,YAAY,EAAE,KAAK,UAAU,OAAO,OAAO,CAAC;AAAA,EACvD,SAAS,OAAO;AAEd,UAAM,EAAE,GAAG,IAAI,MAAM,OAAO,aAAkB;AAC9C,UAAM,GAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAEnD,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAC9D,UAAM,IAAI,MAAM,wCAAwC,YAAY,EAAE;AAAA,EACxE;AAGA,QAAM,UAAU,cAAgB,aAAa,QAAQ;AAErD,SAAO;AACT;","names":[]}