@mukulaggarwal/pacman 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.
- package/README.md +39 -0
- package/dist/chunk-3QNXXON5.js +330 -0
- package/dist/chunk-3QNXXON5.js.map +1 -0
- package/dist/chunk-43PUZDIZ.js +148 -0
- package/dist/chunk-43PUZDIZ.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-AYFIQNZ5.js +807 -0
- package/dist/chunk-AYFIQNZ5.js.map +1 -0
- package/dist/chunk-FH6ZHWGR.js +37 -0
- package/dist/chunk-FH6ZHWGR.js.map +1 -0
- package/dist/chunk-O6T35A4O.js +137 -0
- package/dist/chunk-O6T35A4O.js.map +1 -0
- package/dist/chunk-TRQIZP6Z.js +451 -0
- package/dist/chunk-TRQIZP6Z.js.map +1 -0
- package/dist/chunk-UWT6AFJB.js +471 -0
- package/dist/chunk-UWT6AFJB.js.map +1 -0
- package/dist/chunk-ZKKMIDRK.js +3923 -0
- package/dist/chunk-ZKKMIDRK.js.map +1 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +141 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dist-3PIJOFZ4.js +91 -0
- package/dist/dist-3PIJOFZ4.js.map +1 -0
- package/dist/dist-L76NGFFH.js +102 -0
- package/dist/dist-L76NGFFH.js.map +1 -0
- package/dist/dist-NV2YVVHI.js +178 -0
- package/dist/dist-NV2YVVHI.js.map +1 -0
- package/dist/dist-RMYCRZIU.js +41 -0
- package/dist/dist-RMYCRZIU.js.map +1 -0
- package/dist/dist-THLCZNOZ.js +14 -0
- package/dist/dist-THLCZNOZ.js.map +1 -0
- package/dist/dist-TWNHTXYH.js +95 -0
- package/dist/dist-TWNHTXYH.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +452 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-compat.d.ts +1 -0
- package/dist/mcp-compat.js +78 -0
- package/dist/mcp-compat.js.map +1 -0
- package/dist/onboarding-server.d.ts +3 -0
- package/dist/onboarding-server.js +1172 -0
- package/dist/onboarding-server.js.map +1 -0
- package/dist/provider-runtime.d.ts +11 -0
- package/dist/provider-runtime.js +10 -0
- package/dist/provider-runtime.js.map +1 -0
- package/dist/slack-listener.d.ts +49 -0
- package/dist/slack-listener.js +888 -0
- package/dist/slack-listener.js.map +1 -0
- package/dist/storage.d.ts +8 -0
- package/dist/storage.js +9 -0
- package/dist/storage.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNoopEventClient,
|
|
3
|
+
validateIntegrationConfig
|
|
4
|
+
} from "./chunk-43PUZDIZ.js";
|
|
5
|
+
import {
|
|
6
|
+
createContextManager
|
|
7
|
+
} from "./chunk-UWT6AFJB.js";
|
|
8
|
+
import {
|
|
9
|
+
validateProviderConfig
|
|
10
|
+
} from "./chunk-O6T35A4O.js";
|
|
11
|
+
import {
|
|
12
|
+
createConfigManager,
|
|
13
|
+
createGDriveStorage,
|
|
14
|
+
createLocalStorage
|
|
15
|
+
} from "./chunk-TRQIZP6Z.js";
|
|
16
|
+
import {
|
|
17
|
+
createSlackConnector,
|
|
18
|
+
validateSlackAppToken
|
|
19
|
+
} from "./chunk-ZKKMIDRK.js";
|
|
20
|
+
import "./chunk-7D4SUZUM.js";
|
|
21
|
+
|
|
22
|
+
// src/onboarding-server.ts
|
|
23
|
+
import express from "express";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
import * as fs from "fs/promises";
|
|
26
|
+
import { exec } from "child_process";
|
|
27
|
+
import { promisify } from "util";
|
|
28
|
+
|
|
29
|
+
// ../template-engine/dist/index.js
|
|
30
|
+
var PROFILE_TEMPLATES = {
|
|
31
|
+
"software-engineer": {
|
|
32
|
+
profileType: "software-engineer",
|
|
33
|
+
name: "Software Engineer",
|
|
34
|
+
description: "Context template for software engineers",
|
|
35
|
+
responsibilities: [
|
|
36
|
+
"Code review and PR management",
|
|
37
|
+
"Feature development",
|
|
38
|
+
"Bug fixing and debugging",
|
|
39
|
+
"Architecture decisions",
|
|
40
|
+
"Technical documentation",
|
|
41
|
+
"On-call and incident response",
|
|
42
|
+
"Mentoring junior engineers",
|
|
43
|
+
"Sprint planning and estimation",
|
|
44
|
+
"CI/CD pipeline maintenance",
|
|
45
|
+
"API design and development"
|
|
46
|
+
],
|
|
47
|
+
sections: [
|
|
48
|
+
{
|
|
49
|
+
name: "Overview",
|
|
50
|
+
fileName: "overview.md",
|
|
51
|
+
required: true,
|
|
52
|
+
defaultContent: `# Overview
|
|
53
|
+
|
|
54
|
+
## Role
|
|
55
|
+
Software Engineer
|
|
56
|
+
|
|
57
|
+
## Team
|
|
58
|
+
<!-- Your team name -->
|
|
59
|
+
|
|
60
|
+
## Primary Focus
|
|
61
|
+
<!-- Main area of work -->
|
|
62
|
+
|
|
63
|
+
## Key Repositories
|
|
64
|
+
<!-- List your main repos -->
|
|
65
|
+
|
|
66
|
+
## Tech Stack
|
|
67
|
+
<!-- Languages, frameworks, tools -->
|
|
68
|
+
`
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Responsibilities",
|
|
72
|
+
fileName: "responsibilities.md",
|
|
73
|
+
required: true,
|
|
74
|
+
defaultContent: `# Responsibilities
|
|
75
|
+
|
|
76
|
+
## Core Duties
|
|
77
|
+
<!-- Your primary responsibilities -->
|
|
78
|
+
|
|
79
|
+
## On-Call
|
|
80
|
+
<!-- On-call schedule and escalation paths -->
|
|
81
|
+
|
|
82
|
+
## Code Review
|
|
83
|
+
<!-- Review expectations and areas -->
|
|
84
|
+
`
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "Stakeholders",
|
|
88
|
+
fileName: "stakeholders.md",
|
|
89
|
+
required: false,
|
|
90
|
+
defaultContent: `# Stakeholders
|
|
91
|
+
|
|
92
|
+
## Direct Manager
|
|
93
|
+
<!-- Name, role -->
|
|
94
|
+
|
|
95
|
+
## Team Members
|
|
96
|
+
<!-- Key teammates -->
|
|
97
|
+
|
|
98
|
+
## Cross-Team Contacts
|
|
99
|
+
<!-- People you work with in other teams -->
|
|
100
|
+
|
|
101
|
+
## Escalation Path
|
|
102
|
+
<!-- Who to escalate to and when -->
|
|
103
|
+
`
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "Documentation",
|
|
107
|
+
fileName: "docs.md",
|
|
108
|
+
required: false,
|
|
109
|
+
defaultContent: `# Documentation References
|
|
110
|
+
|
|
111
|
+
## Internal Docs
|
|
112
|
+
<!-- Links to internal documentation -->
|
|
113
|
+
|
|
114
|
+
## Runbooks
|
|
115
|
+
<!-- Links to runbooks -->
|
|
116
|
+
|
|
117
|
+
## Architecture Docs
|
|
118
|
+
<!-- Links to architecture documents -->
|
|
119
|
+
`
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"product-manager": {
|
|
124
|
+
profileType: "product-manager",
|
|
125
|
+
name: "Product Manager",
|
|
126
|
+
description: "Context template for product managers",
|
|
127
|
+
responsibilities: [
|
|
128
|
+
"Product roadmap management",
|
|
129
|
+
"Feature prioritization",
|
|
130
|
+
"Stakeholder communication",
|
|
131
|
+
"User research and interviews",
|
|
132
|
+
"Metrics and KPI tracking",
|
|
133
|
+
"Sprint planning and grooming",
|
|
134
|
+
"Go-to-market strategy",
|
|
135
|
+
"Competitive analysis",
|
|
136
|
+
"Cross-team coordination",
|
|
137
|
+
"Product requirements documentation"
|
|
138
|
+
],
|
|
139
|
+
sections: [
|
|
140
|
+
{
|
|
141
|
+
name: "Overview",
|
|
142
|
+
fileName: "overview.md",
|
|
143
|
+
required: true,
|
|
144
|
+
defaultContent: `# Overview
|
|
145
|
+
|
|
146
|
+
## Role
|
|
147
|
+
Product Manager
|
|
148
|
+
|
|
149
|
+
## Product Area
|
|
150
|
+
<!-- Your product area -->
|
|
151
|
+
|
|
152
|
+
## Key Metrics
|
|
153
|
+
<!-- North star and supporting metrics -->
|
|
154
|
+
|
|
155
|
+
## Current Quarter Goals
|
|
156
|
+
<!-- OKRs or key goals -->
|
|
157
|
+
`
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "Responsibilities",
|
|
161
|
+
fileName: "responsibilities.md",
|
|
162
|
+
required: true,
|
|
163
|
+
defaultContent: `# Responsibilities
|
|
164
|
+
|
|
165
|
+
## Product Areas
|
|
166
|
+
<!-- Products/features you own -->
|
|
167
|
+
|
|
168
|
+
## Decision Authority
|
|
169
|
+
<!-- What you can decide vs. escalate -->
|
|
170
|
+
|
|
171
|
+
## Regular Meetings
|
|
172
|
+
<!-- Recurring meetings and cadences -->
|
|
173
|
+
`
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "Stakeholders",
|
|
177
|
+
fileName: "stakeholders.md",
|
|
178
|
+
required: false,
|
|
179
|
+
defaultContent: `# Stakeholders
|
|
180
|
+
|
|
181
|
+
## Engineering Leads
|
|
182
|
+
<!-- Engineering partners -->
|
|
183
|
+
|
|
184
|
+
## Design Partners
|
|
185
|
+
<!-- Design team contacts -->
|
|
186
|
+
|
|
187
|
+
## Business Stakeholders
|
|
188
|
+
<!-- Sales, marketing, exec sponsors -->
|
|
189
|
+
|
|
190
|
+
## Customers
|
|
191
|
+
<!-- Key customer contacts or segments -->
|
|
192
|
+
`
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "Documentation",
|
|
196
|
+
fileName: "docs.md",
|
|
197
|
+
required: false,
|
|
198
|
+
defaultContent: `# Documentation References
|
|
199
|
+
|
|
200
|
+
## PRDs
|
|
201
|
+
<!-- Product requirement documents -->
|
|
202
|
+
|
|
203
|
+
## Roadmap
|
|
204
|
+
<!-- Roadmap links -->
|
|
205
|
+
|
|
206
|
+
## Analytics Dashboards
|
|
207
|
+
<!-- Links to dashboards -->
|
|
208
|
+
`
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
"engineering-manager": {
|
|
213
|
+
profileType: "engineering-manager",
|
|
214
|
+
name: "Engineering Manager",
|
|
215
|
+
description: "Context template for engineering managers",
|
|
216
|
+
responsibilities: [
|
|
217
|
+
"Team management and 1:1s",
|
|
218
|
+
"Hiring and interviewing",
|
|
219
|
+
"Performance reviews",
|
|
220
|
+
"Technical strategy",
|
|
221
|
+
"Cross-team coordination",
|
|
222
|
+
"Sprint planning and velocity",
|
|
223
|
+
"Incident management",
|
|
224
|
+
"Budget and resource planning",
|
|
225
|
+
"Career development coaching",
|
|
226
|
+
"Process improvement"
|
|
227
|
+
],
|
|
228
|
+
sections: [
|
|
229
|
+
{
|
|
230
|
+
name: "Overview",
|
|
231
|
+
fileName: "overview.md",
|
|
232
|
+
required: true,
|
|
233
|
+
defaultContent: `# Overview
|
|
234
|
+
|
|
235
|
+
## Role
|
|
236
|
+
Engineering Manager
|
|
237
|
+
|
|
238
|
+
## Team
|
|
239
|
+
<!-- Team name and size -->
|
|
240
|
+
|
|
241
|
+
## Charter
|
|
242
|
+
<!-- Team charter / mission -->
|
|
243
|
+
|
|
244
|
+
## Key Systems
|
|
245
|
+
<!-- Systems your team owns -->
|
|
246
|
+
`
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "Responsibilities",
|
|
250
|
+
fileName: "responsibilities.md",
|
|
251
|
+
required: true,
|
|
252
|
+
defaultContent: `# Responsibilities
|
|
253
|
+
|
|
254
|
+
## People Management
|
|
255
|
+
<!-- Direct reports, 1:1 cadence -->
|
|
256
|
+
|
|
257
|
+
## Technical Oversight
|
|
258
|
+
<!-- Architecture, code quality, tech debt -->
|
|
259
|
+
|
|
260
|
+
## Process
|
|
261
|
+
<!-- Agile ceremonies, team rituals -->
|
|
262
|
+
|
|
263
|
+
## Hiring
|
|
264
|
+
<!-- Open roles, pipeline -->
|
|
265
|
+
`
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "Stakeholders",
|
|
269
|
+
fileName: "stakeholders.md",
|
|
270
|
+
required: false,
|
|
271
|
+
defaultContent: `# Stakeholders
|
|
272
|
+
|
|
273
|
+
## Direct Reports
|
|
274
|
+
<!-- Team members -->
|
|
275
|
+
|
|
276
|
+
## Skip Level
|
|
277
|
+
<!-- Your manager -->
|
|
278
|
+
|
|
279
|
+
## Product Partners
|
|
280
|
+
<!-- PMs you work with -->
|
|
281
|
+
|
|
282
|
+
## Peer Managers
|
|
283
|
+
<!-- Other EMs -->
|
|
284
|
+
`
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "Documentation",
|
|
288
|
+
fileName: "docs.md",
|
|
289
|
+
required: false,
|
|
290
|
+
defaultContent: `# Documentation References
|
|
291
|
+
|
|
292
|
+
## Team Docs
|
|
293
|
+
<!-- Team wiki, confluence pages -->
|
|
294
|
+
|
|
295
|
+
## Process Docs
|
|
296
|
+
<!-- Runbooks, playbooks -->
|
|
297
|
+
|
|
298
|
+
## HR Resources
|
|
299
|
+
<!-- Performance review templates, etc. -->
|
|
300
|
+
`
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
},
|
|
304
|
+
devops: {
|
|
305
|
+
profileType: "devops",
|
|
306
|
+
name: "DevOps Engineer",
|
|
307
|
+
description: "Context template for DevOps engineers",
|
|
308
|
+
responsibilities: [
|
|
309
|
+
"CI/CD pipeline management",
|
|
310
|
+
"Infrastructure as code",
|
|
311
|
+
"Monitoring and alerting",
|
|
312
|
+
"Incident response",
|
|
313
|
+
"Security compliance",
|
|
314
|
+
"Cost optimization",
|
|
315
|
+
"Deployment automation",
|
|
316
|
+
"Container orchestration",
|
|
317
|
+
"Database management",
|
|
318
|
+
"Disaster recovery planning"
|
|
319
|
+
],
|
|
320
|
+
sections: [
|
|
321
|
+
{
|
|
322
|
+
name: "Overview",
|
|
323
|
+
fileName: "overview.md",
|
|
324
|
+
required: true,
|
|
325
|
+
defaultContent: `# Overview
|
|
326
|
+
|
|
327
|
+
## Role
|
|
328
|
+
DevOps Engineer
|
|
329
|
+
|
|
330
|
+
## Infrastructure
|
|
331
|
+
<!-- Cloud provider, key services -->
|
|
332
|
+
|
|
333
|
+
## Key Environments
|
|
334
|
+
<!-- Production, staging, dev -->
|
|
335
|
+
|
|
336
|
+
## Monitoring Stack
|
|
337
|
+
<!-- Tools and dashboards -->
|
|
338
|
+
`
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "Responsibilities",
|
|
342
|
+
fileName: "responsibilities.md",
|
|
343
|
+
required: true,
|
|
344
|
+
defaultContent: `# Responsibilities
|
|
345
|
+
|
|
346
|
+
## Infrastructure
|
|
347
|
+
<!-- IaC, cloud resources -->
|
|
348
|
+
|
|
349
|
+
## CI/CD
|
|
350
|
+
<!-- Pipelines, deployment processes -->
|
|
351
|
+
|
|
352
|
+
## On-Call
|
|
353
|
+
<!-- PagerDuty, escalation paths -->
|
|
354
|
+
|
|
355
|
+
## Security
|
|
356
|
+
<!-- Compliance, access management -->
|
|
357
|
+
`
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "Stakeholders",
|
|
361
|
+
fileName: "stakeholders.md",
|
|
362
|
+
required: false,
|
|
363
|
+
defaultContent: `# Stakeholders
|
|
364
|
+
|
|
365
|
+
## Platform Team
|
|
366
|
+
<!-- Team members -->
|
|
367
|
+
|
|
368
|
+
## Engineering Teams
|
|
369
|
+
<!-- Teams you support -->
|
|
370
|
+
|
|
371
|
+
## Security Team
|
|
372
|
+
<!-- Security contacts -->
|
|
373
|
+
|
|
374
|
+
## Vendor Contacts
|
|
375
|
+
<!-- Cloud, tooling vendors -->
|
|
376
|
+
`
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "Documentation",
|
|
380
|
+
fileName: "docs.md",
|
|
381
|
+
required: false,
|
|
382
|
+
defaultContent: `# Documentation References
|
|
383
|
+
|
|
384
|
+
## Runbooks
|
|
385
|
+
<!-- Incident response runbooks -->
|
|
386
|
+
|
|
387
|
+
## Architecture Diagrams
|
|
388
|
+
<!-- Infrastructure diagrams -->
|
|
389
|
+
|
|
390
|
+
## Compliance Docs
|
|
391
|
+
<!-- SOC2, security policies -->
|
|
392
|
+
`
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
function getTemplate(profileType) {
|
|
398
|
+
const template = PROFILE_TEMPLATES[profileType];
|
|
399
|
+
if (!template) {
|
|
400
|
+
throw new Error(`Unknown profile type: ${profileType}`);
|
|
401
|
+
}
|
|
402
|
+
return { ...template };
|
|
403
|
+
}
|
|
404
|
+
function listProfiles() {
|
|
405
|
+
return Object.keys(PROFILE_TEMPLATES);
|
|
406
|
+
}
|
|
407
|
+
function getResponsibilities(profileType) {
|
|
408
|
+
const template = PROFILE_TEMPLATES[profileType];
|
|
409
|
+
if (!template) {
|
|
410
|
+
throw new Error(`Unknown profile type: ${profileType}`);
|
|
411
|
+
}
|
|
412
|
+
return [...template.responsibilities];
|
|
413
|
+
}
|
|
414
|
+
function renderTemplate(template, user) {
|
|
415
|
+
const files = {};
|
|
416
|
+
for (const section of template.sections) {
|
|
417
|
+
let content = section.defaultContent;
|
|
418
|
+
content = content.replace("<!-- Your team name -->", `${user.name}'s team`);
|
|
419
|
+
if (section.fileName === "responsibilities.md" && user.responsibilities.length > 0) {
|
|
420
|
+
content += "\n## Selected Responsibilities\n";
|
|
421
|
+
for (const r of user.responsibilities) {
|
|
422
|
+
content += `- ${r}
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
files[section.fileName] = content;
|
|
427
|
+
}
|
|
428
|
+
return files;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/onboarding-server.ts
|
|
432
|
+
var execAsync = promisify(exec);
|
|
433
|
+
async function startOnboardingServer(port, workspacePath) {
|
|
434
|
+
const app = express();
|
|
435
|
+
app.use(express.json());
|
|
436
|
+
let gdriveAuthSession = null;
|
|
437
|
+
const staticPath = path.resolve(
|
|
438
|
+
import.meta.dirname ?? __dirname,
|
|
439
|
+
"../../../apps/onboarding-web/dist"
|
|
440
|
+
);
|
|
441
|
+
try {
|
|
442
|
+
await fs.access(staticPath);
|
|
443
|
+
app.use(express.static(staticPath));
|
|
444
|
+
} catch {
|
|
445
|
+
app.get("/", (_req, res) => {
|
|
446
|
+
res.send(getInlineOnboardingHtml());
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
app.get("/api/profiles", (_req, res) => {
|
|
450
|
+
const profiles = listProfiles();
|
|
451
|
+
res.json({ profiles });
|
|
452
|
+
});
|
|
453
|
+
app.get("/api/responsibilities/:profileType", (req, res) => {
|
|
454
|
+
try {
|
|
455
|
+
const responsibilities = getResponsibilities(req.params.profileType);
|
|
456
|
+
res.json({ responsibilities });
|
|
457
|
+
} catch {
|
|
458
|
+
res.status(400).json({ error: "Invalid profile type" });
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
app.get("/api/template/:profileType", (req, res) => {
|
|
462
|
+
try {
|
|
463
|
+
const template = getTemplate(req.params.profileType);
|
|
464
|
+
res.json({ template });
|
|
465
|
+
} catch {
|
|
466
|
+
res.status(400).json({ error: "Invalid profile type" });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
app.post("/api/preview-template", (req, res) => {
|
|
470
|
+
const { profileType, name, assistantName, responsibilities } = req.body;
|
|
471
|
+
try {
|
|
472
|
+
const template = getTemplate(profileType);
|
|
473
|
+
const files = renderTemplate(template, { name, assistantName, responsibilities });
|
|
474
|
+
res.json({ files });
|
|
475
|
+
} catch (err) {
|
|
476
|
+
res.status(400).json({ error: String(err) });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
app.post("/api/save", async (req, res) => {
|
|
480
|
+
try {
|
|
481
|
+
const config = req.body;
|
|
482
|
+
const effectivePath = config.storage.mode === "local" && config.storage.workspacePath ? path.resolve(config.storage.workspacePath) : workspacePath;
|
|
483
|
+
await fs.mkdir(effectivePath, { recursive: true });
|
|
484
|
+
const rcPath = path.join(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".personal-assistant-rc.json");
|
|
485
|
+
await fs.writeFile(rcPath, JSON.stringify({ workspacePath: effectivePath }, null, 2), "utf-8");
|
|
486
|
+
const localStorage = createLocalStorage(effectivePath);
|
|
487
|
+
const localConfigManager = createConfigManager(localStorage);
|
|
488
|
+
await localConfigManager.saveConfig(config);
|
|
489
|
+
let targetStorage = localStorage;
|
|
490
|
+
if (config.storage.mode === "gdrive") {
|
|
491
|
+
const gdriveConfig = config.storage;
|
|
492
|
+
const resolvedCachePath = path.isAbsolute(gdriveConfig.cachePath) ? gdriveConfig.cachePath : path.resolve(path.dirname(effectivePath), gdriveConfig.cachePath);
|
|
493
|
+
const gdriveStorage = createGDriveStorage({
|
|
494
|
+
...gdriveConfig,
|
|
495
|
+
cachePath: resolvedCachePath
|
|
496
|
+
});
|
|
497
|
+
await gdriveStorage.initialize();
|
|
498
|
+
targetStorage = gdriveStorage;
|
|
499
|
+
const gdriveConfigManager = createConfigManager(gdriveStorage);
|
|
500
|
+
await gdriveConfigManager.saveConfig(config);
|
|
501
|
+
}
|
|
502
|
+
const contextManager = createContextManager(targetStorage);
|
|
503
|
+
await contextManager.initWorkspace();
|
|
504
|
+
const template = getTemplate(config.user.profileType);
|
|
505
|
+
const files = renderTemplate(template, {
|
|
506
|
+
name: config.user.name,
|
|
507
|
+
assistantName: config.user.assistantName,
|
|
508
|
+
responsibilities: config.user.responsibilities
|
|
509
|
+
});
|
|
510
|
+
await contextManager.writeCanonicalFiles(files);
|
|
511
|
+
const eventClient = createNoopEventClient();
|
|
512
|
+
await eventClient.emit("onboarding_completed", {
|
|
513
|
+
profile: config.user.profileType,
|
|
514
|
+
storage_mode: config.storage.mode
|
|
515
|
+
});
|
|
516
|
+
res.json({ success: true, workspacePath: effectivePath });
|
|
517
|
+
} catch (err) {
|
|
518
|
+
res.status(500).json({ error: String(err) });
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
app.post("/api/validate-integration", async (req, res) => {
|
|
522
|
+
const { type, credentials } = req.body;
|
|
523
|
+
try {
|
|
524
|
+
const result = await validateIntegrationConfig({
|
|
525
|
+
type,
|
|
526
|
+
enabled: true,
|
|
527
|
+
credentials
|
|
528
|
+
});
|
|
529
|
+
if (result.ok) {
|
|
530
|
+
res.json({ valid: true, info: result.summary ?? `Connected to ${type}` });
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
res.json({
|
|
534
|
+
valid: false,
|
|
535
|
+
error: [result.reason, ...result.fix ?? []].filter(Boolean).join(" ")
|
|
536
|
+
});
|
|
537
|
+
} catch (err) {
|
|
538
|
+
res.json({ valid: false, error: String(err) });
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
app.post("/api/validate-provider", async (req, res) => {
|
|
542
|
+
const { provider, apiKey, model } = req.body;
|
|
543
|
+
try {
|
|
544
|
+
const result = await validateProviderConfig(provider, apiKey, model);
|
|
545
|
+
res.json(result);
|
|
546
|
+
} catch (err) {
|
|
547
|
+
res.json({ valid: false, error: String(err) });
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
app.post("/api/validate-slack-runtime", async (req, res) => {
|
|
551
|
+
const {
|
|
552
|
+
botToken,
|
|
553
|
+
appToken,
|
|
554
|
+
generationEnabled,
|
|
555
|
+
providers
|
|
556
|
+
} = req.body;
|
|
557
|
+
if (!botToken || !appToken) {
|
|
558
|
+
res.json({ valid: false, error: "Slack bot token and app token are required." });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (!botToken.startsWith("xoxb-")) {
|
|
562
|
+
res.json({
|
|
563
|
+
valid: false,
|
|
564
|
+
error: "Slack bot token must start with xoxb-. Paste the Bot User OAuth Token from OAuth & Permissions."
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (!appToken.startsWith("xapp-")) {
|
|
569
|
+
res.json({
|
|
570
|
+
valid: false,
|
|
571
|
+
error: "Slack Socket Mode app token must start with xapp-. Create an App-Level Token with connections:write under Socket Mode."
|
|
572
|
+
});
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const connector = createSlackConnector();
|
|
577
|
+
await connector.authenticate({
|
|
578
|
+
type: "slack",
|
|
579
|
+
enabled: true,
|
|
580
|
+
credentials: { botToken }
|
|
581
|
+
});
|
|
582
|
+
const health = await connector.healthCheck();
|
|
583
|
+
await validateSlackAppToken(appToken);
|
|
584
|
+
if (generationEnabled) {
|
|
585
|
+
const providerEntries = [
|
|
586
|
+
["openai", providers?.openai],
|
|
587
|
+
["anthropic", providers?.anthropic]
|
|
588
|
+
];
|
|
589
|
+
const configuredProviders = providerEntries.filter(([, value]) => value?.apiKey);
|
|
590
|
+
if (configuredProviders.length === 0) {
|
|
591
|
+
res.json({ valid: false, error: "Enable at least one provider when Slack generation is turned on." });
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
for (const [provider, value] of configuredProviders) {
|
|
595
|
+
const result = await validateProviderConfig(provider, value?.apiKey ?? "", value?.model);
|
|
596
|
+
if (!result.valid) {
|
|
597
|
+
res.json({ valid: false, error: result.error ?? `Failed to validate ${provider}` });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
res.json({
|
|
603
|
+
valid: true,
|
|
604
|
+
info: `${health.summary ?? "Slack connected"} via Socket Mode. Atlas will monitor channels where the bot is added.`
|
|
605
|
+
});
|
|
606
|
+
} catch (err) {
|
|
607
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
608
|
+
if (message.includes("not_allowed_token_type")) {
|
|
609
|
+
res.json({
|
|
610
|
+
valid: false,
|
|
611
|
+
error: "Slack rejected the Socket Mode token. Use an App-Level Token that starts with xapp- and has the connections:write scope."
|
|
612
|
+
});
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
res.json({
|
|
616
|
+
valid: false,
|
|
617
|
+
error: message
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
app.get("/api/pick-folder", async (_req, res) => {
|
|
622
|
+
try {
|
|
623
|
+
let folderPath;
|
|
624
|
+
const platform = process.platform;
|
|
625
|
+
if (platform === "darwin") {
|
|
626
|
+
const { stdout } = await execAsync(
|
|
627
|
+
`osascript -e 'POSIX path of (choose folder with prompt "Select a folder for your personal assistant context")'`
|
|
628
|
+
);
|
|
629
|
+
folderPath = stdout.trim();
|
|
630
|
+
} else if (platform === "linux") {
|
|
631
|
+
const { stdout } = await execAsync(
|
|
632
|
+
'zenity --file-selection --directory --title="Select storage folder"'
|
|
633
|
+
);
|
|
634
|
+
folderPath = stdout.trim();
|
|
635
|
+
} else if (platform === "win32") {
|
|
636
|
+
const ps = "[System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null; $f = New-Object System.Windows.Forms.FolderBrowserDialog; $f.Description = 'Select a folder for your personal assistant context'; $f.ShowDialog() | Out-Null; $f.SelectedPath";
|
|
637
|
+
const { stdout } = await execAsync(`powershell -command "${ps}"`);
|
|
638
|
+
folderPath = stdout.trim();
|
|
639
|
+
} else {
|
|
640
|
+
res.status(400).json({ error: "Folder picker not supported on this platform" });
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (!folderPath) {
|
|
644
|
+
res.json({ cancelled: true });
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
res.json({ path: folderPath });
|
|
648
|
+
} catch (err) {
|
|
649
|
+
if (String(err).includes("User canceled") || String(err).includes("cancel")) {
|
|
650
|
+
res.json({ cancelled: true });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
res.status(500).json({ error: String(err) });
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
app.post("/api/gdrive/auth-start", async (req, res) => {
|
|
657
|
+
const { clientId, clientSecret } = req.body;
|
|
658
|
+
if (!clientId || !clientSecret) {
|
|
659
|
+
res.status(400).json({ error: "clientId and clientSecret are required" });
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
const { google } = await import("googleapis");
|
|
664
|
+
const redirectUri = `http://localhost:${port}/api/gdrive/callback`;
|
|
665
|
+
const oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
|
|
666
|
+
const authUrl = oauth2Client.generateAuthUrl({
|
|
667
|
+
access_type: "offline",
|
|
668
|
+
prompt: "consent",
|
|
669
|
+
scope: ["https://www.googleapis.com/auth/drive.file"]
|
|
670
|
+
});
|
|
671
|
+
gdriveAuthSession = { clientId, clientSecret, status: "pending" };
|
|
672
|
+
res.json({ authUrl });
|
|
673
|
+
} catch (err) {
|
|
674
|
+
res.status(500).json({ error: String(err) });
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
app.get("/api/gdrive/callback", async (req, res) => {
|
|
678
|
+
const { code } = req.query;
|
|
679
|
+
if (!code || !gdriveAuthSession) {
|
|
680
|
+
res.status(400).send("Invalid OAuth callback \u2014 no pending auth session.");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const { google } = await import("googleapis");
|
|
685
|
+
const redirectUri = `http://localhost:${port}/api/gdrive/callback`;
|
|
686
|
+
const oauth2Client = new google.auth.OAuth2(
|
|
687
|
+
gdriveAuthSession.clientId,
|
|
688
|
+
gdriveAuthSession.clientSecret,
|
|
689
|
+
redirectUri
|
|
690
|
+
);
|
|
691
|
+
const { tokens } = await oauth2Client.getToken(code);
|
|
692
|
+
gdriveAuthSession = {
|
|
693
|
+
...gdriveAuthSession,
|
|
694
|
+
status: "authenticated",
|
|
695
|
+
refreshToken: tokens.refresh_token ?? void 0,
|
|
696
|
+
accessToken: tokens.access_token ?? void 0
|
|
697
|
+
};
|
|
698
|
+
res.send(`<!DOCTYPE html><html><body>
|
|
699
|
+
<script>window.close();</script>
|
|
700
|
+
<p style="font-family:sans-serif;padding:2rem;color:#4ade80;">
|
|
701
|
+
\u2713 Authentication successful \u2014 you can close this tab and return to the setup.
|
|
702
|
+
</p>
|
|
703
|
+
</body></html>`);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
if (gdriveAuthSession) {
|
|
706
|
+
gdriveAuthSession.status = "error";
|
|
707
|
+
gdriveAuthSession.error = String(err);
|
|
708
|
+
}
|
|
709
|
+
res.status(500).send("Authentication failed: " + String(err));
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
app.get("/api/gdrive/auth-status", (_req, res) => {
|
|
713
|
+
if (!gdriveAuthSession) {
|
|
714
|
+
res.json({ status: "idle" });
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const { clientId: _cid, clientSecret: _csec, accessToken: _at, refreshToken: _rt, ...publicSession } = gdriveAuthSession;
|
|
718
|
+
res.json(publicSession);
|
|
719
|
+
});
|
|
720
|
+
app.post("/api/gdrive/create-folder", async (req, res) => {
|
|
721
|
+
if (!gdriveAuthSession || gdriveAuthSession.status !== "authenticated") {
|
|
722
|
+
res.status(400).json({ error: "Not authenticated. Please connect Google Drive first." });
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const { folderName = "Personal Assistant", parentFolderName = "" } = req.body;
|
|
726
|
+
try {
|
|
727
|
+
const { google } = await import("googleapis");
|
|
728
|
+
const redirectUri = `http://localhost:${port}/api/gdrive/callback`;
|
|
729
|
+
const oauth2Client = new google.auth.OAuth2(
|
|
730
|
+
gdriveAuthSession.clientId,
|
|
731
|
+
gdriveAuthSession.clientSecret,
|
|
732
|
+
redirectUri
|
|
733
|
+
);
|
|
734
|
+
oauth2Client.setCredentials({
|
|
735
|
+
access_token: gdriveAuthSession.accessToken,
|
|
736
|
+
refresh_token: gdriveAuthSession.refreshToken
|
|
737
|
+
});
|
|
738
|
+
const drive = google.drive({ version: "v3", auth: oauth2Client });
|
|
739
|
+
let parentId = "root";
|
|
740
|
+
let locationPath = "My Drive";
|
|
741
|
+
if (parentFolderName.trim()) {
|
|
742
|
+
const searchRes = await drive.files.list({
|
|
743
|
+
q: `name='${parentFolderName.trim()}' and mimeType='application/vnd.google-apps.folder' and trashed=false`,
|
|
744
|
+
fields: "files(id,name)",
|
|
745
|
+
spaces: "drive"
|
|
746
|
+
});
|
|
747
|
+
if (searchRes.data.files && searchRes.data.files.length > 0) {
|
|
748
|
+
parentId = searchRes.data.files[0].id;
|
|
749
|
+
} else {
|
|
750
|
+
const parentRes = await drive.files.create({
|
|
751
|
+
requestBody: {
|
|
752
|
+
name: parentFolderName.trim(),
|
|
753
|
+
mimeType: "application/vnd.google-apps.folder"
|
|
754
|
+
},
|
|
755
|
+
fields: "id"
|
|
756
|
+
});
|
|
757
|
+
parentId = parentRes.data.id;
|
|
758
|
+
}
|
|
759
|
+
locationPath = `My Drive / ${parentFolderName.trim()}`;
|
|
760
|
+
}
|
|
761
|
+
let folderId;
|
|
762
|
+
let resolvedFolderName = folderName.trim();
|
|
763
|
+
const existingSearch = await drive.files.list({
|
|
764
|
+
q: `name='${resolvedFolderName}' and '${parentId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
|
|
765
|
+
fields: "files(id,name)",
|
|
766
|
+
spaces: "drive"
|
|
767
|
+
});
|
|
768
|
+
if (existingSearch.data.files && existingSearch.data.files.length > 0) {
|
|
769
|
+
folderId = existingSearch.data.files[0].id;
|
|
770
|
+
resolvedFolderName = existingSearch.data.files[0].name;
|
|
771
|
+
} else {
|
|
772
|
+
const folderRes = await drive.files.create({
|
|
773
|
+
requestBody: {
|
|
774
|
+
name: resolvedFolderName,
|
|
775
|
+
mimeType: "application/vnd.google-apps.folder",
|
|
776
|
+
parents: [parentId]
|
|
777
|
+
},
|
|
778
|
+
fields: "id,name"
|
|
779
|
+
});
|
|
780
|
+
folderId = folderRes.data.id;
|
|
781
|
+
resolvedFolderName = folderRes.data.name;
|
|
782
|
+
}
|
|
783
|
+
const folderPath = `${locationPath} / ${resolvedFolderName}`;
|
|
784
|
+
const freshCredentials = await oauth2Client.getAccessToken();
|
|
785
|
+
const latestRefreshToken = oauth2Client.credentials.refresh_token ?? gdriveAuthSession.refreshToken;
|
|
786
|
+
gdriveAuthSession = {
|
|
787
|
+
...gdriveAuthSession,
|
|
788
|
+
status: "complete",
|
|
789
|
+
folderId,
|
|
790
|
+
folderName: resolvedFolderName,
|
|
791
|
+
folderPath,
|
|
792
|
+
refreshToken: latestRefreshToken,
|
|
793
|
+
accessToken: freshCredentials.token ?? void 0
|
|
794
|
+
};
|
|
795
|
+
res.json({
|
|
796
|
+
folderId,
|
|
797
|
+
folderName: resolvedFolderName,
|
|
798
|
+
folderPath,
|
|
799
|
+
refreshToken: latestRefreshToken
|
|
800
|
+
});
|
|
801
|
+
} catch (err) {
|
|
802
|
+
res.status(500).json({ error: String(err) });
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
app.get("/api/status", async (_req, res) => {
|
|
806
|
+
const storage = createLocalStorage(workspacePath);
|
|
807
|
+
const configManager = createConfigManager(storage);
|
|
808
|
+
try {
|
|
809
|
+
const config = await configManager.loadConfig();
|
|
810
|
+
res.json({ configured: true, config });
|
|
811
|
+
} catch {
|
|
812
|
+
res.json({ configured: false });
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
const server = app.listen(port, () => {
|
|
816
|
+
const url = `http://localhost:${port}`;
|
|
817
|
+
console.log(`Onboarding server running at ${url}`);
|
|
818
|
+
import("open").then((openModule) => openModule.default(url)).catch(() => {
|
|
819
|
+
console.log(`Open ${url} in your browser to continue.`);
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
process.on("SIGINT", () => {
|
|
823
|
+
console.log("\nShutting down onboarding server...");
|
|
824
|
+
server.close();
|
|
825
|
+
process.exit(0);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function getInlineOnboardingHtml() {
|
|
829
|
+
return `<!DOCTYPE html>
|
|
830
|
+
<html lang="en">
|
|
831
|
+
<head>
|
|
832
|
+
<meta charset="UTF-8">
|
|
833
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
834
|
+
<title>Personal Assistant - Setup</title>
|
|
835
|
+
<style>
|
|
836
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
837
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
|
|
838
|
+
.container { max-width: 640px; margin: 0 auto; padding: 2rem; }
|
|
839
|
+
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; color: #f8fafc; }
|
|
840
|
+
h2 { font-size: 1.2rem; margin-bottom: 1rem; color: #94a3b8; font-weight: 400; }
|
|
841
|
+
.step { display: none; }
|
|
842
|
+
.step.active { display: block; }
|
|
843
|
+
.progress { display: flex; gap: 0.5rem; margin-bottom: 2rem; }
|
|
844
|
+
.progress-dot { width: 2rem; height: 0.25rem; background: #334155; border-radius: 2px; transition: background 0.3s; }
|
|
845
|
+
.progress-dot.done { background: #3b82f6; }
|
|
846
|
+
.progress-dot.current { background: #60a5fa; }
|
|
847
|
+
label { display: block; margin-bottom: 0.5rem; color: #cbd5e1; font-size: 0.9rem; }
|
|
848
|
+
input, select, textarea { width: 100%; padding: 0.75rem; background: #1e293b; border: 1px solid #334155; border-radius: 0.5rem; color: #f8fafc; font-size: 1rem; margin-bottom: 1rem; }
|
|
849
|
+
input:focus, select:focus, textarea:focus { outline: none; border-color: #3b82f6; }
|
|
850
|
+
textarea { min-height: 200px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.85rem; }
|
|
851
|
+
button { padding: 0.75rem 1.5rem; border: none; border-radius: 0.5rem; font-size: 1rem; cursor: pointer; transition: all 0.2s; }
|
|
852
|
+
.btn-primary { background: #3b82f6; color: white; }
|
|
853
|
+
.btn-primary:hover { background: #2563eb; }
|
|
854
|
+
.btn-secondary { background: #334155; color: #e2e8f0; }
|
|
855
|
+
.btn-secondary:hover { background: #475569; }
|
|
856
|
+
.btn-group { display: flex; gap: 0.75rem; margin-top: 1.5rem; }
|
|
857
|
+
.checkbox-group { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem; }
|
|
858
|
+
.checkbox-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #1e293b; border: 1px solid #334155; border-radius: 0.375rem; cursor: pointer; }
|
|
859
|
+
.checkbox-item:hover { border-color: #475569; }
|
|
860
|
+
.checkbox-item input[type="checkbox"] { width: auto; margin: 0; }
|
|
861
|
+
.radio-group { display: flex; gap: 1rem; margin-bottom: 1rem; }
|
|
862
|
+
.radio-item { flex: 1; padding: 1rem; background: #1e293b; border: 2px solid #334155; border-radius: 0.5rem; cursor: pointer; text-align: center; transition: all 0.2s; }
|
|
863
|
+
.radio-item:hover { border-color: #475569; }
|
|
864
|
+
.radio-item.selected { border-color: #3b82f6; background: #1e3a5f; }
|
|
865
|
+
.success { text-align: center; padding: 3rem 0; }
|
|
866
|
+
.success h2 { color: #4ade80; font-size: 1.5rem; }
|
|
867
|
+
</style>
|
|
868
|
+
</head>
|
|
869
|
+
<body>
|
|
870
|
+
<div class="container">
|
|
871
|
+
<div class="progress" id="progress"></div>
|
|
872
|
+
|
|
873
|
+
<!-- Step 1: Identity -->
|
|
874
|
+
<div class="step active" data-step="0">
|
|
875
|
+
<h1>Welcome</h1>
|
|
876
|
+
<h2>Let's set up your personal assistant</h2>
|
|
877
|
+
<label>Your Name</label>
|
|
878
|
+
<input type="text" id="userName" placeholder="Jane Smith">
|
|
879
|
+
<label>Assistant Name</label>
|
|
880
|
+
<input type="text" id="assistantName" placeholder="Atlas" value="Atlas">
|
|
881
|
+
<label>Profile</label>
|
|
882
|
+
<select id="profileType">
|
|
883
|
+
<option value="software-engineer">Software Engineer</option>
|
|
884
|
+
<option value="product-manager">Product Manager</option>
|
|
885
|
+
<option value="engineering-manager">Engineering Manager</option>
|
|
886
|
+
<option value="devops">DevOps Engineer</option>
|
|
887
|
+
</select>
|
|
888
|
+
<div class="btn-group"><button class="btn-primary" onclick="nextStep()">Continue</button></div>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
<!-- Step 2: Responsibilities -->
|
|
892
|
+
<div class="step" data-step="1">
|
|
893
|
+
<h1>Responsibilities</h1>
|
|
894
|
+
<h2>Select your core responsibilities</h2>
|
|
895
|
+
<div class="checkbox-group" id="responsibilitiesGroup"></div>
|
|
896
|
+
<div class="btn-group">
|
|
897
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
898
|
+
<button class="btn-primary" onclick="nextStep()">Continue</button>
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
|
|
902
|
+
<!-- Step 3: Template Preview -->
|
|
903
|
+
<div class="step" data-step="2">
|
|
904
|
+
<h1>Context Template</h1>
|
|
905
|
+
<h2>Preview and customize your context template</h2>
|
|
906
|
+
<textarea id="templatePreview"></textarea>
|
|
907
|
+
<div class="btn-group">
|
|
908
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
909
|
+
<button class="btn-primary" onclick="nextStep()">Continue</button>
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
|
|
913
|
+
<!-- Step 4: Storage -->
|
|
914
|
+
<div class="step" data-step="3">
|
|
915
|
+
<h1>Storage</h1>
|
|
916
|
+
<h2>Where should your context be stored?</h2>
|
|
917
|
+
<div class="radio-group">
|
|
918
|
+
<div class="radio-item selected" onclick="selectStorage('local')" id="storage-local">
|
|
919
|
+
<strong>Local Workspace</strong><br><small>Files stored on this machine</small>
|
|
920
|
+
</div>
|
|
921
|
+
<div class="radio-item" onclick="selectStorage('gdrive')" id="storage-gdrive">
|
|
922
|
+
<strong>Google Drive</strong><br><small>Files stored in your Drive folder</small>
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
<div id="storage-local-fields">
|
|
926
|
+
<label>Storage path</label>
|
|
927
|
+
<div style="display:flex;gap:0.5rem;align-items:center;">
|
|
928
|
+
<input type="text" id="localPath" value=".personal-assistant" placeholder=".personal-assistant" style="flex:1;margin-bottom:0;">
|
|
929
|
+
<button class="btn-secondary" onclick="browseFolder()" id="browseBtn" style="white-space:nowrap;flex-shrink:0;">Browse</button>
|
|
930
|
+
</div>
|
|
931
|
+
<small style="color:#64748b;font-size:0.8rem;display:block;margin-top:0.4rem;margin-bottom:1rem;">Click Browse to open a folder picker, or type a path directly.</small>
|
|
932
|
+
</div>
|
|
933
|
+
<div id="storage-gdrive-fields" style="display:none;">
|
|
934
|
+
<p style="color:#f59e0b;font-size:0.85rem;margin-bottom:1rem;">
|
|
935
|
+
Google Drive OAuth is only supported in the full onboarding UI.<br>
|
|
936
|
+
Run <code>pnpm build</code> in <code>apps/onboarding-web</code> then restart <code>pacman init</code>.
|
|
937
|
+
</p>
|
|
938
|
+
</div>
|
|
939
|
+
<div style="background:#1c1917;border:1px solid #92400e;border-radius:0.5rem;padding:0.75rem 1rem;margin-bottom:1.5rem;font-size:0.85rem;color:#fbbf24;">
|
|
940
|
+
\u26A0\uFE0F Do not delete, move, or rename the folder structure created by this setup. Claude and the sync daemon rely on the exact file layout to load and update your context.
|
|
941
|
+
</div>
|
|
942
|
+
<div class="btn-group">
|
|
943
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
944
|
+
<button class="btn-primary" onclick="nextStep()">Continue</button>
|
|
945
|
+
</div>
|
|
946
|
+
</div>
|
|
947
|
+
|
|
948
|
+
<!-- Step 5: Integrations -->
|
|
949
|
+
<div class="step" data-step="4">
|
|
950
|
+
<h1>Integrations</h1>
|
|
951
|
+
<h2>Connect your tools (optional)</h2>
|
|
952
|
+
<div class="checkbox-group" id="integrationsGroup">
|
|
953
|
+
<label class="checkbox-item"><input type="checkbox" value="slack"> Slack</label>
|
|
954
|
+
<label class="checkbox-item"><input type="checkbox" value="gmail"> Gmail</label>
|
|
955
|
+
<label class="checkbox-item"><input type="checkbox" value="github"> GitHub</label>
|
|
956
|
+
<label class="checkbox-item"><input type="checkbox" value="gitlab"> GitLab</label>
|
|
957
|
+
<label class="checkbox-item"><input type="checkbox" value="gdrive"> Google Drive Docs</label>
|
|
958
|
+
<label class="checkbox-item"><input type="checkbox" value="gchat"> Google Chat</label>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="btn-group">
|
|
961
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
962
|
+
<button class="btn-primary" onclick="nextStep()">Continue</button>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
|
|
966
|
+
<!-- Step 6: Sync Schedule -->
|
|
967
|
+
<div class="step" data-step="5">
|
|
968
|
+
<h1>Sync Schedule</h1>
|
|
969
|
+
<h2>When should context be refreshed?</h2>
|
|
970
|
+
<label>Daily Sync Time</label>
|
|
971
|
+
<input type="time" id="syncTime" value="08:00">
|
|
972
|
+
<label>Timezone</label>
|
|
973
|
+
<select id="timezone">
|
|
974
|
+
<option value="America/New_York">Eastern Time (ET)</option>
|
|
975
|
+
<option value="America/Chicago">Central Time (CT)</option>
|
|
976
|
+
<option value="America/Denver">Mountain Time (MT)</option>
|
|
977
|
+
<option value="America/Los_Angeles">Pacific Time (PT)</option>
|
|
978
|
+
<option value="UTC">UTC</option>
|
|
979
|
+
<option value="Europe/London">London (GMT/BST)</option>
|
|
980
|
+
<option value="Europe/Berlin">Berlin (CET)</option>
|
|
981
|
+
<option value="Asia/Tokyo">Tokyo (JST)</option>
|
|
982
|
+
<option value="Asia/Kolkata">India (IST)</option>
|
|
983
|
+
<option value="Australia/Sydney">Sydney (AEST)</option>
|
|
984
|
+
</select>
|
|
985
|
+
<div class="checkbox-group">
|
|
986
|
+
<label class="checkbox-item"><input type="checkbox" id="manualSync" checked> Manual sync</label>
|
|
987
|
+
<label class="checkbox-item"><input type="checkbox" id="asyncUpdate" checked> Async updates</label>
|
|
988
|
+
</div>
|
|
989
|
+
<div class="btn-group">
|
|
990
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
991
|
+
<button class="btn-primary" onclick="nextStep()">Continue</button>
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
994
|
+
|
|
995
|
+
<!-- Step 7: Finish -->
|
|
996
|
+
<div class="step" data-step="6">
|
|
997
|
+
<h1>All Set!</h1>
|
|
998
|
+
<h2>Review and save your configuration</h2>
|
|
999
|
+
<div id="summary" style="background:#1e293b;padding:1rem;border-radius:0.5rem;margin-bottom:1rem;font-family:monospace;font-size:0.85rem;white-space:pre-wrap;"></div>
|
|
1000
|
+
<div class="btn-group">
|
|
1001
|
+
<button class="btn-secondary" onclick="prevStep()">Back</button>
|
|
1002
|
+
<button class="btn-primary" onclick="saveConfig()">Save & Finish</button>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
|
|
1006
|
+
<!-- Success -->
|
|
1007
|
+
<div class="step" data-step="7">
|
|
1008
|
+
<div class="success">
|
|
1009
|
+
<h2>Setup Complete!</h2>
|
|
1010
|
+
<p style="margin-top:1rem;color:#94a3b8;">Your personal assistant context has been initialized.</p>
|
|
1011
|
+
<p style="margin-top:1rem;color:#94a3b8;">Next steps:</p>
|
|
1012
|
+
<pre style="text-align:left;margin-top:1rem;background:#1e293b;padding:1rem;border-radius:0.5rem;font-size:0.85rem;">
|
|
1013
|
+
# Install Claude Code integration
|
|
1014
|
+
pacman claude install
|
|
1015
|
+
pacman mcp claude install
|
|
1016
|
+
|
|
1017
|
+
# Install Codex integration
|
|
1018
|
+
pacman codex install
|
|
1019
|
+
pacman mcp codex install
|
|
1020
|
+
|
|
1021
|
+
# Start the sync daemon
|
|
1022
|
+
pacman daemon
|
|
1023
|
+
|
|
1024
|
+
# Start the real-time Slack listener
|
|
1025
|
+
pacman slack listen
|
|
1026
|
+
|
|
1027
|
+
# Use in Claude Code
|
|
1028
|
+
/personal-assistant start <project>
|
|
1029
|
+
|
|
1030
|
+
# Use in Codex
|
|
1031
|
+
Ask Codex to load or refresh your Personal Assistant context for <project></pre>
|
|
1032
|
+
<p style="margin-top:1rem;color:#64748b;font-size:0.85rem;">Restart Claude Code or Codex after installing the MCP server.<br>If <code style="background:#1e293b;padding:0 4px;border-radius:3px;">pacman</code> is not found, link it first. The legacy alias <code style="background:#1e293b;padding:0 4px;border-radius:3px;">personal-assistant</code> also remains available:<br>
|
|
1033
|
+
<code style="background:#1e293b;padding:2px 6px;border-radius:3px;">pnpm setup && source ~/.zshrc && cd packages/cli && pnpm link --global</code></p>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
|
|
1038
|
+
<script>
|
|
1039
|
+
let currentStep = 0;
|
|
1040
|
+
const totalSteps = 7;
|
|
1041
|
+
let state = { storageMode: 'local', responsibilities: [] };
|
|
1042
|
+
|
|
1043
|
+
function updateProgress() {
|
|
1044
|
+
const bar = document.getElementById('progress');
|
|
1045
|
+
bar.innerHTML = '';
|
|
1046
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
1047
|
+
const dot = document.createElement('div');
|
|
1048
|
+
dot.className = 'progress-dot' + (i < currentStep ? ' done' : i === currentStep ? ' current' : '');
|
|
1049
|
+
bar.appendChild(dot);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function showStep(n) {
|
|
1054
|
+
document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
|
|
1055
|
+
document.querySelector('[data-step="' + n + '"]').classList.add('active');
|
|
1056
|
+
currentStep = n;
|
|
1057
|
+
updateProgress();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async function nextStep() {
|
|
1061
|
+
if (currentStep === 0) await loadResponsibilities();
|
|
1062
|
+
if (currentStep === 1) await loadTemplatePreview();
|
|
1063
|
+
if (currentStep === 5) updateSummary();
|
|
1064
|
+
showStep(currentStep + 1);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function prevStep() { showStep(currentStep - 1); }
|
|
1068
|
+
|
|
1069
|
+
async function loadResponsibilities() {
|
|
1070
|
+
const profileType = document.getElementById('profileType').value;
|
|
1071
|
+
const res = await fetch('/api/responsibilities/' + profileType);
|
|
1072
|
+
const data = await res.json();
|
|
1073
|
+
const group = document.getElementById('responsibilitiesGroup');
|
|
1074
|
+
group.innerHTML = data.responsibilities.map(r =>
|
|
1075
|
+
'<label class="checkbox-item"><input type="checkbox" value="' + r + '" checked> ' + r + '</label>'
|
|
1076
|
+
).join('');
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
async function loadTemplatePreview() {
|
|
1080
|
+
const profileType = document.getElementById('profileType').value;
|
|
1081
|
+
const name = document.getElementById('userName').value;
|
|
1082
|
+
const assistantName = document.getElementById('assistantName').value;
|
|
1083
|
+
const responsibilities = Array.from(document.querySelectorAll('#responsibilitiesGroup input:checked')).map(c => c.value);
|
|
1084
|
+
state.responsibilities = responsibilities;
|
|
1085
|
+
|
|
1086
|
+
const res = await fetch('/api/preview-template', {
|
|
1087
|
+
method: 'POST',
|
|
1088
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1089
|
+
body: JSON.stringify({ profileType, name, assistantName, responsibilities })
|
|
1090
|
+
});
|
|
1091
|
+
const data = await res.json();
|
|
1092
|
+
const preview = Object.entries(data.files).map(([f, c]) => '--- ' + f + ' ---\\n' + c).join('\\n\\n');
|
|
1093
|
+
document.getElementById('templatePreview').value = preview;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function selectStorage(mode) {
|
|
1097
|
+
state.storageMode = mode;
|
|
1098
|
+
document.querySelectorAll('.radio-item').forEach(r => r.classList.remove('selected'));
|
|
1099
|
+
document.getElementById('storage-' + mode).classList.add('selected');
|
|
1100
|
+
document.getElementById('storage-local-fields').style.display = mode === 'local' ? 'block' : 'none';
|
|
1101
|
+
document.getElementById('storage-gdrive-fields').style.display = mode === 'gdrive' ? 'block' : 'none';
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
async function browseFolder() {
|
|
1105
|
+
const btn = document.getElementById('browseBtn');
|
|
1106
|
+
btn.textContent = '\u2026';
|
|
1107
|
+
btn.disabled = true;
|
|
1108
|
+
try {
|
|
1109
|
+
const res = await fetch('/api/pick-folder');
|
|
1110
|
+
const data = await res.json();
|
|
1111
|
+
if (!data.cancelled && data.path) {
|
|
1112
|
+
document.getElementById('localPath').value = data.path;
|
|
1113
|
+
}
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
// ignore \u2014 user can type path manually
|
|
1116
|
+
} finally {
|
|
1117
|
+
btn.textContent = 'Browse';
|
|
1118
|
+
btn.disabled = false;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function updateSummary() {
|
|
1123
|
+
const config = buildConfig();
|
|
1124
|
+
document.getElementById('summary').textContent = JSON.stringify(config, null, 2);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function buildConfig() {
|
|
1128
|
+
const integrations = Array.from(document.querySelectorAll('#integrationsGroup input:checked')).map(c => ({
|
|
1129
|
+
type: c.value, enabled: true, credentials: {}, cursor: null, lastSyncAt: null
|
|
1130
|
+
}));
|
|
1131
|
+
|
|
1132
|
+
return {
|
|
1133
|
+
user: {
|
|
1134
|
+
name: document.getElementById('userName').value,
|
|
1135
|
+
assistantName: document.getElementById('assistantName').value,
|
|
1136
|
+
profileType: document.getElementById('profileType').value,
|
|
1137
|
+
responsibilities: state.responsibilities
|
|
1138
|
+
},
|
|
1139
|
+
storage: state.storageMode === 'local'
|
|
1140
|
+
? { mode: 'local', workspacePath: document.getElementById('localPath').value || '.personal-assistant' }
|
|
1141
|
+
: { mode: 'gdrive', folderId: '', folderName: 'Personal Assistant', cachePath: '.personal-assistant/cache' },
|
|
1142
|
+
integrations,
|
|
1143
|
+
sync: {
|
|
1144
|
+
dailySyncTime: document.getElementById('syncTime').value,
|
|
1145
|
+
timezone: document.getElementById('timezone').value,
|
|
1146
|
+
manualSyncEnabled: document.getElementById('manualSync').checked,
|
|
1147
|
+
asyncUpdateEnabled: document.getElementById('asyncUpdate').checked
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
async function saveConfig() {
|
|
1153
|
+
const config = buildConfig();
|
|
1154
|
+
const res = await fetch('/api/save', {
|
|
1155
|
+
method: 'POST',
|
|
1156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1157
|
+
body: JSON.stringify(config)
|
|
1158
|
+
});
|
|
1159
|
+
const data = await res.json();
|
|
1160
|
+
if (data.success) showStep(7);
|
|
1161
|
+
else alert('Error: ' + data.error);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
updateProgress();
|
|
1165
|
+
</script>
|
|
1166
|
+
</body>
|
|
1167
|
+
</html>`;
|
|
1168
|
+
}
|
|
1169
|
+
export {
|
|
1170
|
+
startOnboardingServer
|
|
1171
|
+
};
|
|
1172
|
+
//# sourceMappingURL=onboarding-server.js.map
|