@apart-tech/apart-intelligence 1.0.4 → 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.
- 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 +378 -258
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/map.d.ts.map +1 -1
- package/dist/commands/map.js +104 -10
- package/dist/commands/map.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,424 @@ 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 & 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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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(` ✗
|
|
181
|
-
process.exit(1);
|
|
206
|
+
console.error(chalk.red(` ✗ Domain seeding failed: ${err.message}`));
|
|
182
207
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 (
|
|
210
|
-
console.error(chalk.red(` ✗
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
console.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
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
|