@apart-tech/apart-intelligence 1.0.5 → 1.0.6

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.
@@ -5,7 +5,7 @@ import { randomBytes } from "crypto";
5
5
  import chalk from "chalk";
6
6
  import prompts from "prompts";
7
7
  import { PrismaClient } from "@prisma/client";
8
- import { withAccelerate } from "@prisma/extension-accelerate";
8
+ import { saveCredentials } from "../api-client/index.js";
9
9
  const LOGO = `
10
10
  ╔══════════════════════════════════════╗
11
11
  ║ ${chalk.bold("Apart Intelligence")} ║
@@ -33,304 +33,424 @@ export function initCommand(program) {
33
33
  process.exit(0);
34
34
  }
35
35
  }
36
- // ── Step 1: Database ────────────────────────────────────────────
37
- console.log(chalk.bold("\n1. Database\n"));
38
- const { dbChoice } = await prompts({
36
+ // ── Step 1: Choose setup mode ─────────────────────────────────
37
+ console.log(chalk.bold("\n1. Setup\n"));
38
+ const { setupMode } = await prompts({
39
39
  type: "select",
40
- name: "dbChoice",
41
- message: "How would you like to connect to a database?",
40
+ name: "setupMode",
41
+ message: "How would you like to get started?",
42
42
  choices: [
43
43
  {
44
- title: "Bring your own PostgreSQL",
45
- description: "Connect to an existing PostgreSQL instance with pgvector",
46
- value: "byod",
44
+ title: "Cloud",
45
+ description: "Connect to Apart Intelligence cloud",
46
+ value: "cloud",
47
47
  },
48
48
  {
49
- title: "Prisma Postgres (cloud)",
50
- description: "Use a Prisma Postgres connection string",
51
- value: "prisma",
49
+ title: "Self-hosted",
50
+ description: "Connect your own PostgreSQL database",
51
+ value: "self-hosted",
52
52
  },
53
53
  ],
54
54
  });
55
- if (dbChoice === undefined)
55
+ if (setupMode === undefined)
56
56
  abort();
57
- let databaseUrl;
58
- if (dbChoice === "prisma") {
59
- const { url } = await prompts({
60
- type: "text",
61
- name: "url",
62
- message: "Prisma Postgres connection string:",
63
- validate: (v) => v.startsWith("prisma+postgres://") || v.startsWith("prisma://")
64
- ? true
65
- : "Must start with prisma+postgres:// or prisma://",
66
- });
67
- if (!url)
68
- abort();
69
- databaseUrl = url;
57
+ if (setupMode === "cloud") {
58
+ // ── Cloud flow ────────────────────────────────────────────────
59
+ await initCloud(configPath);
70
60
  }
71
61
  else {
72
- const { url } = await prompts({
73
- type: "text",
74
- name: "url",
75
- message: "PostgreSQL connection string:",
76
- initial: "postgresql://localhost:5432/apart",
77
- validate: (v) => v.startsWith("postgresql://") || v.startsWith("postgres://")
78
- ? true
79
- : "Must start with postgresql:// or postgres://",
80
- });
81
- if (!url)
82
- abort();
83
- databaseUrl = url;
62
+ // ── Self-hosted flow ──────────────────────────────────────────
63
+ await initSelfHosted(configPath);
84
64
  }
85
- // ── Step 2: Test connection & set up schema ─────────────────────
86
- console.log(chalk.dim("\nTesting database connection..."));
87
- let db;
88
- try {
89
- if (databaseUrl.startsWith("prisma+postgres://") || databaseUrl.startsWith("prisma://")) {
90
- process.env.DATABASE_URL = databaseUrl;
91
- db = new PrismaClient().$extends(withAccelerate());
92
- }
93
- else {
94
- db = new PrismaClient({ datasourceUrl: databaseUrl });
95
- }
96
- await db.$connect();
97
- console.log(chalk.green(" ✓ Connected"));
65
+ process.exit(0);
66
+ });
67
+ }
68
+ function abort() {
69
+ console.log(chalk.dim("\nAborted."));
70
+ process.exit(0);
71
+ }
72
+ function findPrismaSchema() {
73
+ const candidates = [
74
+ join(process.cwd(), "packages", "core"),
75
+ join(process.cwd(), "node_modules", "@apart-tech", "intelligence-core"),
76
+ resolve(new URL(".", import.meta.url).pathname, "..", "..", ".."),
77
+ ];
78
+ for (const dir of candidates) {
79
+ if (existsSync(join(dir, "prisma", "schema.prisma"))) {
80
+ return dir;
98
81
  }
99
- catch (err) {
100
- console.error(chalk.red(` ✗ Connection failed: ${err.message}`));
101
- console.log(chalk.dim(" Check your connection string and try again."));
82
+ }
83
+ const pkgDir = resolve(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "core");
84
+ if (existsSync(join(pkgDir, "prisma", "schema.prisma"))) {
85
+ return pkgDir;
86
+ }
87
+ throw new Error("Could not find Prisma schema. Make sure @apart-tech/intelligence-core is installed.");
88
+ }
89
+ // ── Cloud flow ────────────────────────────────────────────────────────
90
+ const DEFAULT_SERVER = "https://intelligence-api-prod-959271008232.europe-west3.run.app";
91
+ async function initCloud(configPath) {
92
+ // ── Server & API Key ──────────────────────────────────────────────
93
+ console.log(chalk.bold("\n2. Connect to Apart Intelligence\n"));
94
+ const { server } = await prompts({
95
+ type: "text",
96
+ name: "server",
97
+ message: "Server URL:",
98
+ initial: DEFAULT_SERVER,
99
+ });
100
+ if (!server)
101
+ abort();
102
+ const serverUrl = server.replace(/\/$/, "");
103
+ // Validate server
104
+ console.log(chalk.dim("\nConnecting to server..."));
105
+ try {
106
+ const healthRes = await fetch(`${serverUrl}/api/health`);
107
+ if (!healthRes.ok) {
108
+ console.error(chalk.red(` ✗ Server not reachable: ${serverUrl}`));
102
109
  process.exit(1);
103
110
  }
104
- // Push schema
105
- console.log(chalk.dim("Setting up database schema..."));
106
- try {
107
- const prismaSchemaDir = findPrismaSchema();
108
- execSync(`npx prisma db push --skip-generate --accept-data-loss`, {
109
- cwd: prismaSchemaDir,
110
- stdio: "pipe",
111
- env: { ...process.env, DATABASE_URL: databaseUrl },
112
- });
113
- console.log(chalk.green(" Schema synced"));
111
+ console.log(chalk.green(" ✓ Server reachable"));
112
+ }
113
+ catch (err) {
114
+ console.error(chalk.red(` ✗ Cannot connect to server: ${err.message}`));
115
+ process.exit(1);
116
+ }
117
+ const { apiKey } = await prompts({
118
+ type: "password",
119
+ name: "apiKey",
120
+ message: "API key:",
121
+ });
122
+ if (!apiKey)
123
+ abort();
124
+ // Validate API key
125
+ console.log(chalk.dim("Validating API key..."));
126
+ try {
127
+ const authRes = await fetch(`${serverUrl}/api/nodes/types`, {
128
+ headers: { Authorization: `Bearer ${apiKey}` },
129
+ });
130
+ if (authRes.status === 401) {
131
+ console.error(chalk.red(" ✗ Invalid API key"));
132
+ process.exit(1);
114
133
  }
115
- catch (err) {
116
- if (err.stderr?.toString().includes('type "vector" does not exist')) {
117
- console.log(chalk.dim(" Enabling pgvector extension..."));
118
- try {
119
- const prismaSchemaDir = findPrismaSchema();
120
- execSync(`echo "CREATE EXTENSION IF NOT EXISTS vector;" | npx prisma db execute --schema prisma/schema.prisma --stdin`, {
121
- cwd: prismaSchemaDir,
122
- stdio: "pipe",
123
- env: { ...process.env, DATABASE_URL: databaseUrl },
124
- });
125
- execSync(`npx prisma db push --skip-generate --accept-data-loss`, {
126
- cwd: prismaSchemaDir,
127
- stdio: "pipe",
128
- env: { ...process.env, DATABASE_URL: databaseUrl },
129
- });
130
- console.log(chalk.green(" ✓ pgvector enabled & schema synced"));
131
- }
132
- catch (innerErr) {
133
- console.error(chalk.red(` ✗ Schema push failed: ${innerErr.message}`));
134
- console.log(chalk.dim(" You may need to manually enable pgvector: CREATE EXTENSION IF NOT EXISTS vector;"));
135
- process.exit(1);
136
- }
137
- }
138
- else {
139
- console.error(chalk.red(` ✗ Schema push failed`));
140
- console.log(chalk.dim(err.stderr?.toString().slice(0, 300) ?? err.message));
141
- process.exit(1);
142
- }
134
+ if (!authRes.ok) {
135
+ console.error(chalk.red(` ✗ Auth check failed: HTTP ${authRes.status}`));
136
+ process.exit(1);
143
137
  }
144
- // ── Step 3: Organization ────────────────────────────────────────
145
- console.log(chalk.bold("\n2. Organization\n"));
146
- const { orgName } = await prompts({
147
- type: "text",
148
- name: "orgName",
149
- message: "Organization name:",
150
- initial: "My Company",
138
+ console.log(chalk.green(" ✓ Authenticated"));
139
+ }
140
+ catch (err) {
141
+ console.error(chalk.red(` ✗ Auth check failed: ${err.message}`));
142
+ process.exit(1);
143
+ }
144
+ // Save credentials
145
+ saveCredentials({ server: serverUrl, apiKey });
146
+ // ── Organization ──────────────────────────────────────────────────
147
+ console.log(chalk.bold("\n3. Organization\n"));
148
+ const { orgName } = await prompts({
149
+ type: "text",
150
+ name: "orgName",
151
+ message: "Organization name:",
152
+ initial: "My Company",
153
+ });
154
+ if (!orgName)
155
+ abort();
156
+ console.log(chalk.dim("Creating organization..."));
157
+ try {
158
+ const orgRes = await fetch(`${serverUrl}/api/organizations`, {
159
+ method: "POST",
160
+ headers: {
161
+ Authorization: `Bearer ${apiKey}`,
162
+ "Content-Type": "application/json",
163
+ },
164
+ body: JSON.stringify({ name: orgName }),
151
165
  });
152
- if (!orgName)
153
- abort();
154
- const orgSlug = orgName
155
- .toLowerCase()
156
- .replace(/[^a-z0-9]+/g, "-")
157
- .replace(/^-|-$/g, "");
158
- // Reconnect with fresh client after schema push
159
- if (databaseUrl.startsWith("prisma+postgres://") || databaseUrl.startsWith("prisma://")) {
160
- process.env.DATABASE_URL = databaseUrl;
161
- db = new PrismaClient().$extends(withAccelerate());
166
+ if (orgRes.ok) {
167
+ const org = await orgRes.json();
168
+ console.log(chalk.green(` ✓ Organization created: ${org.name}`));
169
+ }
170
+ else if (orgRes.status === 409) {
171
+ console.log(chalk.yellow(` Organization "${orgName}" already exists`));
162
172
  }
163
173
  else {
164
- db = new PrismaClient({ datasourceUrl: databaseUrl });
174
+ const err = await orgRes.json().catch(() => ({}));
175
+ console.error(chalk.red(` ✗ Failed to create organization: ${err.error || `HTTP ${orgRes.status}`}`));
176
+ process.exit(1);
165
177
  }
166
- let org;
178
+ }
179
+ catch (err) {
180
+ console.error(chalk.red(` ✗ Failed to create organization: ${err.message}`));
181
+ process.exit(1);
182
+ }
183
+ // ── Seed domains ──────────────────────────────────────────────────
184
+ console.log(chalk.bold("\n4. Knowledge Domains\n"));
185
+ const { seedDomains } = await prompts({
186
+ type: "confirm",
187
+ name: "seedDomains",
188
+ message: "Seed default domain taxonomy (strategy, people, products, etc.)?",
189
+ initial: true,
190
+ });
191
+ if (seedDomains) {
167
192
  try {
168
- org = await db.organization.findFirst({ where: { slug: orgSlug } });
169
- if (org) {
170
- console.log(chalk.yellow(` Organization "${orgName}" already exists (${org.id})`));
193
+ const seedRes = await fetch(`${serverUrl}/api/domains/seed`, {
194
+ method: "POST",
195
+ headers: { Authorization: `Bearer ${apiKey}` },
196
+ });
197
+ if (seedRes.ok) {
198
+ const result = await seedRes.json();
199
+ console.log(chalk.green(` ✓ ${result.created} domains seeded`));
171
200
  }
172
201
  else {
173
- org = await db.organization.create({
174
- data: { name: orgName, slug: orgSlug },
175
- });
176
- console.log(chalk.green(` ✓ Organization created: ${org.id}`));
202
+ console.error(chalk.red(" ✗ Domain seeding failed"));
177
203
  }
178
204
  }
179
205
  catch (err) {
180
- console.error(chalk.red(` ✗ Failed to create organization: ${err.message}`));
181
- process.exit(1);
206
+ console.error(chalk.red(` ✗ Domain seeding failed: ${err.message}`));
182
207
  }
183
- // ── Step 4: API Key ─────────────────────────────────────────────
184
- console.log(chalk.bold("\n3. API Key\n"));
185
- const { createApiKey } = await prompts({
186
- type: "confirm",
187
- name: "createApiKey",
188
- message: "Generate an API key for this organization?",
189
- initial: true,
208
+ }
209
+ // ── Write config ──────────────────────────────────────────────────
210
+ console.log(chalk.bold("\n5. Writing config\n"));
211
+ writeConfigFile(configPath);
212
+ // ── Done ──────────────────────────────────────────────────────────
213
+ console.log(chalk.bold("\n✓ Setup complete!\n"));
214
+ console.log(" Get started:");
215
+ console.log(chalk.cyan(" ai add --type note --title 'My first note' 'Hello from Apart Intelligence!'"));
216
+ console.log(chalk.cyan(" ai search 'hello'"));
217
+ console.log(chalk.cyan(" ai map . # map a codebase"));
218
+ console.log(chalk.cyan(" ai graph # visualize the graph"));
219
+ console.log();
220
+ }
221
+ // ── Self-hosted flow ──────────────────────────────────────────────────
222
+ async function initSelfHosted(configPath) {
223
+ // ── Database ──────────────────────────────────────────────────────
224
+ console.log(chalk.bold("\n2. Database\n"));
225
+ const { url } = await prompts({
226
+ type: "text",
227
+ name: "url",
228
+ message: "PostgreSQL connection string:",
229
+ initial: "postgresql://localhost:5432/apart",
230
+ validate: (v) => v.startsWith("postgresql://") || v.startsWith("postgres://")
231
+ ? true
232
+ : "Must start with postgresql:// or postgres://",
233
+ });
234
+ if (!url)
235
+ abort();
236
+ const databaseUrl = url;
237
+ // Test connection
238
+ console.log(chalk.dim("\nTesting database connection..."));
239
+ let db;
240
+ try {
241
+ db = new PrismaClient({ datasourceUrl: databaseUrl });
242
+ await db.$connect();
243
+ console.log(chalk.green(" ✓ Connected"));
244
+ }
245
+ catch (err) {
246
+ console.error(chalk.red(` ✗ Connection failed: ${err.message}`));
247
+ console.log(chalk.dim(" Check your connection string and try again."));
248
+ process.exit(1);
249
+ }
250
+ // Push schema
251
+ console.log(chalk.dim("Setting up database schema..."));
252
+ try {
253
+ const prismaSchemaDir = findPrismaSchema();
254
+ execSync(`npx prisma db push --skip-generate --accept-data-loss`, {
255
+ cwd: prismaSchemaDir,
256
+ stdio: "pipe",
257
+ env: { ...process.env, DATABASE_URL: databaseUrl },
190
258
  });
191
- let apiKeyValue = null;
192
- if (createApiKey) {
193
- apiKeyValue = `apart_${randomBytes(24).toString("hex")}`;
194
- const { createHash } = await import("crypto");
195
- const keyHash = createHash("sha256").update(apiKeyValue).digest("hex");
259
+ console.log(chalk.green(" ✓ Schema synced"));
260
+ }
261
+ catch (err) {
262
+ if (err.stderr?.toString().includes('type "vector" does not exist')) {
263
+ console.log(chalk.dim(" Enabling pgvector extension..."));
196
264
  try {
197
- await db.apiKey.create({
198
- data: {
199
- name: `${orgName} — CLI`,
200
- keyHash,
201
- createdBy: "ai-init",
202
- organizationId: org.id,
203
- },
265
+ const prismaSchemaDir = findPrismaSchema();
266
+ execSync(`echo "CREATE EXTENSION IF NOT EXISTS vector;" | npx prisma db execute --schema prisma/schema.prisma --stdin`, {
267
+ cwd: prismaSchemaDir,
268
+ stdio: "pipe",
269
+ env: { ...process.env, DATABASE_URL: databaseUrl },
204
270
  });
205
- console.log(chalk.green(" ✓ API key created"));
206
- console.log(chalk.bold(`\n ${apiKeyValue}\n`));
207
- console.log(chalk.yellow(" Save this key — it won't be shown again."));
271
+ execSync(`npx prisma db push --skip-generate --accept-data-loss`, {
272
+ cwd: prismaSchemaDir,
273
+ stdio: "pipe",
274
+ env: { ...process.env, DATABASE_URL: databaseUrl },
275
+ });
276
+ console.log(chalk.green(" ✓ pgvector enabled & schema synced"));
208
277
  }
209
- catch (err) {
210
- console.error(chalk.red(` ✗ Failed to create API key: ${err.message}`));
278
+ catch (innerErr) {
279
+ console.error(chalk.red(` ✗ Schema push failed: ${innerErr.message}`));
280
+ console.log(chalk.dim(" You may need to manually enable pgvector: CREATE EXTENSION IF NOT EXISTS vector;"));
281
+ process.exit(1);
211
282
  }
212
283
  }
213
- // ── Step 5: Embeddings ──────────────────────────────────────────
214
- console.log(chalk.bold("\n4. Embeddings\n"));
215
- const { openaiKey } = await prompts({
216
- type: "password",
217
- name: "openaiKey",
218
- message: "OpenAI API key (for embeddings):",
219
- validate: (v) => v.startsWith("sk-") ? true : "Must start with sk-",
220
- });
221
- if (!openaiKey)
222
- abort();
223
- // ── Step 6: Seed domains ────────────────────────────────────────
224
- console.log(chalk.bold("\n5. Knowledge Domains\n"));
225
- const { seedDomains } = await prompts({
226
- type: "confirm",
227
- name: "seedDomains",
228
- message: "Seed default domain taxonomy (strategy, people, products, etc.)?",
229
- initial: true,
230
- });
231
- if (seedDomains) {
232
- try {
233
- const { DomainService, getTenantDb } = await import("@apart-tech/intelligence-core");
234
- const tenantDb = getTenantDb(databaseUrl, { organizationId: org.id });
235
- const domainService = new DomainService(tenantDb, { organizationId: org.id });
236
- const result = await domainService.seed();
237
- console.log(chalk.green(` ✓ ${result.created} domains seeded`));
238
- }
239
- catch (err) {
240
- console.error(chalk.red(` ✗ Domain seeding failed: ${err.message}`));
241
- }
284
+ else {
285
+ console.error(chalk.red(` ✗ Schema push failed`));
286
+ console.log(chalk.dim(err.stderr?.toString().slice(0, 300) ?? err.message));
287
+ process.exit(1);
242
288
  }
243
- // ── Step 7: Write config files ──────────────────────────────────
244
- console.log(chalk.bold("\n6. Writing config files\n"));
245
- // .apart.yaml
246
- const yamlContent = [
247
- "# Apart Intelligence configuration",
248
- "# Docs: https://github.com/aleksander-apart/the-collective",
249
- "",
250
- "embeddings:",
251
- " provider: openai",
252
- " model: text-embedding-3-small",
253
- " dimensions: 1536",
254
- "",
255
- "search:",
256
- " semanticWeight: 0.6",
257
- " defaultLimit: 10",
258
- " includeDrafts: false",
259
- "",
260
- "context:",
261
- " maxDepth: 1",
262
- " maxNodes: 20",
263
- " minRelevance: 0.001",
264
- "",
265
- "mcp:",
266
- " port: 3100",
267
- " auth: none",
268
- "",
269
- ].join("\n");
270
- writeFileSync(configPath, yamlContent, "utf-8");
271
- console.log(chalk.green(" ✓ .apart.yaml"));
272
- // .env
273
- const envPath = join(process.cwd(), ".env");
274
- const envContent = [
275
- `# Apart Intelligence — generated by ai init`,
276
- `APART_DATABASE_URL=${databaseUrl}`,
277
- `OPENAI_API_KEY=${openaiKey}`,
278
- apiKeyValue ? `APART_API_KEY=${apiKeyValue}` : `# APART_API_KEY=`,
279
- `APART_ORG_ID=${org.id}`,
280
- "",
281
- ].join("\n");
282
- if (existsSync(envPath)) {
283
- const { overwriteEnv } = await prompts({
284
- type: "confirm",
285
- name: "overwriteEnv",
286
- message: ".env already exists. Overwrite?",
287
- initial: false,
288
- });
289
- if (overwriteEnv) {
290
- writeFileSync(envPath, envContent, "utf-8");
291
- console.log(chalk.green(" ✓ .env (overwritten)"));
292
- }
293
- else {
294
- const altPath = join(process.cwd(), ".env.apart");
295
- writeFileSync(altPath, envContent, "utf-8");
296
- console.log(chalk.green(` ✓ .env.apart (merge manually into .env)`));
297
- }
289
+ }
290
+ // ── Organization ──────────────────────────────────────────────────
291
+ console.log(chalk.bold("\n3. Organization\n"));
292
+ const { orgName } = await prompts({
293
+ type: "text",
294
+ name: "orgName",
295
+ message: "Organization name:",
296
+ initial: "My Company",
297
+ });
298
+ if (!orgName)
299
+ abort();
300
+ const orgSlug = orgName
301
+ .toLowerCase()
302
+ .replace(/[^a-z0-9]+/g, "-")
303
+ .replace(/^-|-$/g, "");
304
+ // Reconnect with fresh client after schema push
305
+ db = new PrismaClient({ datasourceUrl: databaseUrl });
306
+ let org;
307
+ try {
308
+ org = await db.organization.findFirst({ where: { slug: orgSlug } });
309
+ if (org) {
310
+ console.log(chalk.yellow(` Organization "${orgName}" already exists (${org.id})`));
298
311
  }
299
312
  else {
300
- writeFileSync(envPath, envContent, "utf-8");
301
- console.log(chalk.green(" ✓ .env"));
313
+ org = await db.organization.create({
314
+ data: { name: orgName, slug: orgSlug },
315
+ });
316
+ console.log(chalk.green(` ✓ Organization created: ${org.id}`));
302
317
  }
303
- // ── Done ────────────────────────────────────────────────────────
304
- console.log(chalk.bold("\n✓ Setup complete!\n"));
305
- console.log(" Get started:");
306
- console.log(chalk.cyan(" ai add --type note --title 'My first note' 'Hello from Apart Intelligence!'"));
307
- console.log(chalk.cyan(" ai search 'hello'"));
308
- console.log(chalk.cyan(" ai map . # map a codebase"));
309
- console.log(chalk.cyan(" ai graph # visualize the graph"));
310
- console.log();
311
- await db.$disconnect();
312
- process.exit(0);
318
+ }
319
+ catch (err) {
320
+ console.error(chalk.red(` Failed to create organization: ${err.message}`));
321
+ process.exit(1);
322
+ }
323
+ // ── API Key ───────────────────────────────────────────────────────
324
+ console.log(chalk.bold("\n4. API Key\n"));
325
+ const { createApiKey } = await prompts({
326
+ type: "confirm",
327
+ name: "createApiKey",
328
+ message: "Generate an API key for this organization?",
329
+ initial: true,
313
330
  });
314
- }
315
- function abort() {
316
- console.log(chalk.dim("\nAborted."));
317
- process.exit(0);
318
- }
319
- function findPrismaSchema() {
320
- const candidates = [
321
- join(process.cwd(), "packages", "core"),
322
- join(process.cwd(), "node_modules", "@apart-tech", "intelligence-core"),
323
- resolve(new URL(".", import.meta.url).pathname, "..", "..", ".."),
324
- ];
325
- for (const dir of candidates) {
326
- if (existsSync(join(dir, "prisma", "schema.prisma"))) {
327
- return dir;
331
+ let apiKeyValue = null;
332
+ if (createApiKey) {
333
+ apiKeyValue = `apart_${randomBytes(24).toString("hex")}`;
334
+ const { createHash } = await import("crypto");
335
+ const keyHash = createHash("sha256").update(apiKeyValue).digest("hex");
336
+ try {
337
+ await db.apiKey.create({
338
+ data: {
339
+ name: `${orgName} CLI`,
340
+ keyHash,
341
+ createdBy: "ai-init",
342
+ organizationId: org.id,
343
+ },
344
+ });
345
+ console.log(chalk.green(" ✓ API key created"));
346
+ console.log(chalk.bold(`\n ${apiKeyValue}\n`));
347
+ console.log(chalk.yellow(" Save this key — it won't be shown again."));
348
+ }
349
+ catch (err) {
350
+ console.error(chalk.red(` ✗ Failed to create API key: ${err.message}`));
328
351
  }
329
352
  }
330
- const pkgDir = resolve(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "core");
331
- if (existsSync(join(pkgDir, "prisma", "schema.prisma"))) {
332
- return pkgDir;
353
+ // ── Embeddings ────────────────────────────────────────────────────
354
+ console.log(chalk.bold("\n5. Embeddings\n"));
355
+ const { openaiKey } = await prompts({
356
+ type: "password",
357
+ name: "openaiKey",
358
+ message: "OpenAI API key (for embeddings):",
359
+ validate: (v) => v.startsWith("sk-") ? true : "Must start with sk-",
360
+ });
361
+ if (!openaiKey)
362
+ abort();
363
+ // ── Seed domains ──────────────────────────────────────────────────
364
+ console.log(chalk.bold("\n6. Knowledge Domains\n"));
365
+ const { seedDomains } = await prompts({
366
+ type: "confirm",
367
+ name: "seedDomains",
368
+ message: "Seed default domain taxonomy (strategy, people, products, etc.)?",
369
+ initial: true,
370
+ });
371
+ if (seedDomains) {
372
+ try {
373
+ const { DomainService, getTenantDb } = await import("@apart-tech/intelligence-core");
374
+ const tenantDb = getTenantDb(databaseUrl, { organizationId: org.id });
375
+ const domainService = new DomainService(tenantDb, { organizationId: org.id });
376
+ const result = await domainService.seed();
377
+ console.log(chalk.green(` ✓ ${result.created} domains seeded`));
378
+ }
379
+ catch (err) {
380
+ console.error(chalk.red(` ✗ Domain seeding failed: ${err.message}`));
381
+ }
333
382
  }
334
- throw new Error("Could not find Prisma schema. Make sure @apart-tech/intelligence-core is installed.");
383
+ // ── Write config files ────────────────────────────────────────────
384
+ console.log(chalk.bold("\n7. Writing config files\n"));
385
+ writeConfigFile(configPath);
386
+ // .env
387
+ const envPath = join(process.cwd(), ".env");
388
+ const envContent = [
389
+ `# Apart Intelligence — generated by ai init`,
390
+ `APART_DATABASE_URL=${databaseUrl}`,
391
+ `OPENAI_API_KEY=${openaiKey}`,
392
+ apiKeyValue ? `APART_API_KEY=${apiKeyValue}` : `# APART_API_KEY=`,
393
+ `APART_ORG_ID=${org.id}`,
394
+ "",
395
+ ].join("\n");
396
+ if (existsSync(envPath)) {
397
+ const { overwriteEnv } = await prompts({
398
+ type: "confirm",
399
+ name: "overwriteEnv",
400
+ message: ".env already exists. Overwrite?",
401
+ initial: false,
402
+ });
403
+ if (overwriteEnv) {
404
+ writeFileSync(envPath, envContent, "utf-8");
405
+ console.log(chalk.green(" ✓ .env (overwritten)"));
406
+ }
407
+ else {
408
+ const altPath = join(process.cwd(), ".env.apart");
409
+ writeFileSync(altPath, envContent, "utf-8");
410
+ console.log(chalk.green(` ✓ .env.apart (merge manually into .env)`));
411
+ }
412
+ }
413
+ else {
414
+ writeFileSync(envPath, envContent, "utf-8");
415
+ console.log(chalk.green(" ✓ .env"));
416
+ }
417
+ // ── Done ──────────────────────────────────────────────────────────
418
+ console.log(chalk.bold("\n✓ Setup complete!\n"));
419
+ console.log(" Get started:");
420
+ console.log(chalk.cyan(" ai add --type note --title 'My first note' 'Hello from Apart Intelligence!'"));
421
+ console.log(chalk.cyan(" ai search 'hello'"));
422
+ console.log(chalk.cyan(" ai map . # map a codebase"));
423
+ console.log(chalk.cyan(" ai graph # visualize the graph"));
424
+ console.log();
425
+ await db.$disconnect();
426
+ }
427
+ // ── Shared helpers ────────────────────────────────────────────────────
428
+ function writeConfigFile(configPath) {
429
+ const yamlContent = [
430
+ "# Apart Intelligence configuration",
431
+ "# Docs: https://github.com/aleksander-apart/the-collective",
432
+ "",
433
+ "embeddings:",
434
+ " provider: openai",
435
+ " model: text-embedding-3-small",
436
+ " dimensions: 1536",
437
+ "",
438
+ "search:",
439
+ " semanticWeight: 0.6",
440
+ " defaultLimit: 10",
441
+ " includeDrafts: false",
442
+ "",
443
+ "context:",
444
+ " maxDepth: 1",
445
+ " maxNodes: 20",
446
+ " minRelevance: 0.001",
447
+ "",
448
+ "mcp:",
449
+ " port: 3100",
450
+ " auth: none",
451
+ "",
452
+ ].join("\n");
453
+ writeFileSync(configPath, yamlContent, "utf-8");
454
+ console.log(chalk.green(" ✓ .apart.yaml"));
335
455
  }
336
456
  //# sourceMappingURL=init.js.map