@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.
Files changed (53) hide show
  1. package/README.md +39 -0
  2. package/dist/chunk-3QNXXON5.js +330 -0
  3. package/dist/chunk-3QNXXON5.js.map +1 -0
  4. package/dist/chunk-43PUZDIZ.js +148 -0
  5. package/dist/chunk-43PUZDIZ.js.map +1 -0
  6. package/dist/chunk-7D4SUZUM.js +38 -0
  7. package/dist/chunk-7D4SUZUM.js.map +1 -0
  8. package/dist/chunk-AYFIQNZ5.js +807 -0
  9. package/dist/chunk-AYFIQNZ5.js.map +1 -0
  10. package/dist/chunk-FH6ZHWGR.js +37 -0
  11. package/dist/chunk-FH6ZHWGR.js.map +1 -0
  12. package/dist/chunk-O6T35A4O.js +137 -0
  13. package/dist/chunk-O6T35A4O.js.map +1 -0
  14. package/dist/chunk-TRQIZP6Z.js +451 -0
  15. package/dist/chunk-TRQIZP6Z.js.map +1 -0
  16. package/dist/chunk-UWT6AFJB.js +471 -0
  17. package/dist/chunk-UWT6AFJB.js.map +1 -0
  18. package/dist/chunk-ZKKMIDRK.js +3923 -0
  19. package/dist/chunk-ZKKMIDRK.js.map +1 -0
  20. package/dist/daemon.d.ts +3 -0
  21. package/dist/daemon.js +141 -0
  22. package/dist/daemon.js.map +1 -0
  23. package/dist/dist-3PIJOFZ4.js +91 -0
  24. package/dist/dist-3PIJOFZ4.js.map +1 -0
  25. package/dist/dist-L76NGFFH.js +102 -0
  26. package/dist/dist-L76NGFFH.js.map +1 -0
  27. package/dist/dist-NV2YVVHI.js +178 -0
  28. package/dist/dist-NV2YVVHI.js.map +1 -0
  29. package/dist/dist-RMYCRZIU.js +41 -0
  30. package/dist/dist-RMYCRZIU.js.map +1 -0
  31. package/dist/dist-THLCZNOZ.js +14 -0
  32. package/dist/dist-THLCZNOZ.js.map +1 -0
  33. package/dist/dist-TWNHTXYH.js +95 -0
  34. package/dist/dist-TWNHTXYH.js.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +452 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/mcp-compat.d.ts +1 -0
  39. package/dist/mcp-compat.js +78 -0
  40. package/dist/mcp-compat.js.map +1 -0
  41. package/dist/onboarding-server.d.ts +3 -0
  42. package/dist/onboarding-server.js +1172 -0
  43. package/dist/onboarding-server.js.map +1 -0
  44. package/dist/provider-runtime.d.ts +11 -0
  45. package/dist/provider-runtime.js +10 -0
  46. package/dist/provider-runtime.js.map +1 -0
  47. package/dist/slack-listener.d.ts +49 -0
  48. package/dist/slack-listener.js +888 -0
  49. package/dist/slack-listener.js.map +1 -0
  50. package/dist/storage.d.ts +8 -0
  51. package/dist/storage.js +9 -0
  52. package/dist/storage.js.map +1 -0
  53. 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 &amp; 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 &lt;project&gt;
1029
+
1030
+ # Use in Codex
1031
+ Ask Codex to load or refresh your Personal Assistant context for &lt;project&gt;</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 &amp;&amp; source ~/.zshrc &amp;&amp; cd packages/cli &amp;&amp; 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