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