personal-ai 0.2.3 → 0.2.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"entry.mjs","names":["URL","extractTitle","console.spinner","console.spinner","console.spinner","console.spinner","console.bold","console.spinner","console.spinner","console.bold","console.dim","console.spinner","console.spinner","console.bold","console.spinner","console.dim","console.spinner","console.dim"],"sources":["../src/config/paths.ts","../src/auth/encryption.ts","../src/utils/console.ts","../src/auth/google-oauth.ts","../src/cli/register.auth.ts","../src/config/defaults.ts","../src/config/schema.ts","../src/config/io.ts","../src/utils/process.ts","../src/utils/slug.ts","../src/utils/frontmatter.ts","../src/raw/add.ts","../src/profile/compile.ts","../src/connectors/sanitize.ts","../src/connectors/mac/collectors.ts","../src/profile/index.ts","../src/cli/register.init.ts","../src/scraper/index.ts","../src/cli/register.add.ts","../src/llm/client.ts","../src/prompts/extract.ts","../src/distill/extract.ts","../src/vault/writer.ts","../src/memory/journal.ts","../src/distill/index.ts","../src/cli/register.distill.ts","../src/prompts/generate.ts","../src/generate/index.ts","../src/cli/register.generate.ts","../src/search/index.ts","../src/cli/register.search.ts","../src/cli/register.index.ts","../src/cli/register.import.ts","../src/cli/register.status.ts","../src/cli/register.reset.ts","../src/ask/system-prompt.ts","../src/ask/tools-meta.ts","../src/ask/tools-connector.ts","../src/ask/tools-pai.ts","../src/ask/tools.ts","../src/ask/agent.ts","../src/cli/register.ask.ts","../src/cli/register.context.ts","../src/cli/register.distribute.ts","../src/cli/register.profile.ts","../src/cli/register.log.ts","../src/cli/register.digest.ts","../src/cli/register.gaps.ts","../src/cli/command-registry.ts","../src/cli/build-program.ts","../src/entry.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\n\n/** Root data directory for pai */\nexport function getPaiHome(): string {\n return process.env.PAI_HOME ?? path.join(os.homedir(), \".pai\");\n}\n\nexport function getRawDir(): string {\n return path.join(getPaiHome(), \"raw\");\n}\n\nexport function getVaultDir(): string {\n return path.join(getPaiHome(), \"vault\");\n}\n\nexport function getMemoryDir(): string {\n return path.join(getPaiHome(), \"memory\");\n}\n\nexport function getWeeklyDir(): string {\n return path.join(getMemoryDir(), \"weekly\");\n}\n\n/** Get the journal file path for a given date (YYYY-MM-DD) */\nexport function getJournalPath(date: string): string {\n return path.join(getMemoryDir(), `${date}.md`);\n}\n\n/** Get today's date as YYYY-MM-DD */\nexport function getTodayDate(): string {\n return new Date().toISOString().split(\"T\")[0]!;\n}\n\nexport function getSkillsDir(): string {\n return path.join(getPaiHome(), \"skills\", \"profiles\");\n}\n\nexport function getConfigDir(): string {\n return path.join(getPaiHome(), \"config\");\n}\n\nexport function getConfigPath(): string {\n return path.join(getConfigDir(), \"pai.json5\");\n}\n\nexport function getProfilesPath(): string {\n return path.join(getConfigDir(), \"profiles.json5\");\n}\n\nexport function getPreferencesPath(): string {\n return path.join(getConfigDir(), \"preferences.md\");\n}\n\n/** Compiled user profile — the core output of pai */\nexport function getProfilePath(): string {\n return path.join(getPaiHome(), \"profile.md\");\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getPaiHome } from \"../config/paths.js\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32;\nconst IV_LENGTH = 16;\n\nexport class Encryption {\n private keyPath: string;\n private key: Buffer | null = null;\n\n constructor() {\n this.keyPath = path.join(getPaiHome(), \".encryption-key\");\n }\n\n async generateKey(): Promise<void> {\n const key = crypto.randomBytes(KEY_LENGTH);\n await fs.mkdir(path.dirname(this.keyPath), { recursive: true });\n await fs.writeFile(this.keyPath, key.toString(\"hex\"), { mode: 0o600 });\n this.key = key;\n }\n\n async loadKey(): Promise<void> {\n try {\n const keyHex = await fs.readFile(this.keyPath, \"utf-8\");\n this.key = Buffer.from(keyHex.trim(), \"hex\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await this.generateKey();\n } else {\n throw err;\n }\n }\n }\n\n private ensureKey(): Buffer {\n if (!this.key) {\n throw new Error(\"Encryption key not loaded. Call loadKey() first.\");\n }\n return this.key;\n }\n\n encrypt(data: string): string {\n const key = this.ensureKey();\n const iv = crypto.randomBytes(IV_LENGTH);\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\n let encrypted = cipher.update(data, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n const authTag = cipher.getAuthTag();\n return iv.toString(\"hex\") + \":\" + authTag.toString(\"hex\") + \":\" + encrypted;\n }\n\n decrypt(encryptedData: string): string {\n const key = this.ensureKey();\n const parts = encryptedData.split(\":\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid encrypted data format\");\n }\n const iv = Buffer.from(parts[0], \"hex\");\n const authTag = Buffer.from(parts[1], \"hex\");\n const encrypted = parts[2];\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n let decrypted = decipher.update(encrypted, \"hex\", \"utf8\");\n decrypted += decipher.final(\"utf8\");\n return decrypted;\n }\n\n async encryptFile(filePath: string, data: unknown): Promise<void> {\n const jsonData = JSON.stringify(data, null, 2);\n const encrypted = this.encrypt(jsonData);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, encrypted, { mode: 0o600 });\n }\n\n async decryptFile(filePath: string): Promise<unknown> {\n const encrypted = await fs.readFile(filePath, \"utf-8\");\n const decrypted = this.decrypt(encrypted);\n return JSON.parse(decrypted) as unknown;\n }\n}\n\nexport const encryption = new Encryption();\n","import chalk from \"chalk\";\nimport ora, { type Ora } from \"ora\";\n\nexport function log(message: string): void {\n // eslint-disable-next-line no-console\n console.log(message);\n}\n\nexport function info(message: string): void {\n // eslint-disable-next-line no-console\n console.log(chalk.blue(\"ℹ\"), message);\n}\n\nexport function success(message: string): void {\n // eslint-disable-next-line no-console\n console.log(chalk.green(\"✓\"), message);\n}\n\nexport function warn(message: string): void {\n // eslint-disable-next-line no-console\n console.warn(chalk.yellow(\"⚠\"), message);\n}\n\nexport function error(message: string): void {\n // eslint-disable-next-line no-console\n console.error(chalk.red(\"✗\"), message);\n}\n\nexport function spinner(text: string): Ora {\n return ora({ text, color: \"cyan\" }).start();\n}\n\nexport function dim(message: string): string {\n return chalk.dim(message);\n}\n\nexport function bold(message: string): string {\n return chalk.bold(message);\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport http from \"node:http\";\nimport path from \"node:path\";\nimport { URL } from \"node:url\";\nimport { google } from \"googleapis\";\nimport open from \"open\";\nimport { getPaiHome } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\nimport { encryption } from \"./encryption.js\";\n\nconst SCOPES = [\n \"https://www.googleapis.com/auth/gmail.readonly\",\n \"https://www.googleapis.com/auth/gmail.send\",\n \"https://www.googleapis.com/auth/calendar.readonly\",\n];\n\nconst REDIRECT_URI = \"http://localhost:8888/callback\";\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Embedded OAuth client credentials for Desktop/CLI app.\n * Google explicitly states that for \"installed\" (desktop) applications,\n * the client_secret is NOT treated as a secret.\n * Users can override by placing their own client_secret.json in ~/.pai/credentials/.\n */\nconst DEFAULT_CLIENT_SECRETS = {\n installed: {\n client_id: \"973253759191-ihev26p7n9t1faksavdei36fbii46js7.apps.googleusercontent.com\",\n project_id: \"pinai-428200\",\n auth_uri: \"https://accounts.google.com/o/oauth2/auth\",\n token_uri: \"https://oauth2.googleapis.com/token\",\n auth_provider_x509_cert_url: \"https://www.googleapis.com/oauth2/v1/certs\",\n client_secret: \"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\",\n redirect_uris: [\"http://localhost\"],\n },\n} as const;\n\ntype OAuth2Client = InstanceType<typeof google.auth.OAuth2>;\n\nexport class GoogleOAuth {\n private oauth2Client: OAuth2Client | null = null;\n private credentialsPath: string;\n private clientSecretsPath: string;\n\n constructor() {\n const baseDir = path.join(getPaiHome(), \"credentials\");\n this.credentialsPath = path.join(baseDir, \"google-oauth.json.enc\");\n this.clientSecretsPath = path.join(baseDir, \"client_secret.json\");\n }\n\n async init(): Promise<void> {\n // Resolve client secrets with fallback chain:\n // 1. ~/.pai/credentials/client_secret.json (user override)\n // 2. cwd/credentials/client_secret.json (dev repo)\n // 3. Embedded default (bundled with npm package)\n let clientSecrets: {\n installed?: { client_id: string; client_secret: string };\n web?: { client_id: string; client_secret: string };\n } | null = null;\n\n // Try user-provided file first\n try {\n const content = await fs.readFile(this.clientSecretsPath, \"utf-8\");\n clientSecrets = JSON.parse(content);\n } catch {\n // Try copying from cwd (dev scenario)\n const fromCwd = path.join(process.cwd(), \"credentials\", \"client_secret.json\");\n try {\n const content = await fs.readFile(fromCwd, \"utf-8\");\n clientSecrets = JSON.parse(content);\n // Also persist to ~/.pai/credentials/ for future use\n await fs.mkdir(path.dirname(this.clientSecretsPath), { recursive: true });\n await fs.copyFile(fromCwd, this.clientSecretsPath);\n console.info(`Copied credentials from ${fromCwd} to ~/.pai/credentials/`);\n } catch {\n // Fall through to embedded default\n }\n }\n\n // Fallback to embedded default\n if (!clientSecrets) {\n clientSecrets = DEFAULT_CLIENT_SECRETS;\n }\n\n try {\n const { client_id, client_secret } =\n clientSecrets.installed ?? clientSecrets.web ?? {};\n\n if (!client_id || !client_secret) {\n throw new Error(\"Missing client_id or client_secret in client_secret.json\");\n }\n\n this.oauth2Client = new google.auth.OAuth2(\n client_id,\n client_secret,\n REDIRECT_URI,\n );\n\n await this.loadCredentials();\n } catch {\n console.warn(\"OAuth client initialization failed. Run: pai auth google\");\n }\n }\n\n private async loadCredentials(): Promise<void> {\n try {\n await encryption.loadKey();\n const credentials = await encryption.decryptFile(this.credentialsPath);\n this.oauth2Client!.setCredentials(credentials as Record<string, unknown>);\n\n this.oauth2Client!.on(\"tokens\", (tokens: unknown) => {\n if (tokens && typeof tokens === \"object\" && \"refresh_token\" in tokens && tokens.refresh_token) {\n this.saveCredentials(tokens);\n }\n });\n } catch {\n // No existing credentials\n }\n }\n\n private async saveCredentials(credentials: unknown): Promise<void> {\n await encryption.loadKey();\n await encryption.encryptFile(this.credentialsPath, credentials);\n }\n\n async authorize(): Promise<void> {\n if (!this.oauth2Client) {\n throw new Error(\n \"OAuth client not initialized. Place client_secret.json in ~/.pai/credentials/ and run again.\",\n );\n }\n\n const state = crypto.randomBytes(16).toString(\"hex\");\n const authUrl = this.oauth2Client.generateAuthUrl({\n access_type: \"offline\",\n scope: SCOPES,\n prompt: \"consent\",\n state,\n });\n\n console.log(\"\\nPlease visit this URL to authorize the application:\\n\");\n console.log(authUrl);\n console.log(\"\\nOpening browser...\\n\");\n\n await open(authUrl);\n\n const code = await this.startCallbackServer(state);\n const { tokens } = await this.oauth2Client.getToken(code);\n this.oauth2Client.setCredentials(tokens);\n await this.saveCredentials(tokens);\n\n console.success(\"Google OAuth authorization successful.\");\n }\n\n private startCallbackServer(expectedState: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = http.createServer((req, res) => {\n if (req.url?.startsWith(\"/callback\")) {\n const url = new URL(req.url ?? \"\", `http://${req.headers.host}`);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n\n if (code && state === expectedState) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>\",\n );\n server.close();\n resolve(code);\n } else {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Authorization failed: Invalid callback parameters\");\n server.close();\n reject(new Error(\"Authorization failed: invalid callback parameters\"));\n }\n }\n });\n\n server.listen(8888, () => {});\n\n server.on(\"error\", (err) => {\n reject(err);\n });\n\n const timeout = setTimeout(() => {\n server.close();\n reject(\n new Error(\n \"Authorization timed out. Please retry: pai auth google\",\n ),\n );\n }, CALLBACK_TIMEOUT_MS);\n\n server.on(\"close\", () => {\n clearTimeout(timeout);\n });\n });\n }\n\n getClient(): OAuth2Client {\n if (!this.oauth2Client) {\n throw new Error(\"OAuth client not initialized\");\n }\n return this.oauth2Client;\n }\n\n /** Returns true if user has valid credentials (access_token or refresh_token). */\n isAuthenticated(): boolean {\n if (!this.oauth2Client) return false;\n const creds = this.oauth2Client.credentials;\n return Boolean(creds.access_token || creds.refresh_token);\n }\n\n async ensureAuthenticated(): Promise<void> {\n if (!this.oauth2Client || !this.oauth2Client.credentials.access_token) {\n throw new Error(\"Not authenticated. Run: pai auth google\");\n }\n\n const now = Date.now();\n const expiry = this.oauth2Client.credentials.expiry_date;\n if (expiry != null && expiry < now) {\n await this.oauth2Client.refreshAccessToken();\n }\n }\n}\n\nexport const googleOAuth = new GoogleOAuth();\n","import type { Command } from \"commander\";\nimport { encryption } from \"../auth/encryption.js\";\nimport { googleOAuth } from \"../auth/google-oauth.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerAuthCommand(program: Command): void {\n program\n .command(\"auth\")\n .description(\"Authenticate with Google (for gmail/calendar connectors)\")\n .argument(\"<provider>\", \"Provider (google)\")\n .action(async (provider: string) => {\n if (provider !== \"google\") {\n console.error('Only \"google\" provider is supported.');\n process.exit(1);\n }\n\n try {\n await encryption.loadKey();\n await googleOAuth.init();\n await googleOAuth.authorize();\n console.success(\"Authentication successful.\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Authentication failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","/** Default content for pai.json5 */\nexport const DEFAULT_CONFIG = `{\n // pai configuration\n version: \"0.1\",\n\n qmd: {\n rawCollection: \"raw\",\n vaultCollection: \"vault\",\n },\n\n llm: {\n apiKeyEnv: \"OPENAI_API_KEY\",\n baseUrl: \"\", // leave empty for OpenAI, or set for compatible services\n cheapModel: \"gpt-4o-mini\",\n expensiveModel: \"gpt-4o\",\n },\n\n scraper: {\n timeout: 30000,\n },\n\n vault: {\n maxFileTokens: 4000,\n warnFileTokens: 3000,\n },\n}\n`;\n\n/** Default content for preferences.md — injected into ALL LLM calls */\nexport const DEFAULT_PREFERENCES = `# User Preferences\n\nThese preferences are automatically included in every AI operation (triage, distill, generate).\nEdit this file directly — changes take effect on the next pai command.\n\n## Language\n\n- My native language is: 中文 (Chinese)\n- All distilled knowledge and vault content should prefer: 中文\n- Raw data should be preserved in its original language\n- SKILL.md profiles should be written in the same language as the vault content\n\n## Output Style\n\n- Be concise and actionable — no filler\n- Use bullet points for experience entries\n- Preserve technical terms in English (e.g. API, CORS, TypeScript)\n`;\n\n/** Default content for profiles.json5 */\nexport const DEFAULT_PROFILES = `{\n profiles: {\n \"coding-assistant\": {\n scope: [\n \"vault/coding/**\",\n \"vault/preferences/**\",\n \"vault/context/**\",\n ],\n maxLines: 60,\n },\n\n \"full-context\": {\n scope: [\"vault/**\"],\n maxLines: 120,\n },\n\n \"life-assistant\": {\n scope: [\n \"vault/life/**\",\n \"vault/work/**\",\n \"vault/preferences/**\",\n \"vault/context/**\",\n ],\n maxLines: 60,\n },\n },\n}\n`;\n","import { z } from \"zod\";\n\nexport const QmdConfigSchema = z.object({\n rawCollection: z.string().default(\"raw\"),\n vaultCollection: z.string().default(\"vault\"),\n});\n\nexport const LlmConfigSchema = z.object({\n apiKeyEnv: z.string().default(\"OPENAI_API_KEY\"),\n baseUrl: z.string().optional(),\n cheapModel: z.string().default(\"gpt-4o-mini\"),\n expensiveModel: z.string().default(\"gpt-4o\"),\n});\n\nexport const ScraperConfigSchema = z.object({\n timeout: z.number().default(30_000),\n});\n\nexport const VaultConfigSchema = z.object({\n maxFileTokens: z.number().default(4000),\n warnFileTokens: z.number().default(3000),\n});\n\nexport const PaiConfigSchema = z\n .object({\n version: z.string().default(\"0.1\"),\n qmd: QmdConfigSchema.default(() => QmdConfigSchema.parse({})),\n llm: LlmConfigSchema.default(() => LlmConfigSchema.parse({})),\n scraper: ScraperConfigSchema.default(() => ScraperConfigSchema.parse({})),\n vault: VaultConfigSchema.default(() => VaultConfigSchema.parse({})),\n })\n .strict();\n\nexport type PaiConfig = z.infer<typeof PaiConfigSchema>;\n","import fs from \"node:fs/promises\";\nimport JSON5 from \"json5\";\nimport { PaiConfigSchema, type PaiConfig } from \"./schema.js\";\nimport { getConfigPath, getProfilesPath } from \"./paths.js\";\nimport type { ProfilesConfig } from \"../types.js\";\n\n/** Load and validate pai.json5 config */\nexport async function loadConfig(): Promise<PaiConfig> {\n const configPath = getConfigPath();\n try {\n const raw = await fs.readFile(configPath, \"utf-8\");\n const parsed = JSON5.parse(raw);\n return PaiConfigSchema.parse(parsed);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // Return defaults if config file doesn't exist\n return PaiConfigSchema.parse({});\n }\n throw err;\n }\n}\n\n/** Load profiles.json5 */\nexport async function loadProfiles(): Promise<ProfilesConfig> {\n const profilesPath = getProfilesPath();\n try {\n const raw = await fs.readFile(profilesPath, \"utf-8\");\n return JSON5.parse(raw) as ProfilesConfig;\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { profiles: {} };\n }\n throw err;\n }\n}\n\n/** Write config file (with JSON5 formatting) */\nexport async function saveConfig(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, \"utf-8\");\n}\n","import { execFile } from \"node:child_process\";\n\n/** Execute QMD CLI command and return stdout */\nexport async function execQmd(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\"qmd\", args, { encoding: \"utf-8\", timeout: 60_000 }, (err, stdout, stderr) => {\n if (err) {\n const msg = stderr?.trim() || err.message;\n reject(new Error(`qmd ${args[0]} failed: ${msg}`));\n } else {\n resolve(stdout);\n }\n });\n });\n}\n\n/** Check if QMD is available on PATH */\nexport async function isQmdAvailable(): Promise<boolean> {\n try {\n await execQmd([\"status\"]);\n return true;\n } catch {\n return false;\n }\n}\n","/** Generate a URL-safe slug from a title string */\nexport function generateSlug(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\") // remove special chars\n .replace(/\\s+/g, \"-\") // spaces to hyphens\n .replace(/-+/g, \"-\") // collapse multiple hyphens\n .replace(/^-|-$/g, \"\") // trim leading/trailing hyphens\n .slice(0, 50); // max 50 chars\n}\n\n/** Generate a timestamped filename: YYYY-MM-DDTHH-MM-{slug}.md */\nexport function generateRawFilename(title: string): string {\n const now = new Date();\n const ts = now\n .toISOString()\n .replace(/:\\d{2}\\.\\d{3}Z$/, \"\")\n .replace(/:/g, \"-\");\n const slug = generateSlug(title) || \"untitled\";\n return `${ts}-${slug}.md`;\n}\n","import matter from \"gray-matter\";\nimport type { RawFrontmatter } from \"../types.js\";\n\n/** Parse a markdown file with frontmatter */\nexport function parseFrontmatter<T = Record<string, unknown>>(\n content: string,\n): { data: T; content: string } {\n const result = matter(content);\n return { data: result.data as T, content: result.content };\n}\n\n/** Stringify data + content into a frontmatter markdown string */\nexport function stringifyFrontmatter(\n data: Record<string, unknown>,\n content: string,\n): string {\n return matter.stringify(content, data);\n}\n\n/** Create a raw file with proper frontmatter */\nexport function createRawFile(\n frontmatter: RawFrontmatter,\n title: string,\n body: string,\n): string {\n const content = `\\n# ${title}\\n\\n${body}\\n`;\n return stringifyFrontmatter(frontmatter as unknown as Record<string, unknown>, content);\n}\n\n/** Update frontmatter fields in a raw file */\nexport function updateRawFrontmatter(\n fileContent: string,\n updates: Partial<RawFrontmatter>,\n): string {\n const { data, content } = parseFrontmatter<RawFrontmatter>(fileContent);\n const updated = { ...data, ...updates };\n return stringifyFrontmatter(updated as unknown as Record<string, unknown>, content);\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getRawDir } from \"../config/paths.js\";\nimport { generateRawFilename } from \"../utils/slug.js\";\nimport { createRawFile, parseFrontmatter } from \"../utils/frontmatter.js\";\nimport type { CollectorResult, RawFrontmatter } from \"../types.js\";\n\n/** Add plain text content to raw/local/ */\nexport async function addText(\n content: string,\n source: string = \"local\",\n): Promise<string> {\n const title = extractTitle(content);\n const filename = generateRawFilename(title);\n const dir = path.join(getRawDir(), \"local\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return filePath;\n}\n\n/** Add a local file's content to raw/local/ */\nexport async function addFile(\n filePath: string,\n source: string = \"local\",\n): Promise<string> {\n const content = await fs.readFile(filePath, \"utf-8\");\n const basename = path.basename(filePath, path.extname(filePath));\n const filename = generateRawFilename(basename);\n const dir = path.join(getRawDir(), \"local\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, basename, content);\n const outPath = path.join(dir, filename);\n await fs.writeFile(outPath, fileContent, \"utf-8\");\n return outPath;\n}\n\n/** Add URL-scraped content to raw/web/ */\nexport async function addUrl(\n url: string,\n title: string,\n markdown: string,\n): Promise<string> {\n const filename = generateRawFilename(title);\n const dir = path.join(getRawDir(), \"web\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source: \"web\",\n timestamp: new Date().toISOString(),\n url,\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, markdown);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return filePath;\n}\n\n/**\n * Add or update a connector-scanned entry to raw/connector/{name}/.\n * Uses scan_id in frontmatter for dedup:\n * - If no existing file with same scan_id → create new\n * - If existing file with same content body → skip (\"skipped\")\n * - If existing file with different content → overwrite (\"updated\")\n * Returns \"created\" | \"updated\" | \"skipped\".\n */\nexport async function addConnectorEntry(\n connectorName: string,\n entry: CollectorResult,\n): Promise<\"created\" | \"updated\" | \"skipped\"> {\n const dir = path.join(getRawDir(), \"connector\", connectorName);\n await fs.mkdir(dir, { recursive: true });\n\n const scanId = `${connectorName}/${entry.id}`;\n\n // Check for existing file with same scan_id\n const existingPath = await findByScanId(dir, scanId);\n\n const fm: RawFrontmatter = {\n source: `connector/${connectorName}`,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n scan_id: scanId,\n };\n\n if (existingPath) {\n // Compare content body\n const oldRaw = await fs.readFile(existingPath, \"utf-8\");\n const { content: oldBody } = parseFrontmatter(oldRaw);\n const newBody = `\\n# ${entry.title}\\n\\n${entry.content}\\n`;\n\n if (oldBody.trim() === newBody.trim()) {\n return \"skipped\";\n }\n\n // Overwrite with updated content (preserve filename)\n const fileContent = createRawFile(fm, entry.title, entry.content);\n await fs.writeFile(existingPath, fileContent, \"utf-8\");\n return \"updated\";\n }\n\n // Create new entry\n const filename = generateRawFilename(entry.id);\n const fileContent = createRawFile(fm, entry.title, entry.content);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return \"created\";\n}\n\n/** Find a raw file in a directory whose frontmatter scan_id matches */\nasync function findByScanId(dir: string, scanId: string): Promise<string | null> {\n try {\n const entries = await fs.readdir(dir);\n for (const name of entries) {\n if (!name.endsWith(\".md\")) continue;\n const filePath = path.join(dir, name);\n const content = await fs.readFile(filePath, \"utf-8\");\n if (content.includes(`scan_id: ${scanId}`) || content.includes(`scan_id: '${scanId}'`)) {\n return filePath;\n }\n }\n } catch {\n // Directory doesn't exist or read error\n }\n return null;\n}\n\n/** List all pending raw files (status: pending) */\nexport async function listPending(): Promise<string[]> {\n const rawDir = getRawDir();\n const pending: string[] = [];\n\n const subdirs = [\"local\", \"web\", \"connector\"];\n for (const sub of subdirs) {\n const dir = path.join(rawDir, sub);\n try {\n const entries = await walkDir(dir);\n for (const entry of entries) {\n if (entry.endsWith(\".md\")) {\n const content = await fs.readFile(entry, \"utf-8\");\n if (content.includes(\"status: pending\")) {\n pending.push(entry);\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n return pending;\n}\n\n/** List all raw files regardless of status */\nexport async function listAll(): Promise<string[]> {\n const rawDir = getRawDir();\n const files: string[] = [];\n\n const subdirs = [\"local\", \"web\", \"connector\"];\n for (const sub of subdirs) {\n const dir = path.join(rawDir, sub);\n try {\n const entries = await walkDir(dir);\n files.push(...entries.filter((e) => e.endsWith(\".md\")));\n } catch {\n // Directory might not exist\n }\n }\n return files;\n}\n\n/** Recursively walk a directory and return file paths */\nasync function walkDir(dir: string): Promise<string[]> {\n const results: string[] = [];\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await walkDir(fullPath)));\n } else {\n results.push(fullPath);\n }\n }\n } catch {\n // Ignore errors (dir doesn't exist, etc.)\n }\n return results;\n}\n\n/** Extract a title from content (first line or first N words) */\nfunction extractTitle(content: string): string {\n const firstLine = content.split(\"\\n\")[0]?.trim() ?? \"\";\n // Remove markdown heading syntax\n const cleaned = firstLine.replace(/^#+\\s*/, \"\");\n if (cleaned.length > 0 && cleaned.length <= 100) {\n return cleaned;\n }\n // Fall back to first 8 words\n return content.split(/\\s+/).slice(0, 8).join(\" \").slice(0, 100) || \"untitled\";\n}\n","/**\n * Profile compiler — transforms raw CollectorResult[] into a structured profile.md.\n * Zero LLM dependency: pure template + data formatting.\n */\n\nimport type { CollectorResult } from \"../types.js\";\n\n/** Mapping from collector IDs to profile sections */\ninterface SectionDef {\n heading: string;\n /** Collector IDs that feed this section (in order) */\n collectors: string[];\n}\n\nconst PROFILE_SECTIONS: SectionDef[] = [\n {\n heading: \"Identity\",\n collectors: [\"identity-profile\", \"github-profile\"],\n },\n {\n heading: \"Environment & Tools\",\n collectors: [\"dev-environment\", \"dev-preferences\"],\n },\n {\n heading: \"Work Style & Habits\",\n collectors: [\"shell-habits\", \"coding-rules\"],\n },\n {\n heading: \"Active Projects & Recent Focus\",\n collectors: [\"active-projects\", \"recent-focus\"],\n },\n {\n heading: \"Digital Footprint\",\n collectors: [\"browser-bookmarks\", \"browser-domains\", \"productivity-setup\"],\n },\n {\n heading: \"Registry & Cloud Accounts\",\n collectors: [\"social-profiles\"],\n },\n {\n heading: \"Context\",\n collectors: [\"calendar-context\", \"file-organization\"],\n },\n];\n\n/**\n * Compile CollectorResult[] into a structured profile markdown string.\n * No LLM involved — pure formatting.\n */\nexport function compileProfile(results: CollectorResult[]): string {\n const index = new Map<string, CollectorResult>();\n for (const r of results) {\n index.set(r.id, r);\n }\n\n const sections: string[] = [\"# Personal Profile\", \"\"];\n\n for (const section of PROFILE_SECTIONS) {\n const parts: string[] = [];\n\n for (const cid of section.collectors) {\n const result = index.get(cid);\n if (result?.content?.trim()) {\n parts.push(result.content.trim());\n }\n }\n\n if (parts.length === 0) continue;\n\n sections.push(`## ${section.heading}`, \"\");\n sections.push(parts.join(\"\\n\\n\"));\n sections.push(\"\");\n }\n\n // Append any collectors not covered by the predefined sections\n const coveredIds = new Set(PROFILE_SECTIONS.flatMap((s) => s.collectors));\n const extras: string[] = [];\n\n for (const r of results) {\n if (!coveredIds.has(r.id) && r.content?.trim()) {\n extras.push(`## ${r.title}`, \"\", r.content.trim(), \"\");\n }\n }\n\n if (extras.length > 0) {\n sections.push(...extras);\n }\n\n // Footer\n sections.push(\"---\");\n sections.push(`Last updated: ${new Date().toISOString()}`);\n sections.push(\"\");\n\n return sections.join(\"\\n\");\n}\n\n/**\n * Generate a compact profile summary suitable for agent injection.\n * Strips verbose sub-sections and keeps high-signal data only.\n */\nexport function compileCompactProfile(results: CollectorResult[]): string {\n const index = new Map<string, CollectorResult>();\n for (const r of results) {\n index.set(r.id, r);\n }\n\n const parts: string[] = [\"# Personal Profile (Compact)\", \"\"];\n\n // Identity — always include in full\n const identity = index.get(\"identity-profile\");\n if (identity?.content?.trim()) {\n parts.push(identity.content.trim(), \"\");\n }\n\n // Dev environment — keep runtime versions and package managers\n const devEnv = index.get(\"dev-environment\");\n if (devEnv?.content?.trim()) {\n parts.push(devEnv.content.trim(), \"\");\n }\n\n // Active projects — keep repo list\n const projects = index.get(\"active-projects\");\n if (projects?.content?.trim()) {\n parts.push(projects.content.trim(), \"\");\n }\n\n // Shell habits — keep top commands only\n const habits = index.get(\"shell-habits\");\n if (habits?.content?.trim()) {\n // Take only the first 15 lines (header + top commands)\n const lines = habits.content.trim().split(\"\\n\");\n parts.push(lines.slice(0, 17).join(\"\\n\"), \"\");\n }\n\n parts.push(\"---\");\n parts.push(`Last updated: ${new Date().toISOString()}`);\n parts.push(\"\");\n\n return parts.join(\"\\n\");\n}\n","/**\n * Security sanitization utilities for connector-scanned content.\n * Strips secrets, credentials, and sensitive paths before writing to raw layer.\n */\n\n/** Pattern matching shell export lines containing secrets */\nconst SECRET_EXPORT_RE =\n /^(\\s*export\\s+)\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|PASSWD)\\w*\\s*=.*/i;\n\n/** Pattern matching lines that look like inline secret assignments (no export) */\nconst SECRET_ASSIGN_RE =\n /^\\s*\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|PASSWD)\\w*\\s*=\\s*[\"']?\\S+/i;\n\n/**\n * Strip secret export/assignment lines from shell config content.\n * Preserves comments and non-secret lines intact.\n */\nexport function stripSecretExports(content: string): string {\n return content\n .split(\"\\n\")\n .map((line) => {\n if (SECRET_EXPORT_RE.test(line) || SECRET_ASSIGN_RE.test(line)) {\n return \"# [REDACTED by pai — secret removed]\";\n }\n return line;\n })\n .join(\"\\n\");\n}\n\n/**\n * Strip [credential] sections from git config content.\n * Removes the section header and all lines until the next section.\n */\nexport function stripGitCredentials(content: string): string {\n const lines = content.split(\"\\n\");\n const result: string[] = [];\n let inCredentialSection = false;\n\n for (const line of lines) {\n // Detect section headers like [credential] or [credential \"...\"]\n if (/^\\s*\\[credential/i.test(line)) {\n inCredentialSection = true;\n result.push(\"# [REDACTED by pai — credential section removed]\");\n continue;\n }\n // New section starts → exit credential section\n if (inCredentialSection && /^\\s*\\[/.test(line)) {\n inCredentialSection = false;\n }\n if (!inCredentialSection) {\n result.push(line);\n }\n }\n\n return result.join(\"\\n\");\n}\n\n/**\n * Strip IdentityFile lines from SSH config.\n * Keeps Host, HostName, User, Port — removes key paths.\n */\nexport function stripSshIdentityFiles(content: string): string {\n return content\n .split(\"\\n\")\n .map((line) => {\n if (/^\\s*IdentityFile\\s/i.test(line)) {\n return \" # [REDACTED by pai — IdentityFile removed]\";\n }\n return line;\n })\n .join(\"\\n\");\n}\n","/**\n * Mac system context collectors.\n * Each collector gathers a specific category of personalized context\n * from the local macOS environment and returns a CollectorResult.\n */\n\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { CollectorResult } from \"../../types.js\";\nimport {\n stripSecretExports,\n stripGitCredentials,\n stripSshIdentityFiles,\n} from \"../sanitize.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Run a shell command and return stdout (empty string on failure) */\nasync function exec(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve) => {\n execFile(cmd, args, { encoding: \"utf-8\", timeout: 15_000 }, (err, stdout) => {\n resolve(err ? \"\" : stdout.trim());\n });\n });\n}\n\n/** Read a file, return empty string on failure */\nasync function readSafe(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\n/** Check if path exists */\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run osascript and return output */\nasync function osascript(script: string): Promise<string> {\n return exec(\"osascript\", [\"-e\", script]);\n}\n\n/** Run defaults read and return output */\nasync function defaultsRead(...args: string[]): Promise<string> {\n return exec(\"defaults\", [\"read\", ...args]);\n}\n\nconst HOME = os.homedir();\n\n// ---------------------------------------------------------------------------\n// 1. Identity Profile\n// ---------------------------------------------------------------------------\n\nexport async function collectIdentityProfile(): Promise<CollectorResult> {\n const lines: string[] = [\"## User Identity\"];\n\n // macOS username\n const username = await exec(\"id\", [\"-un\"]);\n if (username) lines.push(`- macOS Username: ${username}`);\n\n // Real name from directory service\n const realName = await exec(\"dscl\", [\".\", \"-read\", `/Users/${username}`, \"RealName\"]);\n const nameMatch = realName.split(\"\\n\").find((l) => l.trim() && !l.includes(\"RealName\"));\n if (nameMatch?.trim()) lines.push(`- Real Name (local): ${nameMatch.trim()}`);\n\n // Apple ID / iCloud\n const mobileMe = await defaultsRead(\"MobileMeAccounts\");\n const appleIdMatch = mobileMe.match(/AccountID\\s*=\\s*\"?([^\";\\n]+)/);\n const displayNameMatch = mobileMe.match(/DisplayName\\s*=\\s*\"?([^\";\\n]+)/);\n if (displayNameMatch) lines.push(`- Apple ID Display Name: ${displayNameMatch[1].trim()}`);\n if (appleIdMatch) lines.push(`- Apple ID: ${appleIdMatch[1].trim()}`);\n\n // Computer name\n const computerName = await exec(\"scutil\", [\"--get\", \"ComputerName\"]);\n if (computerName) lines.push(`- Computer Name: ${computerName}`);\n\n // Git identity\n const gitName = await exec(\"git\", [\"config\", \"--global\", \"user.name\"]);\n const gitEmail = await exec(\"git\", [\"config\", \"--global\", \"user.email\"]);\n if (gitName) lines.push(`- Git Name: ${gitName}`);\n if (gitEmail) lines.push(`- Git Email: ${gitEmail}`);\n\n // Locale & Language\n lines.push(\"\", \"## Locale & Language\");\n const langs = await defaultsRead(\"NSGlobalDomain\", \"AppleLanguages\");\n const langList = langs.match(/\"([^\"]+)\"/g)?.map((s) => s.replace(/\"/g, \"\")) ?? [];\n if (langList.length > 0) lines.push(`- Languages: ${langList.join(\", \")}`);\n\n const locale = await defaultsRead(\"NSGlobalDomain\", \"AppleLocale\");\n if (locale) lines.push(`- Locale: ${locale}`);\n\n // Input methods\n const inputSources = await defaultsRead(\"com.apple.HIToolbox\", \"AppleSelectedInputSources\");\n const inputMethods = inputSources.match(/\"Input Mode\"\\s*=\\s*\"([^\"]+)\"/g) ?? [];\n const bundleIds = inputSources.match(/\"Bundle ID\"\\s*=\\s*\"([^\"]+)\"/g) ?? [];\n const inputs = [...inputMethods, ...bundleIds]\n .map((s) => s.replace(/.*\"([^\"]+)\"$/, \"$1\"))\n .filter((s) => !s.includes(\"PressAndHold\"));\n if (inputs.length > 0) lines.push(`- Input Methods: ${inputs.join(\", \")}`);\n\n // System appearance\n const appearance = await defaultsRead(\"-g\", \"AppleInterfaceStyle\");\n lines.push(`- System Appearance: ${appearance || \"Light\"}`);\n\n // Timezone\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n lines.push(`- Timezone: ${tz}`);\n\n return {\n id: \"identity-profile\",\n title: \"Mac User Identity Profile\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 2. Calendar Context\n// ---------------------------------------------------------------------------\n\nexport async function collectCalendarContext(): Promise<CollectorResult> {\n let calNames: string[] = [];\n const raw = await osascript(\n 'tell application \"Calendar\" to get name of every calendar',\n );\n if (raw) {\n calNames = raw.split(\", \").map((s) => s.trim()).filter(Boolean);\n }\n\n const lines = [\n \"## Calendar Subscriptions\",\n \"\",\n `Total calendars: ${calNames.length}`,\n \"\",\n ...calNames.map((name) => `- ${name}`),\n ];\n\n return {\n id: \"calendar-context\",\n title: \"Calendar Context\",\n content: calNames.length > 0\n ? lines.join(\"\\n\")\n : \"No calendar data accessible (Calendar app may not be running).\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 3. File Organization\n// ---------------------------------------------------------------------------\n\nexport async function collectFileOrganization(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Documents top-level folders\n const docsDir = path.join(HOME, \"Documents\");\n try {\n const entries = await fs.readdir(docsDir, { withFileTypes: true });\n const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n lines.push(\"## Documents Folders\", \"\", ...dirs.map((d) => `- ${d}`));\n } catch {\n lines.push(\"## Documents Folders\", \"\", \"(not accessible)\");\n }\n\n // Desktop items\n const desktopDir = path.join(HOME, \"Desktop\");\n try {\n const entries = await fs.readdir(desktopDir);\n lines.push(\"\", \"## Desktop Items\", \"\", ...entries.slice(0, 20).map((e) => `- ${e}`));\n if (entries.length > 20) lines.push(`- ... and ${entries.length - 20} more`);\n } catch {\n lines.push(\"\", \"## Desktop Items\", \"\", \"(not accessible)\");\n }\n\n // Recent downloads (file names only)\n const dlDir = path.join(HOME, \"Downloads\");\n try {\n const entries = await fs.readdir(dlDir);\n // Sort by name only (stat would be slow), take first 15\n lines.push(\n \"\",\n \"## Recent Downloads (latest 15 by name)\",\n \"\",\n ...entries.slice(0, 15).map((e) => `- ${e}`),\n );\n } catch {\n lines.push(\"\", \"## Recent Downloads\", \"\", \"(not accessible)\");\n }\n\n return {\n id: \"file-organization\",\n title: \"File Organization Structure\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 4. Dev Environment\n// ---------------------------------------------------------------------------\n\nexport async function collectDevEnvironment(): Promise<CollectorResult> {\n const lines: string[] = [\"## Runtime Versions\"];\n\n // Language runtimes\n const runtimes: [string, string, string[]][] = [\n [\"Node.js\", \"node\", [\"--version\"]],\n [\"Python\", \"python3\", [\"--version\"]],\n [\"Go\", \"go\", [\"version\"]],\n [\"Rust\", \"rustc\", [\"--version\"]],\n [\"Java\", \"java\", [\"--version\"]],\n ];\n\n for (const [name, cmd, args] of runtimes) {\n const ver = await exec(cmd, args);\n if (ver) {\n // Extract just the version string\n const first = ver.split(\"\\n\")[0] ?? ver;\n lines.push(`- ${name}: ${first}`);\n }\n }\n\n // Package managers\n lines.push(\"\", \"## Package Managers\");\n const pkgMgrs = [\"brew\", \"pnpm\", \"npm\", \"pip3\", \"cargo\"];\n for (const pm of pkgMgrs) {\n const which = await exec(\"which\", [pm]);\n if (which) lines.push(`- ${pm}: ${which}`);\n }\n\n // Docker\n const docker = await exec(\"docker\", [\"--version\"]);\n if (docker) lines.push(\"\", \"## Docker\", \"\", `- ${docker}`);\n\n // Shell info\n lines.push(\"\", \"## Shell\");\n const shell = process.env.SHELL ?? \"\";\n lines.push(`- Shell: ${shell}`);\n const zshrc = await readSafe(path.join(HOME, \".zshrc\"));\n const themeMatch = zshrc.match(/^ZSH_THEME=\"?([^\"\\n]+)/m);\n const pluginsMatch = zshrc.match(/^plugins=\\(([^)]+)\\)/m);\n if (themeMatch) lines.push(`- Oh-My-Zsh Theme: ${themeMatch[1]}`);\n if (pluginsMatch) lines.push(`- Oh-My-Zsh Plugins: ${pluginsMatch[1].trim()}`);\n\n // Global npm packages\n const npmGlobal = await exec(\"npm\", [\"list\", \"-g\", \"--depth=0\"]);\n if (npmGlobal) {\n const pkgs = npmGlobal\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"├\") || l.startsWith(\"└\"))\n .map((l) => l.replace(/^[├└─┬│\\s]+/, \"\").trim())\n .filter(Boolean);\n if (pkgs.length > 0) {\n lines.push(\"\", \"## Global npm Packages\", \"\", ...pkgs.map((p) => `- ${p}`));\n }\n }\n\n // Homebrew casks (installed apps via brew)\n const casks = await exec(\"brew\", [\"list\", \"--cask\"]);\n if (casks) {\n const caskList = casks.split(\"\\n\").filter(Boolean);\n lines.push(\"\", \"## Homebrew Casks\", \"\", ...caskList.map((c) => `- ${c}`));\n }\n\n return {\n id: \"dev-environment\",\n title: \"Development Environment\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 5. Dev Preferences\n// ---------------------------------------------------------------------------\n\nexport async function collectDevPreferences(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Shell aliases (sanitized)\n const zshrc = await readSafe(path.join(HOME, \".zshrc\"));\n if (zshrc) {\n const sanitized = stripSecretExports(zshrc);\n const aliases = sanitized\n .split(\"\\n\")\n .filter((l) => l.match(/^\\s*alias\\s/));\n if (aliases.length > 0) {\n lines.push(\"## Shell Aliases\", \"\", ...aliases);\n }\n\n // PATH additions\n const pathLines = sanitized\n .split(\"\\n\")\n .filter((l) => l.match(/^\\s*export\\s+PATH/) && !l.includes(\"REDACTED\"));\n if (pathLines.length > 0) {\n lines.push(\"\", \"## PATH Additions\", \"\", ...pathLines);\n }\n }\n\n // Git config (sanitized)\n const gitconfigPath = path.join(HOME, \".gitconfig\");\n const gitconfig = await readSafe(gitconfigPath);\n if (gitconfig) {\n const sanitized = stripGitCredentials(gitconfig);\n lines.push(\"\", \"## Git Config\", \"\", \"```\", sanitized.trim(), \"```\");\n }\n\n // Git aliases\n const gitAliases = await exec(\"git\", [\"config\", \"--global\", \"--get-regexp\", \"alias\"]);\n if (gitAliases) {\n lines.push(\"\", \"## Git Aliases\", \"\", ...gitAliases.split(\"\\n\").map((l) => `- ${l}`));\n }\n\n // Default branch\n const defaultBranch = await exec(\"git\", [\"config\", \"--global\", \"init.defaultBranch\"]);\n if (defaultBranch) lines.push(\"\", `## Default Git Branch: ${defaultBranch}`);\n\n // Cursor extensions\n const extDir = path.join(HOME, \".cursor\", \"extensions\");\n try {\n const entries = await fs.readdir(extDir);\n const exts = entries.filter((e) => !e.startsWith(\".\") && e !== \"extensions.json\");\n if (exts.length > 0) {\n lines.push(\"\", \"## Cursor Extensions\", \"\", ...exts.map((e) => `- ${e}`));\n }\n } catch {\n // No Cursor extensions dir\n }\n\n return {\n id: \"dev-preferences\",\n title: \"Development Preferences\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 6. Shell Habits\n// ---------------------------------------------------------------------------\n\nexport async function collectShellHabits(): Promise<CollectorResult> {\n const histPath = path.join(HOME, \".zsh_history\");\n const raw = await readSafe(histPath);\n\n if (!raw) {\n return {\n id: \"shell-habits\",\n title: \"Shell Command Habits\",\n content: \"No zsh history found.\",\n };\n }\n\n const rawLines = raw.split(\"\\n\");\n const totalEntries = rawLines.length;\n\n // Parse commands: zsh history format is `: timestamp:0;command`\n const freq = new Map<string, number>();\n for (const line of rawLines) {\n // Strip zsh extended history prefix\n const cmd = line.replace(/^:\\s*\\d+:\\d+;/, \"\").trim();\n const firstWord = cmd.split(/\\s+/)[0];\n if (firstWord && firstWord.length > 0 && firstWord.length < 50) {\n freq.set(firstWord, (freq.get(firstWord) ?? 0) + 1);\n }\n }\n\n const top30 = [...freq.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 30);\n\n const lines = [\n `Total history entries: ${totalEntries}`,\n \"\",\n \"## Most Used Commands (Top 30)\",\n \"\",\n ...top30.map(([cmd, count]) => `- ${cmd}: ${count} times`),\n ];\n\n return {\n id: \"shell-habits\",\n title: \"Shell Command Habits\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 7. Coding Rules\n// ---------------------------------------------------------------------------\n\nexport async function collectCodingRules(): Promise<CollectorResult> {\n const sections: string[] = [];\n\n // Global CLAUDE.md files\n const claudePaths = [\n path.join(HOME, \"CLAUDE.md\"),\n path.join(HOME, \".claude\", \"CLAUDE.md\"),\n ];\n\n for (const p of claudePaths) {\n const content = await readSafe(p);\n if (content) {\n const relPath = p.replace(HOME, \"~\");\n sections.push(`## ${relPath}`, \"\", content.trim(), \"\");\n }\n }\n\n // Claude custom commands\n const cmdDir = path.join(HOME, \".claude\", \"commands\");\n try {\n const entries = await fs.readdir(cmdDir);\n const mdFiles = entries.filter((e) => e.endsWith(\".md\"));\n if (mdFiles.length > 0) {\n sections.push(\"## Claude Custom Commands\", \"\");\n for (const f of mdFiles) {\n const content = await readSafe(path.join(cmdDir, f));\n if (content) {\n sections.push(`### ${f}`, \"\", content.trim(), \"\");\n }\n }\n }\n } catch {\n // No commands dir\n }\n\n // Scan active project repos for CLAUDE.md / AGENTS.md / .cursorrules\n const activeRepos = await findRecentGitRepos(30);\n const projectRules: string[] = [];\n\n for (const repo of activeRepos.slice(0, 10)) {\n // Only scan top 10 most recent\n for (const ruleFile of [\"CLAUDE.md\", \"AGENTS.md\", \".cursorrules\"]) {\n const rulePath = path.join(repo, ruleFile);\n const content = await readSafe(rulePath);\n if (content && content.length > 10) {\n const repoName = path.basename(repo);\n projectRules.push(\n `### ${repoName}/${ruleFile}`,\n \"\",\n content.trim().slice(0, 2000), // Cap at 2000 chars per file\n \"\",\n );\n }\n }\n }\n\n if (projectRules.length > 0) {\n sections.push(\"## Project-Level Rules\", \"\", ...projectRules);\n }\n\n // Cursor rules directory\n const cursorRulesDir = path.join(HOME, \".cursor\", \"rules\");\n try {\n const entries = await fs.readdir(cursorRulesDir);\n const mdFiles = entries.filter((e) => e.endsWith(\".md\") || e.endsWith(\".mdc\"));\n if (mdFiles.length > 0) {\n sections.push(\"## Global Cursor Rules\", \"\");\n for (const f of mdFiles) {\n const content = await readSafe(path.join(cursorRulesDir, f));\n if (content) {\n sections.push(`### ${f}`, \"\", content.trim().slice(0, 1000), \"\");\n }\n }\n }\n } catch {\n // No cursor rules dir\n }\n\n return {\n id: \"coding-rules\",\n title: \"Coding Rules and AI Agent Config\",\n content: sections.length > 0\n ? sections.join(\"\\n\")\n : \"No coding rules found.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 8. Active Projects\n// ---------------------------------------------------------------------------\n\nexport async function collectActiveProjects(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Recent git repos\n const repos = await findRecentGitRepos(30);\n if (repos.length > 0) {\n lines.push(\"## Active Git Repositories (last 30 days)\", \"\");\n for (const repo of repos) {\n const name = repo.replace(HOME, \"~\");\n lines.push(`- ${name}`);\n }\n }\n\n // SSH config hosts\n const sshConfig = await readSafe(path.join(HOME, \".ssh\", \"config\"));\n if (sshConfig) {\n const sanitized = stripSshIdentityFiles(sshConfig);\n const hosts = sanitized\n .split(\"\\n\")\n .filter((l) => /^\\s*Host\\s/i.test(l) && !l.includes(\"*\"))\n .map((l) => l.replace(/^\\s*Host\\s+/i, \"\").trim());\n if (hosts.length > 0) {\n lines.push(\"\", \"## SSH Hosts\", \"\", ...hosts.map((h) => `- ${h}`));\n }\n }\n\n // Cloud storage\n const cloudDir = path.join(HOME, \"Library\", \"CloudStorage\");\n try {\n const entries = await fs.readdir(cloudDir);\n if (entries.length > 0) {\n lines.push(\"\", \"## Cloud Storage\", \"\", ...entries.map((e) => `- ${e}`));\n }\n } catch {\n // No cloud storage\n }\n\n return {\n id: \"active-projects\",\n title: \"Active Projects and Infrastructure\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 9. Productivity Setup\n// ---------------------------------------------------------------------------\n\nexport async function collectProductivitySetup(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Installed applications\n try {\n const entries = await fs.readdir(\"/Applications\");\n const apps = entries\n .filter((e) => e.endsWith(\".app\"))\n .map((e) => e.replace(\".app\", \"\"));\n lines.push(\"## Installed Applications\", \"\", ...apps.map((a) => `- ${a}`));\n } catch {\n lines.push(\"## Installed Applications\", \"\", \"(not accessible)\");\n }\n\n // Dock persistent apps\n const dockApps = await defaultsRead(\"com.apple.dock\", \"persistent-apps\");\n if (dockApps) {\n const labels = [...dockApps.matchAll(/\"file-label\"\\s*=\\s*\"?([^\";\\n}]+)/g)]\n .map((m) => m[1].trim())\n .filter(Boolean);\n if (labels.length > 0) {\n lines.push(\"\", \"## Dock Apps (pinned)\", \"\", ...labels.map((a) => `- ${a}`));\n }\n }\n\n // Default browser\n const handlers = await defaultsRead(\n \"com.apple.LaunchServices/com.apple.launchservices.secure\",\n \"LSHandlers\",\n );\n const browserMatch = handlers.match(/LSHandlerRoleAll\\s*=\\s*\"([^\"]+)\"/);\n if (browserMatch) {\n const bundleId = browserMatch[1];\n const browserName = bundleId.includes(\"edge\")\n ? \"Microsoft Edge\"\n : bundleId.includes(\"chrome\")\n ? \"Google Chrome\"\n : bundleId.includes(\"firefox\")\n ? \"Firefox\"\n : bundleId.includes(\"safari\")\n ? \"Safari\"\n : bundleId;\n lines.push(\"\", `## Default Browser: ${browserName}`);\n }\n\n // Screen resolution\n const displayInfo = await exec(\"system_profiler\", [\"SPDisplaysDataType\"]);\n const resolutions = [...displayInfo.matchAll(/Resolution:\\s*(.+)/g)].map((m) => m[1].trim());\n if (resolutions.length > 0) {\n lines.push(\"\", \"## Screen Resolution\", \"\", ...resolutions.map((r) => `- ${r}`));\n }\n\n return {\n id: \"productivity-setup\",\n title: \"Productivity Setup\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 10. Browser Bookmarks\n// ---------------------------------------------------------------------------\n\nexport async function collectBrowserBookmarks(): Promise<CollectorResult> {\n const bmPath = path.join(\n HOME,\n \"Library\",\n \"Application Support\",\n \"Microsoft Edge\",\n \"Default\",\n \"Bookmarks\",\n );\n\n const raw = await readSafe(bmPath);\n if (!raw) {\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: \"No Edge bookmarks found.\",\n };\n }\n\n try {\n const data = JSON.parse(raw);\n const folders: { name: string; count: number; depth: number }[] = [];\n let totalUrls = 0;\n\n function walk(\n node: Record<string, unknown>,\n depth: number,\n ): void {\n if (node.type === \"folder\") {\n const children = (node.children ?? []) as Record<string, unknown>[];\n const name = (node.name as string) ?? \"\";\n if (children.length > 0) {\n folders.push({ name, count: children.length, depth });\n }\n for (const child of children) {\n walk(child, depth + 1);\n }\n } else if (node.type === \"url\") {\n totalUrls++;\n }\n }\n\n const roots = data.roots as Record<string, unknown>;\n for (const root of Object.values(roots)) {\n if (root && typeof root === \"object\" && \"children\" in (root as Record<string, unknown>)) {\n walk(root as Record<string, unknown>, 0);\n }\n }\n\n // Sort by count descending\n folders.sort((a, b) => b.count - a.count);\n\n const lines = [\n `Total bookmarks: ${totalUrls}`,\n `Total folders: ${folders.length}`,\n \"\",\n \"## Top Bookmark Folders (by item count)\",\n \"\",\n ...folders.slice(0, 40).map((f) => `- ${\" \".repeat(Math.min(f.depth, 2))}${f.name}: ${f.count} items`),\n ];\n\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: lines.join(\"\\n\"),\n };\n } catch {\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: \"Failed to parse Edge bookmarks.\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// 11. Browser Domains\n// ---------------------------------------------------------------------------\n\nexport async function collectBrowserDomains(): Promise<CollectorResult> {\n const histPath = path.join(\n HOME,\n \"Library\",\n \"Application Support\",\n \"Microsoft Edge\",\n \"Default\",\n \"History\",\n );\n\n if (!(await exists(histPath))) {\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: \"No Edge history found.\",\n };\n }\n\n // Copy the locked DB to a temp file for reading\n const tmpDb = path.join(os.tmpdir(), `pai-edge-history-${Date.now()}.db`);\n try {\n await fs.copyFile(histPath, tmpDb);\n\n const query = `\n SELECT\n REPLACE(REPLACE(SUBSTR(url, 1, INSTR(SUBSTR(url, 9), '/') + 8), 'https://', ''), 'http://', '') as domain,\n COUNT(*) as visits\n FROM urls\n WHERE last_visit_time > (strftime('%s', 'now', '-30 days') + 11644473600) * 1000000\n GROUP BY domain\n ORDER BY visits DESC\n LIMIT 30;\n `.trim();\n\n const output = await exec(\"sqlite3\", [tmpDb, query]);\n\n if (!output) {\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: \"Could not query Edge history (DB may be locked).\",\n };\n }\n\n const lines = [\n \"## Top 30 Domains (last 30 days)\",\n \"\",\n ...output.split(\"\\n\").map((line) => {\n const [domain, visits] = line.split(\"|\");\n return `- ${domain}: ${visits} visits`;\n }),\n ];\n\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: lines.join(\"\\n\"),\n };\n } finally {\n try {\n await fs.unlink(tmpDb);\n } catch {\n // Cleanup failure is ok\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// 12. GitHub Profile\n// ---------------------------------------------------------------------------\n\nexport async function collectGitHubProfile(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Check if gh CLI is available\n const ghVersion = await exec(\"gh\", [\"--version\"]);\n if (!ghVersion) {\n return {\n id: \"github-profile\",\n title: \"GitHub Profile\",\n content: \"GitHub CLI (gh) not installed.\",\n };\n }\n\n // Check auth status\n const authStatus = await exec(\"gh\", [\"auth\", \"status\"]);\n if (!authStatus) {\n return {\n id: \"github-profile\",\n title: \"GitHub Profile\",\n content: \"GitHub CLI not authenticated. Run `gh auth login` to enable.\",\n };\n }\n\n // Get user profile\n const userJson = await exec(\"gh\", [\n \"api\", \"user\",\n \"--jq\", '[.login, .name, .bio, .company, .location, .blog, .public_repos, .followers, .following, .created_at] | @tsv',\n ]);\n\n if (userJson) {\n const parts = userJson.split(\"\\t\");\n const [login, name, bio, company, location, blog, publicRepos, followers, following, createdAt] = parts;\n lines.push(\"## GitHub Profile\", \"\");\n if (login) lines.push(`- Username: ${login}`);\n if (name) lines.push(`- Name: ${name}`);\n if (bio) lines.push(`- Bio: ${bio}`);\n if (company) lines.push(`- Company: ${company}`);\n if (location) lines.push(`- Location: ${location}`);\n if (blog) lines.push(`- Website: ${blog}`);\n if (publicRepos) lines.push(`- Public Repos: ${publicRepos}`);\n if (followers || following) lines.push(`- Followers/Following: ${followers}/${following}`);\n if (createdAt) lines.push(`- Member since: ${createdAt}`);\n }\n\n // Get recent repos (owned, sorted by push date)\n const reposJson = await exec(\"gh\", [\n \"repo\", \"list\", \"--limit\", \"10\", \"--sort\", \"updated\", \"--json\",\n \"name,description,primaryLanguage,pushedAt,isPrivate\",\n \"--jq\", '.[] | [.name, .description, (.primaryLanguage.name // \"\"), .pushedAt, .isPrivate] | @tsv',\n ]);\n\n if (reposJson) {\n const repos = reposJson.split(\"\\n\").filter(Boolean);\n if (repos.length > 0) {\n lines.push(\"\", \"## Recent GitHub Repos (by push date)\", \"\");\n for (const repo of repos) {\n const [name, desc, lang, _pushed, isPrivate] = repo.split(\"\\t\");\n const visibility = isPrivate === \"true\" ? \"private\" : \"public\";\n const langTag = lang ? ` [${lang}]` : \"\";\n const descTag = desc ? ` — ${desc}` : \"\";\n lines.push(`- ${name}${langTag} (${visibility})${descTag}`);\n }\n }\n }\n\n // Get starred repos (recent 20 for interest signals)\n const starsJson = await exec(\"gh\", [\n \"api\", \"user/starred?per_page=20&sort=created&direction=desc\",\n \"--jq\", '.[].full_name',\n ]);\n\n if (starsJson) {\n const stars = starsJson.split(\"\\n\").filter(Boolean);\n if (stars.length > 0) {\n lines.push(\"\", \"## Recently Starred Repos (interest signals)\", \"\");\n lines.push(stars.map((s) => `- ${s}`).join(\"\\n\"));\n }\n }\n\n return {\n id: \"github-profile\",\n title: \"GitHub Profile & Activity\",\n content: lines.length > 0 ? lines.join(\"\\n\") : \"No GitHub data accessible.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 13. Recent Focus (inferred from git commits + shell history)\n// ---------------------------------------------------------------------------\n\nexport async function collectRecentFocus(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Analyze recent commit messages across active repos for topic signals\n const repos = await findRecentGitRepos(14); // Last 2 weeks\n const commitTopics = new Map<string, number>();\n const recentMessages: string[] = [];\n\n for (const repo of repos.slice(0, 8)) {\n // Get recent commit subjects from this repo\n const log = await exec(\"git\", [\n \"-C\", repo,\n \"log\", \"--oneline\", \"--since=14 days ago\",\n \"--format=%s\", \"-n\", \"20\",\n ]);\n if (log) {\n for (const msg of log.split(\"\\n\").filter(Boolean)) {\n recentMessages.push(msg);\n // Extract first word (often a conventional commit type)\n const type = msg.match(/^(feat|fix|refactor|test|docs|chore|perf|ci|build|style)/i);\n if (type) {\n const key = type[1].toLowerCase();\n commitTopics.set(key, (commitTopics.get(key) ?? 0) + 1);\n }\n }\n }\n }\n\n if (commitTopics.size > 0) {\n const sorted = [...commitTopics.entries()].sort((a, b) => b[1] - a[1]);\n lines.push(\"## Recent Commit Activity (last 2 weeks)\", \"\");\n lines.push(`Total commits analyzed: ${recentMessages.length}`);\n lines.push(\"\", \"Commit types:\");\n for (const [type, count] of sorted) {\n lines.push(`- ${type}: ${count}`);\n }\n }\n\n // Extract recent commit subjects for topic inference\n if (recentMessages.length > 0) {\n lines.push(\"\", \"## Recent Commit Messages (sample)\", \"\");\n // Take a diverse sample (first 15 unique-ish messages)\n const unique = [...new Set(recentMessages)].slice(0, 15);\n for (const msg of unique) {\n lines.push(`- ${msg}`);\n }\n }\n\n // Recent shell commands that indicate current work context\n const histPath = path.join(HOME, \".zsh_history\");\n const histRaw = await readSafe(histPath);\n if (histRaw) {\n const histLines = histRaw.split(\"\\n\");\n // Take last 200 history entries and extract project-related patterns\n const recent = histLines.slice(-200);\n const cdPaths = new Map<string, number>();\n const tools = new Map<string, number>();\n\n for (const line of recent) {\n const cmd = line.replace(/^:\\s*\\d+:\\d+;/, \"\").trim();\n // Track cd destinations\n const cdMatch = cmd.match(/^cd\\s+(.+)/);\n if (cdMatch) {\n const dest = cdMatch[1].trim().replace(/^~/, HOME);\n cdPaths.set(dest, (cdPaths.get(dest) ?? 0) + 1);\n }\n // Track tool usage\n const toolMatch = cmd.match(/^(docker|kubectl|terraform|aws|gcloud|firebase|vercel|netlify|npm|pnpm|yarn|bun|cargo|go|python|pip|uv)\\b/);\n if (toolMatch) {\n tools.set(toolMatch[1], (tools.get(toolMatch[1]) ?? 0) + 1);\n }\n }\n\n if (cdPaths.size > 0) {\n const topDirs = [...cdPaths.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);\n lines.push(\"\", \"## Recent Working Directories\", \"\");\n for (const [dir, count] of topDirs) {\n lines.push(`- ${dir.replace(HOME, \"~\")}: ${count}x`);\n }\n }\n\n if (tools.size > 0) {\n const topTools = [...tools.entries()].sort((a, b) => b[1] - a[1]);\n lines.push(\"\", \"## Recently Used Tools (from history)\", \"\");\n for (const [tool, count] of topTools) {\n lines.push(`- ${tool}: ${count}x`);\n }\n }\n }\n\n return {\n id: \"recent-focus\",\n title: \"Recent Focus & Activity\",\n content: lines.length > 0\n ? lines.join(\"\\n\")\n : \"No recent activity data found.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 14. Social & Registry Profiles\n// ---------------------------------------------------------------------------\n\nexport async function collectSocialProfiles(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // npm registry identity\n const npmrc = await readSafe(path.join(HOME, \".npmrc\"));\n if (npmrc) {\n // Extract registry URL (but not tokens)\n const registryMatch = npmrc.match(/^registry\\s*=\\s*(.+)/m);\n if (registryMatch) lines.push(`- npm registry: ${registryMatch[1].trim()}`);\n // Check for scope configs\n const scopes = npmrc.match(/^@[\\w-]+:registry/gm);\n if (scopes) {\n for (const s of scopes) {\n lines.push(`- npm scope: ${s.split(\":\")[0]}`);\n }\n }\n }\n\n // npm whoami\n const npmUser = await exec(\"npm\", [\"whoami\"]);\n if (npmUser) lines.push(`- npm username: ${npmUser}`);\n\n // PyPI config\n const pypirc = await readSafe(path.join(HOME, \".pypirc\"));\n if (pypirc) {\n const repoMatch = pypirc.match(/repository\\s*=\\s*(.+)/);\n if (repoMatch) lines.push(`- PyPI repository: ${repoMatch[1].trim()}`);\n const usernameMatch = pypirc.match(/username\\s*=\\s*(.+)/);\n if (usernameMatch) lines.push(`- PyPI username: ${usernameMatch[1].trim()}`);\n }\n\n // Docker Hub\n const dockerConfig = await readSafe(path.join(HOME, \".docker\", \"config.json\"));\n if (dockerConfig) {\n try {\n const cfg = JSON.parse(dockerConfig);\n const auths = Object.keys(cfg.auths ?? {});\n if (auths.length > 0) {\n lines.push(`- Docker registries: ${auths.join(\", \")}`);\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n // Cargo / crates.io\n const cargoConfig = await readSafe(path.join(HOME, \".cargo\", \"config.toml\"));\n if (cargoConfig) {\n const registries = cargoConfig.match(/\\[registries\\.(\\w+)\\]/g);\n if (registries) {\n lines.push(`- Cargo registries: ${registries.map((r) => r.replace(/\\[registries\\.|\\]/g, \"\")).join(\", \")}`);\n }\n }\n\n // Cloud provider CLIs\n const awsIdentity = await exec(\"aws\", [\"sts\", \"get-caller-identity\", \"--query\", \"Account\", \"--output\", \"text\"]);\n if (awsIdentity) lines.push(`- AWS Account: ${awsIdentity}`);\n\n const gcpProject = await exec(\"gcloud\", [\"config\", \"get-value\", \"project\"]);\n if (gcpProject && !gcpProject.includes(\"unset\")) lines.push(`- GCP Project: ${gcpProject}`);\n\n const vercelUser = await exec(\"vercel\", [\"whoami\"]);\n if (vercelUser) lines.push(`- Vercel: ${vercelUser}`);\n\n return {\n id: \"social-profiles\",\n title: \"Registry & Cloud Profiles\",\n content: lines.length > 0\n ? [\"## Registry & Cloud Accounts\", \"\", ...lines].join(\"\\n\")\n : \"No registry or cloud profiles detected.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\n/** Find git repos under ~/Documents modified in the last N days */\nasync function findRecentGitRepos(days: number): Promise<string[]> {\n const output = await exec(\"find\", [\n path.join(HOME, \"Documents\"),\n \"-maxdepth\", \"4\",\n \"-name\", \".git\",\n \"-type\", \"d\",\n \"-mtime\", `-${days}`,\n ]);\n\n if (!output) return [];\n return output\n .split(\"\\n\")\n .filter(Boolean)\n .map((p) => p.replace(/\\/.git$/, \"\"));\n}\n\n// ---------------------------------------------------------------------------\n// Export all collectors\n// ---------------------------------------------------------------------------\n\n/** All available Mac collectors in execution order */\nexport const ALL_COLLECTORS = [\n collectIdentityProfile,\n collectCalendarContext,\n collectFileOrganization,\n collectDevEnvironment,\n collectDevPreferences,\n collectShellHabits,\n collectCodingRules,\n collectActiveProjects,\n collectProductivitySetup,\n collectBrowserBookmarks,\n collectBrowserDomains,\n collectGitHubProfile,\n collectRecentFocus,\n collectSocialProfiles,\n] as const;\n","/**\n * Profile module — compile, load, and rebuild user profiles.\n */\n\nimport fs from \"node:fs/promises\";\nimport { getProfilePath } from \"../config/paths.js\";\nimport { addConnectorEntry } from \"../raw/add.js\";\nimport * as console from \"../utils/console.js\";\nimport { compileProfile, compileCompactProfile } from \"./compile.js\";\nimport type { CollectorResult } from \"../types.js\";\nimport { ALL_COLLECTORS } from \"../connectors/mac/collectors.js\";\n\nexport { compileProfile, compileCompactProfile } from \"./compile.js\";\n\n/** Load the current profile from disk. Returns null if not found. */\nexport async function loadProfile(): Promise<string | null> {\n try {\n return await fs.readFile(getProfilePath(), \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** Save profile markdown to disk. */\nexport async function saveProfile(content: string): Promise<string> {\n const profilePath = getProfilePath();\n await fs.writeFile(profilePath, content, \"utf-8\");\n return profilePath;\n}\n\n/**\n * Full rebuild: scan local machine → compile profile → write to disk.\n * Also writes raw files for the scan data (for vault/distill pipeline compatibility).\n */\nexport async function rebuildProfile(opts?: {\n /** If true, skip writing raw files (profile-only mode) */\n skipRaw?: boolean;\n /** If true, show progress spinners */\n verbose?: boolean;\n}): Promise<{ profilePath: string; results: CollectorResult[] }> {\n const verbose = opts?.verbose ?? true;\n\n // 1. Run all collectors concurrently\n const spin = verbose ? console.spinner(\"Scanning local machine...\") : null;\n\n const settled = await Promise.allSettled(\n ALL_COLLECTORS.map((collector) => collector()),\n );\n\n const results: CollectorResult[] = [];\n const failed: string[] = [];\n\n for (let i = 0; i < settled.length; i++) {\n const s = settled[i];\n if (s.status === \"fulfilled\") {\n results.push(s.value);\n } else {\n const name = ALL_COLLECTORS[i].name;\n failed.push(name);\n if (verbose) console.warn(`Collector ${name} failed: ${String(s.reason)}`);\n }\n }\n\n spin?.succeed(`Scanned ${results.length} sources (${failed.length} failed)`);\n\n // 2. Optionally write raw files (for backward compat with vault/distill)\n if (!opts?.skipRaw) {\n for (const entry of results) {\n try {\n await addConnectorEntry(\"mac\", entry);\n } catch {\n // Non-critical — profile compilation doesn't depend on raw writes\n }\n }\n }\n\n // 3. Compile and save profile\n const profileContent = compileProfile(results);\n const profilePath = await saveProfile(profileContent);\n\n return { profilePath, results };\n}\n","import type { Command } from \"commander\";\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n getPaiHome,\n getRawDir,\n getVaultDir,\n getMemoryDir,\n getWeeklyDir,\n getSkillsDir,\n getConfigDir,\n getConfigPath,\n getProfilesPath,\n getPreferencesPath,\n} from \"../config/paths.js\";\nimport { DEFAULT_CONFIG, DEFAULT_PROFILES, DEFAULT_PREFERENCES } from \"../config/defaults.js\";\nimport { saveConfig } from \"../config/io.js\";\nimport { execQmd, isQmdAvailable } from \"../utils/process.js\";\nimport { rebuildProfile } from \"../profile/index.js\";\nimport * as console from \"../utils/console.js\";\n\n/** Try to install QMD globally via npm */\nasync function installQmd(): Promise<boolean> {\n const spin = console.spinner(\"Installing QMD (search engine)...\");\n return new Promise((resolve) => {\n execFile(\"npm\", [\"install\", \"-g\", \"https://github.com/tobi/qmd\"], {\n encoding: \"utf-8\",\n timeout: 120_000,\n }, (err) => {\n if (err) {\n spin.fail(\"QMD installation failed.\");\n console.warn(\"Install manually: npm install -g https://github.com/tobi/qmd\");\n resolve(false);\n } else {\n spin.succeed(\"QMD installed.\");\n resolve(true);\n }\n });\n });\n}\n\n/** Options for runInit (used by both init and reset) */\nexport interface InitOptions {\n /** If true, overwrite config/profiles/preferences; if false, skip when file exists */\n overwriteConfig?: boolean;\n /** If true, skip local machine scan and all steps 3-8 (for CI/testing) */\n skipScan?: boolean;\n /** If true, skip Google OAuth (for agents; no browser) */\n nonInteractive?: boolean;\n}\n\nconst TOTAL_STEPS = 8;\n\n/** Create directory structure, write default configs, register QMD, scan, profile, Google import, index. */\nexport async function runInit(options: InitOptions = {}): Promise<void> {\n const { overwriteConfig = false, skipScan = false, nonInteractive = false } = options;\n const paiHome = getPaiHome();\n\n let profileLines = 0;\n let profileSources = 0;\n let gmailCount = 0;\n let calendarCount = 0;\n let googleAuthed = false;\n let qmdReady = false;\n\n // [1/8] Create directory structure\n const dirs = [\n path.join(getRawDir(), \"local\"),\n path.join(getRawDir(), \"web\"),\n path.join(getRawDir(), \"connector\"),\n path.join(getVaultDir(), \"coding\"),\n path.join(getVaultDir(), \"work\"),\n path.join(getVaultDir(), \"life\"),\n path.join(getVaultDir(), \"preferences\"),\n path.join(getVaultDir(), \"context\"),\n getMemoryDir(),\n getWeeklyDir(),\n getSkillsDir(),\n getConfigDir(),\n ];\n\n for (const dir of dirs) {\n await fs.mkdir(dir, { recursive: true });\n }\n\n // [1/8] Write default configs\n const configPath = getConfigPath();\n const profilesPath = getProfilesPath();\n const prefsPath = getPreferencesPath();\n\n if (overwriteConfig) {\n await saveConfig(configPath, DEFAULT_CONFIG);\n await saveConfig(profilesPath, DEFAULT_PROFILES);\n await saveConfig(prefsPath, DEFAULT_PREFERENCES);\n } else {\n try {\n await fs.access(configPath);\n console.info(\"Config already exists, skipping.\");\n } catch {\n await saveConfig(configPath, DEFAULT_CONFIG);\n }\n try {\n await fs.access(profilesPath);\n console.info(\"Profiles config already exists, skipping.\");\n } catch {\n await saveConfig(profilesPath, DEFAULT_PROFILES);\n }\n try {\n await fs.access(prefsPath);\n console.info(\"Preferences already exists, skipping.\");\n } catch {\n await saveConfig(prefsPath, DEFAULT_PREFERENCES);\n console.success(\"Created preferences.md — edit to customize AI behavior.\");\n }\n }\n console.success(`[1/${TOTAL_STEPS}] Creating directories & config...`);\n\n // [2/8] QMD: check → auto-install if missing → register collections\n qmdReady = await isQmdAvailable();\n\n if (!qmdReady) {\n qmdReady = await installQmd();\n }\n\n if (qmdReady) {\n try {\n await execQmd([\n \"collection\", \"add\", getRawDir(),\n \"--name\", \"raw\", \"--mask\", \"**/*.md\",\n ]);\n console.success(\"QMD collection 'raw' registered.\");\n } catch {\n console.warn(\"QMD collection 'raw' may already exist, skipping.\");\n }\n try {\n await execQmd([\n \"collection\", \"add\", getVaultDir(),\n \"--name\", \"vault\", \"--mask\", \"**/*.md\",\n ]);\n console.success(\"QMD collection 'vault' registered.\");\n } catch {\n console.warn(\"QMD collection 'vault' may already exist, skipping.\");\n }\n }\n if (qmdReady) {\n console.success(`[2/${TOTAL_STEPS}] Search engine (QMD) ready.`);\n } else {\n console.warn(`[2/${TOTAL_STEPS}] Search engine (QMD) not available; built-in search will be used.`);\n }\n\n if (skipScan) {\n console.log(\"\");\n console.info(\"Directory structure:\");\n console.log(` ${paiHome}/profile.md — (skipped; run pai profile --rebuild)`);\n console.log(` ${paiHome}/raw/ — raw data input`);\n console.log(` ${paiHome}/vault/ — PINData knowledge store`);\n console.log(` ${paiHome}/memory/ — daily journal & digests`);\n console.log(` ${paiHome}/config/ — configuration`);\n console.info('Run \"pai profile --rebuild\" to scan and build your profile.');\n return;\n }\n\n // [3/8] Auto-scan local machine\n try {\n const { profilePath, results } = await rebuildProfile({ verbose: true });\n profileSources = results.length;\n try {\n const content = await fs.readFile(profilePath, \"utf-8\");\n profileLines = content.split(\"\\n\").length;\n } catch {\n profileLines = 0;\n }\n console.success(`[3/${TOTAL_STEPS}] Scanning your machine (${profileSources} sources)...`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[3/${TOTAL_STEPS}] Scan failed: ${msg}`);\n }\n\n // [4/8] Profile already compiled by rebuildProfile\n console.success(`[4/${TOTAL_STEPS}] Compiling profile...`);\n\n // [5/8] Google OAuth (skip if non-interactive)\n if (!nonInteractive) {\n try {\n const { encryption } = await import(\"../auth/encryption.js\");\n const { googleOAuth } = await import(\"../auth/google-oauth.js\");\n await encryption.loadKey();\n await googleOAuth.init();\n if (googleOAuth.isAuthenticated()) {\n googleAuthed = true;\n console.success(`[5/${TOTAL_STEPS}] Connecting Google account... (already connected)`);\n } else {\n await googleOAuth.authorize();\n googleAuthed = true;\n console.success(`[5/${TOTAL_STEPS}] Connecting Google account...`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[5/${TOTAL_STEPS}] Google auth skipped: ${msg}`);\n console.warn(\"Run \\\"pai auth google\\\" later to connect Gmail and Calendar.\");\n }\n } else {\n console.info(`[5/${TOTAL_STEPS}] Connecting Google account... (skipped, non-interactive)`);\n }\n\n // [6/8] Gmail import\n if (googleAuthed) {\n const spinGmail = console.spinner(`[6/${TOTAL_STEPS}] Importing Gmail (latest 100)...`);\n try {\n const { syncGmail } = await import(\"../connectors/google/gmail.js\");\n const { addConnectorEntry } = await import(\"../raw/add.js\");\n const entries = await syncGmail({ days: 365, maxResults: 100 });\n for (const entry of entries) {\n const status = await addConnectorEntry(\"gmail\", entry);\n if (status === \"created\" || status === \"updated\") gmailCount++;\n }\n spinGmail.succeed(`[6/${TOTAL_STEPS}] Importing Gmail... ${gmailCount} message(s)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spinGmail.fail(`[6/${TOTAL_STEPS}] Gmail import failed: ${msg}`);\n }\n } else {\n console.info(`[6/${TOTAL_STEPS}] Importing Gmail... (skipped, no Google auth)`);\n }\n\n // [7/8] Calendar import\n if (googleAuthed) {\n const spinCal = console.spinner(`[7/${TOTAL_STEPS}] Importing Calendar...`);\n try {\n const { syncCalendar } = await import(\"../connectors/google/calendar.js\");\n const { addConnectorEntry } = await import(\"../raw/add.js\");\n const entries = await syncCalendar({ lookbackDays: 30, lookforwardDays: 90 });\n for (const entry of entries) {\n const status = await addConnectorEntry(\"calendar\", entry);\n if (status === \"created\" || status === \"updated\") calendarCount++;\n }\n spinCal.succeed(`[7/${TOTAL_STEPS}] Importing Calendar... ${calendarCount} event(s)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spinCal.fail(`[7/${TOTAL_STEPS}] Calendar import failed: ${msg}`);\n }\n } else {\n console.info(`[7/${TOTAL_STEPS}] Importing Calendar... (skipped, no Google auth)`);\n }\n\n // [8/8] QMD index + embed\n if (qmdReady) {\n try {\n const { updateIndex } = await import(\"../search/index.js\");\n await updateIndex();\n console.success(`[8/${TOTAL_STEPS}] Indexing for search...`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[8/${TOTAL_STEPS}] Indexing failed: ${msg}. Built-in search still works.`);\n }\n } else {\n console.info(`[8/${TOTAL_STEPS}] Indexing for search... (skipped, QMD not available)`);\n }\n\n // Summary\n console.log(\"\");\n console.success(\"Setup complete!\");\n console.log(` Profile: ${paiHome}/profile.md (${profileLines} lines, ${profileSources} sources)`);\n const rawCount = gmailCount + calendarCount;\n if (rawCount > 0) {\n console.log(` Raw data: ${rawCount} file(s) (${gmailCount} gmail, ${calendarCount} calendar)`);\n } else {\n console.log(` Raw data: ${paiHome}/raw/`);\n }\n console.log(` Search: ${qmdReady ? \"QMD hybrid ready\" : \"built-in keyword (install QMD for semantic)\"}`);\n console.log(` Google: ${googleAuthed ? \"connected\" : \"not connected\"}`);\n console.log(\"\");\n console.info(\"Next:\");\n console.log(\" pai profile # View your profile\");\n console.log(\" pai distribute # Deploy to Cursor/Claude\");\n console.log(\" pai ask \\\"question\\\" # Ask about yourself\");\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize pai: create dirs, scan, profile, Google import, index (full pipeline)\")\n .option(\"--skip-scan\", \"Skip scan and steps 3-8 (CI/testing)\")\n .option(\"--non-interactive\", \"Skip Google OAuth (for agents; no browser)\")\n .action(async (opts: { skipScan?: boolean; nonInteractive?: boolean }) => {\n const paiHome = getPaiHome();\n const spin = console.spinner(`Initializing pai at ${paiHome}...`);\n try {\n spin.succeed(`Initializing pai at ${paiHome}`);\n await runInit({\n overwriteConfig: false,\n skipScan: opts.skipScan,\n nonInteractive: opts.nonInteractive,\n });\n } catch (err) {\n spin.fail(\"Initialization failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","/**\n * Web scraper: three-tier strategy for content extraction.\n *\n * 1. GitHub blob URLs → convert to raw.githubusercontent.com, fetch raw markdown (zero rendering)\n * 2. General URLs → Playwright headless browser + DOM preprocessing + Defuddle extraction\n * 3. Fallback → plain fetch + Defuddle (for when Playwright is unavailable)\n *\n * Reference: linkmind-master/src/scraper.ts\n */\n\nimport * as console from \"../utils/console.js\";\n\nconst CHROME_UA =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\n\n/** Minimum markdown length to consider a scrape successful */\nconst MIN_CONTENT_LENGTH = 100;\n\nexport interface ScrapeResult {\n url: string;\n title: string;\n markdown: string;\n}\n\n// ---------------------------------------------------------------------------\n// Domain-specific fast paths\n// ---------------------------------------------------------------------------\n\n/** Check if a URL points to a file on GitHub (blob view) */\nfunction isGithubBlobUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.hostname === \"github.com\" && /\\/blob\\//.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n/** Convert GitHub blob URL to raw.githubusercontent.com URL */\nfunction toRawGithubUrl(url: string): string {\n // github.com/OWNER/REPO/blob/BRANCH/PATH\n // → raw.githubusercontent.com/OWNER/REPO/BRANCH/PATH\n return url\n .replace(\"https://github.com/\", \"https://raw.githubusercontent.com/\")\n .replace(\"/blob/\", \"/\");\n}\n\n/** Fast path: fetch raw content directly from GitHub (returns markdown/text as-is) */\nasync function scrapeGithubRaw(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n const rawUrl = toRawGithubUrl(url);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const resp = await fetch(rawUrl, {\n signal: controller.signal,\n headers: { \"User-Agent\": CHROME_UA },\n });\n if (!resp.ok) {\n throw new Error(`GitHub raw fetch failed: HTTP ${resp.status}`);\n }\n const markdown = await resp.text();\n\n // Extract title from first heading or filename\n const titleMatch = markdown.match(/^#\\s+(.+)$/m);\n const pathParts = new URL(url).pathname.split(\"/\");\n const filename = pathParts[pathParts.length - 1] ?? \"Untitled\";\n const title = titleMatch?.[1]?.trim() ?? filename;\n\n return { url, title, markdown };\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Playwright scraper (primary path for general URLs)\n// ---------------------------------------------------------------------------\n\n/**\n * DOM preprocessing script executed inside the browser.\n * Removes navigation, ads, cookie banners, and other non-content elements\n * before Defuddle extraction. (Borrowed from linkmind)\n */\nconst DOM_PREPROCESS_SCRIPT = `(() => {\n // Remove script, style, stylesheet links\n document.querySelectorAll(\"script, style, link[rel='stylesheet']\").forEach(el => el.remove());\n // Remove navigation elements\n document.querySelectorAll(\"nav, footer, aside\").forEach(el => el.remove());\n // Remove headers not inside article/main\n document.querySelectorAll(\"header\").forEach(el => {\n if (!el.closest(\"article\") && !el.closest(\"main\")) el.remove();\n });\n // Remove ARIA landmark roles\n document.querySelectorAll('[role=\"navigation\"], [role=\"banner\"], [role=\"contentinfo\"], [role=\"complementary\"], [role=\"search\"]').forEach(el => el.remove());\n // Remove cookie/share/comment noise\n document.querySelectorAll('[class*=\"cookie-banner\"], [id*=\"cookie-banner\"], [class*=\"cookie-consent\"], [class*=\"share-buttons\"], [class*=\"social-share\"], [class*=\"comment-section\"], [id*=\"comments\"]').forEach(el => el.remove());\n // Remove hidden elements\n document.querySelectorAll('[hidden], [aria-hidden=\"true\"]').forEach(el => el.remove());\n\n return {\n title: document.title,\n html: document.documentElement.outerHTML,\n };\n})()`;\n\n/** Scrape with Playwright headless browser + Defuddle */\nasync function scrapeWithPlaywright(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n // Dynamic import so the module still loads when playwright is not installed\n const pw = await import(\"playwright\");\n const { Defuddle } = await import(\"defuddle/node\");\n\n const browser = await pw.chromium.launch({\n headless: true,\n args: [\"--disable-blink-features=AutomationControlled\"],\n });\n\n try {\n const context = await browser.newContext({\n viewport: { width: 1280, height: 900 },\n userAgent: CHROME_UA,\n locale: \"en-US\",\n });\n\n const page = await context.newPage();\n await page.goto(url, { waitUntil: \"domcontentloaded\", timeout });\n // Wait for JS rendering (dynamic content, SPAs)\n await page.waitForTimeout(2000);\n\n // Preprocess DOM and grab cleaned HTML\n const { title: pageTitle, html } = (await page.evaluate(\n DOM_PREPROCESS_SCRIPT,\n )) as { title: string; html: string };\n\n await browser.close();\n\n // Defuddle content extraction (suppress noisy log)\n const origLog = globalThis.console.log;\n globalThis.console.log = (msg: unknown, ...args: unknown[]) => {\n if (\n typeof msg === \"string\" &&\n msg.includes(\"Initial parse returned very little content\")\n )\n return;\n origLog(msg, ...args);\n };\n const result = await Defuddle(html, url);\n globalThis.console.log = origLog;\n\n const title = result.title || pageTitle || \"Untitled\";\n const markdown = result.content\n ? htmlToSimpleMarkdown(result.content)\n : \"\";\n\n return { url, title, markdown };\n } catch (err) {\n await browser.close().catch(() => {});\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fetch fallback (when Playwright is unavailable)\n// ---------------------------------------------------------------------------\n\n/** Lightweight scrape: plain HTTP fetch + Defuddle (no JS rendering) */\nasync function scrapeWithFetch(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n let html: string;\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n \"User-Agent\": CHROME_UA,\n Accept: \"text/html,application/xhtml+xml\",\n },\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n html = await response.text();\n } finally {\n clearTimeout(timer);\n }\n\n // Try Defuddle\n try {\n const { Defuddle } = await import(\"defuddle/node\");\n const result = await Defuddle(html, url);\n const title = result.title || extractTitleFromHtml(html);\n const markdown = result.content\n ? htmlToSimpleMarkdown(result.content)\n : extractTextFromHtml(html);\n return { url, title, markdown };\n } catch {\n // Defuddle unavailable — raw HTML stripping\n console.warn(\"defuddle not available, using basic HTML extraction\");\n }\n\n const title = extractTitleFromHtml(html);\n const markdown = extractTextFromHtml(html);\n return { url, title, markdown };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Scrape a URL and return title + markdown content.\n *\n * Strategy:\n * 1. GitHub blob URL → raw.githubusercontent.com (instant, perfect fidelity)\n * 2. Playwright + Defuddle (handles JS-rendered pages)\n * 3. Fetch + Defuddle fallback (static pages, or when Playwright missing)\n */\nexport async function scrapeUrl(\n url: string,\n timeout: number = 30_000,\n): Promise<ScrapeResult> {\n // --- Fast path: GitHub blob → raw fetch ---\n if (isGithubBlobUrl(url)) {\n console.info(\"GitHub blob detected — fetching raw content directly\");\n return scrapeGithubRaw(url, timeout);\n }\n\n // --- Primary: Playwright ---\n try {\n const result = await scrapeWithPlaywright(url, timeout);\n\n // Quality check: if Playwright returned too little content, try fetch fallback\n if (result.markdown.length < MIN_CONTENT_LENGTH) {\n console.warn(\n `Playwright extracted only ${result.markdown.length} chars — trying fetch fallback`,\n );\n const fallback = await scrapeWithFetch(url, timeout);\n // Return whichever got more content\n return fallback.markdown.length > result.markdown.length\n ? fallback\n : result;\n }\n\n return result;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Playwright scrape failed (${msg}) — falling back to fetch`);\n }\n\n // --- Fallback: plain fetch ---\n return scrapeWithFetch(url, timeout);\n}\n\n// ---------------------------------------------------------------------------\n// HTML helpers\n// ---------------------------------------------------------------------------\n\n/** Extract <title> from HTML */\nfunction extractTitleFromHtml(html: string): string {\n const match = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n return match?.[1]?.trim() ?? \"Untitled\";\n}\n\n/** Basic HTML to text extraction (last-resort fallback) */\nfunction extractTextFromHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \"\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 10000);\n}\n\n/** Convert HTML fragment to simple Markdown (from linkmind, extended) */\nfunction htmlToSimpleMarkdown(html: string): string {\n if (!html) return \"\";\n\n let md = html;\n\n // Headings\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, \"# $1\\n\\n\");\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, \"## $1\\n\\n\");\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, \"### $1\\n\\n\");\n md = md.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, \"#### $1\\n\\n\");\n md = md.replace(/<h5[^>]*>(.*?)<\\/h5>/gi, \"##### $1\\n\\n\");\n md = md.replace(/<h6[^>]*>(.*?)<\\/h6>/gi, \"###### $1\\n\\n\");\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>/gi, \"\\n\\n\");\n md = md.replace(/<\\/p>/gi, \"\");\n md = md.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Bold and italic\n md = md.replace(/<(strong|b)[^>]*>(.*?)<\\/(strong|b)>/gi, \"**$2**\");\n md = md.replace(/<(em|i)[^>]*>(.*?)<\\/(em|i)>/gi, \"*$2*\");\n\n // Links\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, \"[$2]($1)\");\n\n // Code\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, \"`$1`\");\n md = md.replace(/<pre[^>]*>(.*?)<\\/pre>/gis, \"\\n```\\n$1\\n```\\n\");\n\n // Lists\n md = md.replace(/<li[^>]*>/gi, \"- \");\n md = md.replace(/<\\/li>/gi, \"\\n\");\n md = md.replace(/<\\/?[uo]l[^>]*>/gi, \"\\n\");\n\n // Blockquote\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gis, (_, content) => {\n return (content as string)\n .split(\"\\n\")\n .map((line: string) => `> ${line}`)\n .join(\"\\n\");\n });\n\n // Images\n md = md.replace(\n /<img[^>]*src=\"([^\"]*)\"[^>]*alt=\"([^\"]*)\"[^>]*\\/?>/gi,\n \"![$2]($1)\",\n );\n md = md.replace(/<img[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, \"![]($1)\");\n\n // Strip remaining tags\n md = md.replace(/<[^>]+>/g, \"\");\n\n // Decode entities\n md = md.replace(/&amp;/g, \"&\");\n md = md.replace(/&lt;/g, \"<\");\n md = md.replace(/&gt;/g, \">\");\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n md = md.replace(/&nbsp;/g, \" \");\n\n // Clean up whitespace\n md = md.replace(/\\n{3,}/g, \"\\n\\n\");\n md = md.trim();\n\n return md;\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport { addText, addFile, addUrl } from \"../raw/index.js\";\nimport { scrapeUrl } from \"../scraper/index.js\";\nimport { loadConfig } from \"../config/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerAddCommand(program: Command): void {\n program\n .command(\"add <input>\")\n .description(\"Add content to raw layer (text, file path, or URL with --url)\")\n .option(\"--url\", \"Treat input as URL, scrape and save\")\n .option(\"--source <source>\", \"Source tag\", \"local\")\n .action(\n async (input: string, opts: { url?: boolean; source: string }) => {\n try {\n let filePath: string;\n\n if (opts.url) {\n // URL scraping\n const spin = console.spinner(`Scraping ${input}...`);\n try {\n const config = await loadConfig();\n const result = await scrapeUrl(input, config.scraper.timeout);\n filePath = await addUrl(input, result.title, result.markdown);\n spin.succeed(`Scraped: ${result.title}`);\n } catch (err) {\n spin.fail(\"Scraping failed\");\n throw err;\n }\n } else {\n // Check if input is a file path\n try {\n const stat = await fs.stat(input);\n if (stat.isFile()) {\n filePath = await addFile(input, opts.source);\n console.success(`Added file: ${input}`);\n } else {\n throw new Error(\"Not a file\");\n }\n } catch {\n // Treat as plain text\n filePath = await addText(input, opts.source);\n console.success(\"Added text content\");\n }\n }\n\n console.info(`Saved to: ${filePath}`);\n console.info('Run \"pai distill\" to process into vault.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to add: ${msg}`);\n process.exit(1);\n }\n },\n );\n}\n","import fs from \"node:fs/promises\";\nimport OpenAI from \"openai\";\nimport { loadConfig } from \"../config/index.js\";\nimport { getPreferencesPath } from \"../config/paths.js\";\nimport type { PaiConfig } from \"../config/index.js\";\n\nlet cachedConfig: PaiConfig | null = null;\nlet cachedPreferences: string | null = null;\n\nasync function getConfig(): Promise<PaiConfig> {\n if (!cachedConfig) {\n cachedConfig = await loadConfig();\n }\n return cachedConfig;\n}\n\n/** Load user preferences.md (cached per process) */\nasync function getPreferences(): Promise<string> {\n if (cachedPreferences !== null) return cachedPreferences;\n try {\n cachedPreferences = await fs.readFile(getPreferencesPath(), \"utf-8\");\n } catch {\n cachedPreferences = \"\";\n }\n return cachedPreferences;\n}\n\n/** Create an OpenAI-compatible client from config */\nexport async function createLlmClient(): Promise<OpenAI> {\n const config = await getConfig();\n const apiKey = process.env[config.llm.apiKeyEnv];\n if (!apiKey) {\n throw new Error(\n `Missing API key: set ${config.llm.apiKeyEnv} environment variable`,\n );\n }\n return new OpenAI({\n apiKey,\n baseURL: config.llm.baseUrl || undefined,\n });\n}\n\n/**\n * Single LLM call with system + user messages.\n * Automatically prepends user preferences to the system prompt.\n * Retries once on failure.\n */\nexport async function llmCall(\n prompt: string,\n system: string,\n model?: string,\n): Promise<string> {\n const config = await getConfig();\n const client = await createLlmClient();\n const modelName = model ?? config.llm.cheapModel;\n\n // Inject user preferences into system prompt\n const prefs = await getPreferences();\n const fullSystem = prefs\n ? `${system}\\n\\n---USER PREFERENCES (always respect these)---\\n${prefs}`\n : system;\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const response = await client.chat.completions.create({\n model: modelName,\n messages: [\n { role: \"system\", content: fullSystem },\n { role: \"user\", content: prompt },\n ],\n temperature: 0.3,\n });\n return response.choices[0]?.message?.content ?? \"\";\n } catch (err) {\n if (attempt === 0) {\n // Retry once\n await new Promise((r) => setTimeout(r, 1000));\n continue;\n }\n throw err;\n }\n }\n return \"\"; // unreachable\n}\n\n/** Reset cached config and preferences (for testing) */\nexport function resetConfigCache(): void {\n cachedConfig = null;\n cachedPreferences = null;\n}\n","/** Build system prompt for the PINData extract step (replaces triage + distill) */\nexport function extractSystemPrompt(): string {\n return `Extract PINData entries from the input. Each entry is ONE atomic piece of personal knowledge.\n\nEntry types:\n- fact: concrete data point (name, number, date, account, version, error, relationship)\n- pref: implied choice or habit (tool preference, workflow pattern, interest signal)\n- decision: explicit decision with reasoning\n- entity: person, organization, or project with attributes\n- event: time-bound occurrence (meeting, deployment, payment, conference)\n\nTopic routing (pick the best fit):\n- context/identity — user's name, email, linked accounts\n- context/projects — project/repo/app names, deployments, tech stack\n- context/services — services/tools used, account IDs, subscriptions\n- preferences/tools — tool choices and why (e.g. Vercel for hosting, Airtable for PM)\n- preferences/workflow — work habits, communication style, patterns\n- work/activity — tasks, assignments, colleagues, meetings\n- work/finance — invoices, payments, amounts, subscriptions\n- life/interests — interests, events, communities, conferences\n- coding/lessons — coding lessons, debugging insights, tech decisions\n\nRules:\n- Each entry.content = ONE concrete fact with names/numbers/dates. Never generic advice.\n- Return [] (empty array) if input is pure marketing/spam with zero personal signal.\n- Prefer multiple small entries over one big entry.\n- Do NOT extract generic platitudes like \"monitor deployments regularly\".\n\nExamples:\n\nInput: \"Vercel: Failed deployment for pin-sandman on team pinai\"\nOutput: [\n {\"type\":\"fact\",\"content\":\"pin-sandman: Vercel production deployment failed, team pinai\",\"topic\":\"context/projects\",\"tags\":[\"vercel\",\"pin-sandman\"]},\n {\"type\":\"pref\",\"content\":\"Vercel: used for production deployment\",\"topic\":\"preferences/tools\",\"tags\":[\"vercel\"]}\n]\n\nInput: \"50% off all shoes this weekend only!\"\nOutput: []\n\nRespond with valid JSON array only (no markdown fences).`;\n}\n\n/** Build user prompt for extract */\nexport function extractUserPrompt(rawContent: string, source?: string): string {\n const sourceHint = source ? `\\n(Source: ${source})` : \"\";\n return `Extract PINData entries from this input:\n\n---INPUT---\n${rawContent}\n---END INPUT---\n${sourceHint}\nRespond with JSON array only.`;\n}\n\n/** Build system prompt for the daily digest */\nexport function digestSystemPrompt(): string {\n return `Summarize the day's journal entries and extracted knowledge into 3-5 key bullet points.\n\nRules:\n- Each bullet = one concrete takeaway (what happened, what was decided, what matters)\n- Include names, projects, and specific details — never vague\n- If there are action items or follow-ups, list them\n- Keep it under 200 words total\n- Output plain markdown bullets only, no JSON`;\n}\n\n/** Build user prompt for digest */\nexport function digestUserPrompt(\n journalContent: string,\n newPinData: string,\n date: string,\n): string {\n return `Summarize this day (${date}):\n\n---JOURNAL---\n${journalContent || \"(no journal entries)\"}\n---END JOURNAL---\n\n---NEW KNOWLEDGE EXTRACTED---\n${newPinData || \"(no new entries)\"}\n---END KNOWLEDGE---\n\nWrite 3-5 bullet points summarizing the day.`;\n}\n","import { llmCall } from \"../llm/index.js\";\nimport { extractSystemPrompt, extractUserPrompt } from \"../prompts/extract.js\";\nimport * as console from \"../utils/console.js\";\nimport type { PINDataEntry, PINDataType, ExtractResult } from \"../types.js\";\n\nconst VALID_TYPES = new Set<string>([\"fact\", \"pref\", \"decision\", \"entity\", \"event\"]);\n\n/**\n * Budget for the total text sent to LLM.\n * ~10K chars ≈ ~2.5K tokens (English) / ~5K tokens (CJK).\n * PINData extraction only needs gist, not full content.\n */\nconst MAX_INPUT_CHARS = 10_000;\n\n/** Head portion gets the lion's share — title, intro, overview */\nconst HEAD_CHARS = 4_000;\n/** Tail portion — conclusions, takeaways, resource lists */\nconst TAIL_CHARS = 3_000;\n/** Remaining budget goes to random middle samples */\nconst MIDDLE_BUDGET = MAX_INPUT_CHARS - HEAD_CHARS - TAIL_CHARS; // 3000\n/** Number of random middle samples to pick */\nconst MIDDLE_SAMPLES = 2;\n\n// ---------------------------------------------------------------------------\n// Content preparation — sampling > chunking\n// ---------------------------------------------------------------------------\n\n/**\n * For content that fits the budget, return as-is.\n * For long content, sample: head + tail + random middle paragraphs.\n *\n * Rationale: PINData extraction asks \"what does this mean to the USER\",\n * not \"summarize the entire document\". The head (title/intro) and tail\n * (conclusions/resources) carry 80%+ of personal signal. Middle sections\n * of long articles (paper tables, code listings, repetitive data) are\n * mostly noise for personal knowledge extraction.\n */\nfunction prepareContent(text: string): string {\n if (text.length <= MAX_INPUT_CHARS) return text;\n\n const head = text.slice(0, HEAD_CHARS);\n const tail = text.slice(-TAIL_CHARS);\n\n // Middle region: everything between head and tail\n const middleStart = HEAD_CHARS;\n const middleEnd = text.length - TAIL_CHARS;\n const middleText = text.slice(middleStart, middleEnd);\n\n // Split middle into paragraphs (double newline) and pick random samples\n const paragraphs = middleText\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 100); // skip tiny fragments\n\n const samples: string[] = [];\n let sampledChars = 0;\n const perSampleBudget = Math.floor(MIDDLE_BUDGET / MIDDLE_SAMPLES);\n\n if (paragraphs.length > 0) {\n // Pick evenly spaced paragraphs (deterministic, not truly random — reproducible)\n const step = Math.max(1, Math.floor(paragraphs.length / MIDDLE_SAMPLES));\n for (let i = 0; i < MIDDLE_SAMPLES && i * step < paragraphs.length; i++) {\n const para = paragraphs[i * step]!;\n const truncated = para.slice(0, perSampleBudget);\n samples.push(truncated);\n sampledChars += truncated.length;\n }\n }\n\n const middlePart =\n samples.length > 0\n ? `\\n\\n[... middle section sampled — ${(middleEnd - middleStart).toLocaleString()} chars total ...]\\n\\n${samples.join(\"\\n\\n---\\n\\n\")}`\n : \"\";\n\n const assembled = `${head}${middlePart}\\n\\n[... end section ...]\\n\\n${tail}`;\n\n console.info(\n `Content ${text.length.toLocaleString()} chars → sampled to ${assembled.length.toLocaleString()} chars ` +\n `(head:${HEAD_CHARS} + ${samples.length} mid-samples:${sampledChars} + tail:${TAIL_CHARS})`,\n );\n\n return assembled;\n}\n\n// ---------------------------------------------------------------------------\n// LLM response parsing\n// ---------------------------------------------------------------------------\n\n/** Parse one LLM JSON response into validated PINDataEntry[] */\nfunction parseExtractResponse(response: string): PINDataEntry[] {\n const cleaned = response\n .replace(/```json?\\n?/g, \"\")\n .replace(/```/g, \"\")\n .trim();\n const raw = JSON.parse(cleaned) as unknown;\n\n let rawEntries: unknown[];\n if (Array.isArray(raw)) {\n rawEntries = raw;\n } else if (\n raw &&\n typeof raw === \"object\" &&\n \"entries\" in raw &&\n Array.isArray((raw as Record<string, unknown>).entries)\n ) {\n rawEntries = (raw as Record<string, unknown>).entries as unknown[];\n } else {\n return [];\n }\n\n const entries: PINDataEntry[] = [];\n for (const item of rawEntries) {\n if (!item || typeof item !== \"object\") continue;\n const obj = item as Record<string, unknown>;\n\n const type = obj.type as string;\n const entryContent = obj.content as string;\n const topic = obj.topic as string;\n\n if (!type || !entryContent || !topic) continue;\n if (!VALID_TYPES.has(type)) continue;\n\n entries.push({\n type: type as PINDataType,\n content: entryContent.trim(),\n topic: topic.trim(),\n tags: Array.isArray(obj.tags)\n ? (obj.tags as unknown[]).filter(\n (t): t is string => typeof t === \"string\",\n )\n : undefined,\n });\n }\n\n return entries;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Extract PINData entries from raw/journal content via a single LLM call.\n * Long content is sampled (head + tail + middle samples) to fit the budget.\n */\nexport async function extractPinData(\n content: string,\n source?: string,\n): Promise<ExtractResult> {\n const system = extractSystemPrompt();\n const prepared = prepareContent(content);\n const prompt = extractUserPrompt(prepared, source);\n\n try {\n const response = await llmCall(prompt, system);\n const entries = parseExtractResponse(response);\n\n const summary =\n entries.length > 0\n ? entries\n .slice(0, 3)\n .map((e) => e.content)\n .join(\"; \")\n : \"no extractable signal\";\n\n return { entries, summary };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n entries: [],\n summary: `Failed to parse extract response: ${msg.slice(0, 100)}`,\n };\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getVaultDir } from \"../config/paths.js\";\nimport type { PINDataEntry, PINDataType } from \"../types.js\";\n\n/** Format a PINData entry as a markdown bullet line */\nexport function formatEntry(entry: PINDataEntry, date: string, ref?: string): string {\n const parts: string[] = [];\n parts.push(date);\n if (ref) parts.push(`ref:${ref}`);\n const meta = parts.join(\" | \");\n return `- [${entry.type}] ${entry.content} (${meta})`;\n}\n\n/** Parse a PINData line back into structured data */\nexport function parsePinDataLine(line: string): {\n type: PINDataType;\n content: string;\n date?: string;\n verified?: number;\n} | null {\n // Match: - [type] content (date | optional-meta | verified:N)\n const match = line.match(\n /^- \\[(\\w+)\\] (.+?) \\((\\d{4}-\\d{2}-\\d{2})([^)]*)\\)$/,\n );\n if (!match) return null;\n\n const meta = match[4] ?? \"\";\n const verifiedMatch = meta.match(/verified:(\\d+)/);\n\n return {\n type: match[1] as PINDataType,\n content: match[2]!,\n date: match[3],\n verified: verifiedMatch ? parseInt(verifiedMatch[1]!, 10) : undefined,\n };\n}\n\n/** Check if two PINData entries are semantically duplicate (same type + similar content) */\nfunction isDuplicate(existingContent: string, newEntry: PINDataEntry): {\n lineIndex: number;\n line: string;\n} | null {\n const lines = existingContent.split(\"\\n\");\n const newLower = newEntry.content.toLowerCase();\n // Extract key tokens from new entry (remove common words)\n const newTokens = new Set(\n newLower.split(/[\\s:,;]+/).filter((t) => t.length > 2),\n );\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n // Must match the same type tag\n if (!line.startsWith(`- [${newEntry.type}]`)) continue;\n\n const parsed = parsePinDataLine(line);\n if (!parsed) continue;\n\n const existLower = parsed.content.toLowerCase();\n const existTokens = new Set(\n existLower.split(/[\\s:,;]+/).filter((t) => t.length > 2),\n );\n\n // Calculate Jaccard similarity on significant tokens\n const intersection = [...newTokens].filter((t) => existTokens.has(t));\n const union = new Set([...newTokens, ...existTokens]);\n const similarity = union.size > 0 ? intersection.length / union.size : 0;\n\n // Threshold: 60% token overlap = duplicate\n if (similarity >= 0.6) {\n return { lineIndex: i, line };\n }\n }\n return null;\n}\n\n/**\n * Append PINData entries to the appropriate vault files.\n * - Routes each entry to vault/{topic}.md based on entry.topic\n * - Creates file + H1 header if new\n * - Deduplicates: if similar entry exists, bumps verified count + updates date\n * Returns number of new entries added and duplicates updated.\n */\nexport async function appendPinData(\n entries: PINDataEntry[],\n options: { date?: string; ref?: string } = {},\n): Promise<{ added: number; updated: number }> {\n const vaultDir = getVaultDir();\n const date = options.date ?? new Date().toISOString().split(\"T\")[0]!;\n let added = 0;\n let updated = 0;\n\n // Group entries by topic\n const byTopic = new Map<string, PINDataEntry[]>();\n for (const entry of entries) {\n const topic = entry.topic;\n if (!byTopic.has(topic)) byTopic.set(topic, []);\n byTopic.get(topic)!.push(entry);\n }\n\n for (const [topic, topicEntries] of byTopic) {\n const filePath = path.join(vaultDir, `${topic}.md`);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n // Read existing content\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch {\n // Create new file with H1 title derived from topic\n const title = topic\n .split(\"/\")\n .pop()!\n .replace(/-/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n content = `# ${title}\\n`;\n }\n\n for (const entry of topicEntries) {\n const dup = isDuplicate(content, entry);\n if (dup) {\n // Bump verified count and update date\n const parsed = parsePinDataLine(dup.line);\n const newVerified = (parsed?.verified ?? 1) + 1;\n const updatedLine = `- [${entry.type}] ${entry.content} (${date} | verified:${newVerified})`;\n const lines = content.split(\"\\n\");\n lines[dup.lineIndex] = updatedLine;\n content = lines.join(\"\\n\");\n updated++;\n } else {\n // Append new entry\n const line = formatEntry(entry, date, options.ref);\n content = content.trimEnd() + \"\\n\" + line + \"\\n\";\n added++;\n }\n }\n\n await fs.writeFile(filePath, content, \"utf-8\");\n }\n\n return { added, updated };\n}\n\n/** Read a vault file's content. Returns null if not found. */\nexport async function readVaultFile(topic: string): Promise<string | null> {\n const filePath = path.join(getVaultDir(), `${topic}.md`);\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** List all PINData entries across all vault files */\nexport async function listVaultEntries(): Promise<\n Array<{ topic: string; line: string; parsed: ReturnType<typeof parsePinDataLine> }>\n> {\n const vaultDir = getVaultDir();\n const results: Array<{\n topic: string;\n line: string;\n parsed: ReturnType<typeof parsePinDataLine>;\n }> = [];\n\n async function walk(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n const topic = path.relative(vaultDir, fullPath).replace(/\\.md$/, \"\");\n const content = await fs.readFile(fullPath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n if (line.startsWith(\"- [\")) {\n const parsed = parsePinDataLine(line);\n results.push({ topic, line, parsed });\n }\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n await walk(vaultDir);\n return results;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getMemoryDir, getJournalPath, getTodayDate } from \"../config/paths.js\";\n\n/** Append a log entry to today's journal file (memory/YYYY-MM-DD.md) */\nexport async function appendJournal(\n text: string,\n date?: string,\n): Promise<{ path: string; entryCount: number }> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n\n await fs.mkdir(getMemoryDir(), { recursive: true });\n\n const now = new Date();\n const time = now.toTimeString().slice(0, 5); // HH:MM\n const entry = `- ${time} ${text}\\n`;\n\n let existed = false;\n let existingContent = \"\";\n try {\n existingContent = await fs.readFile(filePath, \"utf-8\");\n existed = true;\n } catch {\n // File doesn't exist yet\n }\n\n if (!existed) {\n // Create new journal file with header\n const header = `# ${targetDate}\\n\\n`;\n await fs.writeFile(filePath, header + entry, \"utf-8\");\n } else {\n // Insert entry BEFORE extracted marker / digest section, and remove marker\n // so the journal will be re-processed on next distill\n const markerIdx = existingContent.indexOf(\"\\n<!-- extracted -->\");\n const digestIdx = existingContent.indexOf(\"\\n## Digest\");\n const insertIdx = Math.min(\n markerIdx >= 0 ? markerIdx : Infinity,\n digestIdx >= 0 ? digestIdx : Infinity,\n );\n\n if (insertIdx < Infinity) {\n // Insert before marker/digest, remove extracted marker\n const before = existingContent.slice(0, insertIdx).trimEnd();\n const after = existingContent.slice(insertIdx).replace(\"<!-- extracted -->\", \"\").trimStart();\n const newContent = before + \"\\n\" + entry + (after ? \"\\n\" + after : \"\") + \"\\n\";\n await fs.writeFile(filePath, newContent, \"utf-8\");\n } else {\n await fs.appendFile(filePath, entry, \"utf-8\");\n }\n }\n\n // Count entries in file\n const content = await fs.readFile(filePath, \"utf-8\");\n const entryCount = (content.match(/^- \\d{2}:\\d{2} /gm) || []).length;\n\n return { path: filePath, entryCount };\n}\n\n/** Read a journal file's content. Returns null if not found. */\nexport async function readJournal(date?: string): Promise<string | null> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** List all journal dates (YYYY-MM-DD) sorted descending */\nexport async function listJournalDates(): Promise<string[]> {\n const memDir = getMemoryDir();\n try {\n const entries = await fs.readdir(memDir);\n const dates = entries\n .filter((f) => /^\\d{4}-\\d{2}-\\d{2}\\.md$/.test(f))\n .map((f) => f.replace(\".md\", \"\"))\n .sort()\n .reverse();\n return dates;\n } catch {\n return [];\n }\n}\n\n/** Get status of a journal: entry count and whether it has a digest */\nexport async function getJournalStatus(\n date: string,\n): Promise<{ entries: number; hasDigest: boolean } | null> {\n const content = await readJournal(date);\n if (!content) return null;\n\n const entries = (content.match(/^- \\d{2}:\\d{2} /gm) || []).length;\n const hasDigest = content.includes(\"## Digest\");\n return { entries, hasDigest };\n}\n\n/** Find journal dates with missing entries in the last N days */\nexport async function findGaps(days: number = 30): Promise<string[]> {\n const existing = new Set(await listJournalDates());\n const gaps: string[] = [];\n\n const now = new Date();\n for (let i = 0; i < days; i++) {\n const d = new Date(now);\n d.setDate(d.getDate() - i);\n const dateStr = d.toISOString().split(\"T\")[0]!;\n if (!existing.has(dateStr)) {\n gaps.push(dateStr);\n }\n }\n\n return gaps;\n}\n\n/** Append a digest section to a journal file */\nexport async function appendDigest(\n digest: string,\n date?: string,\n): Promise<string> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch {\n // Create the file if it doesn't exist\n content = `# ${targetDate}\\n\\n`;\n }\n\n // Remove existing digest section if present (replace)\n const digestIndex = content.indexOf(\"\\n## Digest\");\n if (digestIndex !== -1) {\n content = content.slice(0, digestIndex);\n }\n\n const digestSection = `\\n## Digest\\n\\n${digest.trim()}\\n`;\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, content.trimEnd() + \"\\n\" + digestSection, \"utf-8\");\n\n return filePath;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { listPending } from \"../raw/index.js\";\nimport { extractPinData } from \"./extract.js\";\nimport { appendPinData } from \"../vault/index.js\";\nimport { readJournal } from \"../memory/index.js\";\nimport { getPaiHome, getTodayDate } from \"../config/paths.js\";\nimport { parseFrontmatter, updateRawFrontmatter } from \"../utils/frontmatter.js\";\nimport * as console from \"../utils/console.js\";\nimport type { RawFrontmatter, ExtractPipelineResult } from \"../types.js\";\n\nexport type { ExtractPipelineResult };\n\n/**\n * Run the PINData extract pipeline on pending raw files + unprocessed journals.\n * Flow: raw/journal content → 1 LLM extract call → PINData[] → code-append to vault\n */\nexport async function distillPipeline(\n options: { dryRun?: boolean; singleFile?: string; today?: boolean } = {},\n): Promise<ExtractPipelineResult> {\n const result: ExtractPipelineResult = {\n processed: 0,\n extracted: 0,\n discarded: 0,\n errors: [],\n };\n\n // Collect inputs: pending raw files + today's journal\n const inputs: Array<{ label: string; content: string; rawPath?: string; source: string }> = [];\n\n // 1) Pending raw files\n if (options.singleFile) {\n const raw = await fs.readFile(options.singleFile, \"utf-8\");\n const { content } = parseFrontmatter<RawFrontmatter>(raw);\n inputs.push({\n label: path.basename(options.singleFile),\n content,\n rawPath: options.singleFile,\n source: options.singleFile,\n });\n } else if (!options.today) {\n const pendingFiles = await listPending();\n for (const filePath of pendingFiles) {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { content } = parseFrontmatter<RawFrontmatter>(raw);\n inputs.push({\n label: path.basename(filePath),\n content,\n rawPath: filePath,\n source: filePath,\n });\n }\n }\n\n // 2) Today's journal (if --today or default run)\n if (!options.singleFile) {\n const todayDate = getTodayDate();\n const journal = await readJournal(todayDate);\n if (journal && journal.trim().length > 0) {\n // Only include if not already processed (check for marker)\n if (!journal.includes(\"<!-- extracted -->\")) {\n inputs.push({\n label: `journal/${todayDate}`,\n content: journal,\n source: `memory/${todayDate}.md`,\n });\n }\n }\n }\n\n if (inputs.length === 0) {\n console.info(\"No pending raw files or journal entries to process.\");\n return result;\n }\n\n console.info(`Found ${inputs.length} input(s) to process.`);\n\n for (const input of inputs) {\n const spin = console.spinner(`Extracting ${input.label}...`);\n\n try {\n // Single LLM call: extract PINData entries\n const extracted = await extractPinData(input.content, input.source);\n result.processed++;\n\n if (extracted.entries.length === 0) {\n spin.succeed(`Skipped: ${input.label} — ${extracted.summary}`);\n result.discarded++;\n\n // Mark raw file as discarded\n if (input.rawPath && !options.dryRun) {\n const rawContent = await fs.readFile(input.rawPath, \"utf-8\");\n const updated = updateRawFrontmatter(rawContent, { status: \"discarded\" });\n await fs.writeFile(input.rawPath, updated, \"utf-8\");\n }\n continue;\n }\n\n if (options.dryRun) {\n const topics = [...new Set(extracted.entries.map((e) => e.topic))];\n spin.succeed(\n `[DRY RUN] ${input.label} → ${extracted.entries.length} entries → ${topics.join(\", \")}`,\n );\n result.extracted += extracted.entries.length;\n continue;\n }\n\n // Code-controlled append to vault files (no LLM merge needed)\n const date = new Date().toISOString().split(\"T\")[0]!;\n const { added, updated } = await appendPinData(extracted.entries, {\n date,\n ref: input.source,\n });\n\n result.extracted += extracted.entries.length;\n const topics = [...new Set(extracted.entries.map((e) => e.topic))];\n\n // Mark raw file as processed\n if (input.rawPath) {\n const rawContent = await fs.readFile(input.rawPath, \"utf-8\");\n const updatedRaw = updateRawFrontmatter(rawContent, {\n status: \"processed\",\n distilled_to: topics.join(\", \"),\n });\n await fs.writeFile(input.rawPath, updatedRaw, \"utf-8\");\n }\n\n // Mark journal as extracted\n if (!input.rawPath && input.source.startsWith(\"memory/\")) {\n const journalPath = path.join(getPaiHome(), input.source);\n try {\n const journalContent = await fs.readFile(journalPath, \"utf-8\");\n if (!journalContent.includes(\"<!-- extracted -->\")) {\n await fs.appendFile(journalPath, \"\\n<!-- extracted -->\\n\", \"utf-8\");\n }\n } catch {\n // Journal file might not exist\n }\n }\n\n spin.succeed(\n `Extracted ${input.label} → ${added} new, ${updated} updated → ${topics.join(\", \")}`,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spin.fail(`Error processing ${input.label}: ${msg}`);\n result.errors.push(`${input.label}: ${msg}`);\n }\n }\n\n return result;\n}\n","import type { Command } from \"commander\";\nimport { distillPipeline } from \"../distill/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerDistillCommand(program: Command): void {\n program\n .command(\"distill\")\n .description(\"Extract PINData from pending raw files and journal into vault\")\n .option(\"--file <path>\", \"Process a single raw file\")\n .option(\"--today\", \"Only process today's journal\")\n .option(\"--dry-run\", \"Preview what would be extracted without writing\")\n .action(async (opts: { file?: string; today?: boolean; dryRun?: boolean }) => {\n try {\n const result = await distillPipeline({\n singleFile: opts.file,\n today: opts.today,\n dryRun: opts.dryRun,\n });\n\n console.log(\"\");\n console.log(console.bold(\"Distill summary:\"));\n console.log(` Processed: ${result.processed}`);\n console.log(` Extracted: ${result.extracted} PINData entries`);\n console.log(` Discarded: ${result.discarded}`);\n\n if (result.errors.length > 0) {\n console.log(` Errors: ${result.errors.length}`);\n for (const e of result.errors) {\n console.error(` ${e}`);\n }\n }\n console.log(\"\");\n\n if (result.extracted > 0 && !opts.dryRun) {\n console.info(\n 'Vault updated with PINData entries. Run \"pai generate\" to update SKILL.md profiles.',\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Distill failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","/** Build system prompt for SKILL.md generation */\nexport function generateSystemPrompt(): string {\n return `You generate a SKILL.md — a minimal, high-density context file for an AI agent to understand the user.\nEvery line MUST be useful to an AI agent. If a line wouldn't change how an agent responds, delete it.\n\nCOMPRESSION RULES:\n- Identity: pack into 2-3 lines max (name | email | role | languages | timezone — merge onto fewer lines)\n- Preferences: write as \"prefers X for Y\" not just \"uses X\". State the WHY or CONTEXT.\n GOOD: \"Deploys on Vercel (team pinai), manages tasks in Airtable, meetings via Zoom (paid)\"\n BAD: \"Tools: Vercel, Zoom, Docker, npm, Airtable\" (just a list, no context)\n- Interests: compress bookmarks/domains/events into TAGS, not raw lists\n GOOD: \"Interests: AI/LLM, Web3 (ETHDenver 2026), GIS, iOS, prediction markets (Kalshi)\"\n BAD: \"Bookmarks: Favorites Bar 106项, Read 78项, Tools 67项...\" (raw data dump)\n- Current Work: what are they ACTIVELY building? Project names + what they do, not directory paths\n GOOD: \"Building personal-ai (v0.1.0, npm published), PINAI ecosystem (PIN-APP-IOS, pin-sandman on Vercel)\"\n BAD: \"Recent Working Directories: ..: 8次, backend: 2次\"\n- Lessons: ONLY copy verbatim lessons that exist in vault/coding/ or vault/work/ files.\n NEVER INVENT OR PARAPHRASE lessons. If the vault has no explicit lesson, write \"None recorded yet.\"\n A deployment failure is a FACT (goes in Current Work), NOT a lesson.\n GOOD: \"FastAPI CORS: must explicitly list allowed_origins, wildcard doesn't work with credentials\"\n BAD: \"生产环境部署失败可能导致项目延误\" ← THIS IS INVENTED, NEVER DO THIS\n BAD: \"及时关注部署通知有助于快速定位问题\" ← THIS IS GENERIC ADVICE, NEVER DO THIS\n- Financial/services: compress into 1-2 lines\n GOOD: \"Paid services: Zoom (acct 5144380543), BytePlus (acct 3000767749, ~200 USD credits)\"\n- Team: mention key collaborators briefly\n GOOD: \"Works with Ethan Liu (pinai team) on Prediction project via Airtable\"\n\nANTI-PATTERNS — never do these:\n- Raw data dumps (bookmark counts, domain visit counts, directory lists)\n- Generic advice disguised as lessons\n- Separate lines for each identity field (merge them)\n- Lists of tool names without context on how/why they're used\n- \"Account Activities\" as a separate section (fold into Preferences or Current Work)`;\n}\n\n/** Build user prompt for SKILL.md generation */\nexport function generateUserPrompt(\n profileName: string,\n vaultContents: string,\n maxLines: number,\n): string {\n return `Generate SKILL.md \"${profileName}\" from vault content. Max ${maxLines} lines.\n\nSections:\n# Personal Context — ${profileName}\n## Who I Am (2-4 lines: identity, languages, timezone — packed dense)\n## Preferences (tool choices WITH context, workflow habits, communication style)\n## Interests (compressed tags from bookmarks/domains/events, NOT raw lists)\n## Current Work (active projects with names, what they do, team members)\n## Paid Services (services with account IDs, amounts — 1-3 lines)\n## Hard-won Lessons (ONLY real lessons from vault, or 1 line \"None yet\")\n\n---VAULT CONTENT---\n${vaultContents}\n---END VAULT CONTENT---\n\nCRITICAL: Compress, don't dump. Every line must be useful to an AI agent.\n- Merge identity fields onto fewer lines (name | email | timezone on one line)\n- Write preferences as \"prefers X for Y\" not bare lists\n- Convert bookmark counts and domain visits into interest TAGS\n- Never write generic advice as lessons\n- Never write raw data (dir paths, bookmark counts, visit numbers)`;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport { loadProfiles } from \"../config/io.js\";\nimport { getVaultDir, getSkillsDir } from \"../config/paths.js\";\nimport { llmCall } from \"../llm/index.js\";\nimport {\n generateSystemPrompt,\n generateUserPrompt,\n} from \"../prompts/generate.js\";\nimport * as console from \"../utils/console.js\";\n\n/** Recursively list all markdown files in a directory */\nasync function walkMd(dir: string): Promise<string[]> {\n const results: string[] = [];\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await walkMd(full)));\n } else if (entry.name.endsWith(\".md\")) {\n results.push(full);\n }\n }\n } catch {\n // Ignore errors\n }\n return results;\n}\n\n/** Collect vault file contents matching scope patterns */\nasync function collectVaultContent(scope: string[]): Promise<string> {\n const vaultDir = getVaultDir();\n const allFiles = await walkMd(vaultDir);\n const matched: string[] = [];\n\n for (const file of allFiles) {\n const relative = \"vault/\" + path.relative(vaultDir, file);\n // Check if this file matches any scope pattern\n for (const pattern of scope) {\n if (minimatch(relative, pattern)) {\n matched.push(file);\n break;\n }\n }\n }\n\n // Read and concatenate all matched files\n const contents: string[] = [];\n for (const file of matched) {\n try {\n const content = await fs.readFile(file, \"utf-8\");\n contents.push(`--- ${path.relative(vaultDir, file)} ---\\n${content}`);\n } catch {\n // Skip unreadable files\n }\n }\n\n return contents.join(\"\\n\\n\");\n}\n\n/** Generate a single SKILL.md profile */\nexport async function generateProfile(profileName: string): Promise<string> {\n const profiles = await loadProfiles();\n const profileDef = profiles.profiles[profileName];\n\n if (!profileDef) {\n throw new Error(`Profile \"${profileName}\" not found in profiles.json5`);\n }\n\n const vaultContent = await collectVaultContent(profileDef.scope);\n\n if (!vaultContent.trim()) {\n throw new Error(\n `No vault content found for profile \"${profileName}\". Run 'pai distill' first.`,\n );\n }\n\n const system = generateSystemPrompt();\n const prompt = generateUserPrompt(\n profileName,\n vaultContent,\n profileDef.maxLines,\n );\n\n const skillMd = await llmCall(prompt, system);\n\n // Write to skills/profiles/\n const skillsDir = getSkillsDir();\n await fs.mkdir(skillsDir, { recursive: true });\n const outPath = path.join(skillsDir, `${profileName}.md`);\n await fs.writeFile(outPath, skillMd.trim() + \"\\n\", \"utf-8\");\n\n return outPath;\n}\n\n/** Generate all profiles defined in profiles.json5 */\nexport async function generateAll(): Promise<string[]> {\n const profiles = await loadProfiles();\n const profileNames = Object.keys(profiles.profiles);\n\n if (profileNames.length === 0) {\n console.warn(\"No profiles defined in profiles.json5\");\n return [];\n }\n\n const results: string[] = [];\n for (const name of profileNames) {\n const spin = console.spinner(`Generating profile: ${name}...`);\n try {\n const outPath = await generateProfile(name);\n spin.succeed(`Generated ${name} → ${outPath}`);\n results.push(outPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spin.fail(`Failed to generate ${name}: ${msg}`);\n }\n }\n\n return results;\n}\n","import type { Command } from \"commander\";\nimport { generateProfile, generateAll } from \"../generate/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerGenerateCommand(program: Command): void {\n program\n .command(\"generate\")\n .description(\"Generate SKILL.md profiles from vault content\")\n .option(\"--profile <name>\", \"Generate a specific profile only\")\n .action(async (opts: { profile?: string }) => {\n try {\n if (opts.profile) {\n const spin = console.spinner(\n `Generating profile: ${opts.profile}...`,\n );\n try {\n const outPath = await generateProfile(opts.profile);\n spin.succeed(`Generated: ${outPath}`);\n } catch (err) {\n spin.fail(\"Generation failed\");\n throw err;\n }\n } else {\n const results = await generateAll();\n if (results.length > 0) {\n console.log(\"\");\n console.success(\n `Generated ${results.length} SKILL.md profile(s).`,\n );\n console.info(\n \"These files can be used by AI agents (Cursor, Claude, etc.).\",\n );\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Generate failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { execQmd, isQmdAvailable } from \"../utils/process.js\";\nimport { getVaultDir, getRawDir } from \"../config/paths.js\";\nimport type { SearchResult } from \"../types.js\";\n\nexport type SearchMode = \"query\" | \"search\" | \"vsearch\";\n\n/**\n * Search with automatic fallback:\n * 1. QMD available → use QMD (query/search/vsearch modes)\n * 2. QMD not available → built-in grep search (keyword matching, no install needed)\n *\n * QMD modes:\n * - \"query\" (default): BM25 + vector + expansion + reranking (~5s, best quality)\n * - \"search\": BM25 keyword (~50ms, fast)\n * - \"vsearch\": Vector similarity only (~2s, semantic)\n *\n * For \"search\" (fast) mode, QMD BM25 doesn't support CJK tokenization,\n * so we supplement with grep to cover Chinese/Japanese/Korean.\n */\nexport async function search(\n query: string,\n collection: string = \"vault\",\n n: number = 5,\n mode: SearchMode = \"query\",\n): Promise<SearchResult[]> {\n // Fallback to built-in search when QMD is not available\n if (!(await isQmdAvailable())) {\n return builtinSearch(query, collection, n);\n }\n\n const stdout = await execQmd([\n mode,\n query,\n \"--collection\",\n collection,\n \"--json\",\n \"-n\",\n String(n),\n ]);\n\n let results = parseSearchResults(stdout);\n\n // BM25 can't tokenize CJK — supplement with grep for fast mode\n if (mode === \"search\" && results.length === 0 && hasCjk(query)) {\n results = await grepFallback(query, collection, n);\n }\n\n return results;\n}\n\n/**\n * Built-in keyword search — zero external dependencies.\n * Reads markdown files and does case-insensitive substring matching.\n * Good enough for small-medium collections (< 10k files).\n */\nasync function builtinSearch(\n query: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const baseDir = collection === \"raw\" ? getRawDir() : getVaultDir();\n\n // Try grep first (fast, available on all unix systems)\n const grepResults = await grepFallback(query, collection, n);\n if (grepResults.length > 0) return grepResults;\n\n // Final fallback: pure Node.js file scan (no external tools at all)\n return nodeFsSearch(query, baseDir, collection, n);\n}\n\n/**\n * Pure Node.js file search — absolute zero dependencies.\n * Recursively reads .md files and matches query as case-insensitive substring.\n */\nasync function nodeFsSearch(\n query: string,\n baseDir: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const results: SearchResult[] = [];\n const queryLower = query.toLowerCase();\n\n async function walk(dir: string): Promise<void> {\n let entries;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (results.length >= n) return;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(full);\n } else if (entry.name.endsWith(\".md\")) {\n try {\n const content = await fs.readFile(full, \"utf-8\");\n const idx = content.toLowerCase().indexOf(queryLower);\n if (idx !== -1) {\n const snippetStart = Math.max(0, idx - 40);\n const snippetEnd = Math.min(content.length, idx + query.length + 120);\n const snippet = content.slice(snippetStart, snippetEnd).replace(/\\n/g, \" \").trim();\n const titleMatch = content.match(/^#\\s+(.+)/m);\n const relative = path.relative(baseDir, full);\n results.push({\n file: `${collection}/${relative}`,\n title: titleMatch?.[1]?.trim() ?? path.basename(full, \".md\"),\n snippet,\n score: 1.0,\n });\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n }\n\n await walk(baseDir);\n return results;\n}\n\n/** Parse QMD JSON search output into SearchResult[] */\nfunction parseSearchResults(stdout: string): SearchResult[] {\n try {\n const parsed = JSON.parse(stdout);\n if (!Array.isArray(parsed)) return [];\n return parsed.map((item: Record<string, unknown>) => ({\n file: (item.file as string) ?? \"\",\n title: (item.title as string) ?? undefined,\n snippet:\n (item.snippet as string) ??\n (item.content as string)?.slice(0, 200) ??\n \"\",\n score: item.score as number | undefined,\n }));\n } catch {\n return [];\n }\n}\n\n/** Check if a string contains CJK characters */\nfunction hasCjk(text: string): boolean {\n return /[\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/.test(text);\n}\n\n/**\n * grep fallback for CJK text that BM25 can't tokenize.\n * Runs: grep -rl \"query\" <dir> --include=\"*.md\"\n * Then reads matched files and extracts context.\n */\nasync function grepFallback(\n query: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const baseDir = collection === \"raw\" ? getRawDir() : getVaultDir();\n\n // grep -rl: recursive, list filenames only\n const stdout = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-rl\", \"--include=*.md\", query, baseDir],\n { encoding: \"utf-8\", timeout: 5_000 },\n (_err, out) => {\n // grep exits 1 when no matches — that's fine\n resolve(out ?? \"\");\n },\n );\n });\n\n if (!stdout.trim()) return [];\n\n const files = stdout.trim().split(\"\\n\").slice(0, n);\n const results: SearchResult[] = [];\n\n for (const filePath of files) {\n if (!filePath) continue;\n // Get context line with grep -m1 (first match with surrounding text)\n const context = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-m1\", \"-C1\", query, filePath],\n { encoding: \"utf-8\", timeout: 2_000 },\n (_err, out) => resolve(out ?? \"\"),\n );\n });\n\n const snippet = context.replace(/\\n/g, \" \").trim().slice(0, 160);\n const relative = path.relative(baseDir, filePath);\n // Extract title: first H1 line via grep\n const titleLine = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-m1\", \"^# \", filePath],\n { encoding: \"utf-8\", timeout: 1_000 },\n (_err, out) => resolve(out?.replace(/^#\\s+/, \"\").trim() ?? \"\"),\n );\n });\n\n results.push({\n file: `qmd://${collection}/${relative}`,\n title: titleLine || path.basename(filePath, \".md\"),\n snippet,\n score: 1.0,\n });\n }\n\n return results;\n}\n\n/** Update QMD index: runs `qmd update` + `qmd embed` */\nexport async function updateIndex(): Promise<void> {\n if (!(await isQmdAvailable())) {\n throw new Error(\n \"QMD is not installed. Built-in search works without indexing.\\n\" +\n \"For better search quality, install QMD: npm install -g https://github.com/tobi/qmd\",\n );\n }\n await execQmd([\"update\"]);\n await execQmd([\"embed\"]);\n}\n","import type { Command } from \"commander\";\nimport { search, type SearchMode } from \"../search/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerSearchCommand(program: Command): void {\n program\n .command(\"search <query>\")\n .description(\"Search vault (default) or raw via QMD\")\n .option(\"--raw\", \"Search raw collection instead of vault\")\n .option(\"--all\", \"Search all collections\")\n .option(\"--fast\", \"BM25 keyword search only (instant, no AI)\")\n .option(\"--vector\", \"Vector similarity search only\")\n .option(\"--json\", \"Output results as JSON (for agent consumption)\")\n .option(\"-n, --num <number>\", \"Number of results\", \"5\")\n .action(\n async (\n query: string,\n opts: {\n raw?: boolean;\n all?: boolean;\n fast?: boolean;\n vector?: boolean;\n json?: boolean;\n num: string;\n },\n ) => {\n try {\n const n = parseInt(opts.num, 10);\n const collection = opts.raw ? \"raw\" : opts.all ? undefined : \"vault\";\n const mode: SearchMode = opts.fast\n ? \"search\"\n : opts.vector\n ? \"vsearch\"\n : \"query\";\n\n if (opts.all) {\n const [vaultResults, rawResults] = await Promise.all([\n search(query, \"vault\", n, mode),\n search(query, \"raw\", n, mode),\n ]);\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify({ vault: vaultResults, raw: rawResults }, null, 2) + \"\\n\",\n );\n return;\n }\n\n console.log(\"\");\n console.log(console.bold(`Search results for: \"${query}\"`));\n\n if (vaultResults.length > 0) {\n console.log(\"\\n\" + console.bold(\"Vault:\"));\n for (const r of vaultResults) {\n printResult(r);\n }\n }\n\n if (rawResults.length > 0) {\n console.log(\"\\n\" + console.bold(\"Raw:\"));\n for (const r of rawResults) {\n printResult(r);\n }\n }\n\n if (vaultResults.length === 0 && rawResults.length === 0) {\n console.info(\"No results found.\");\n }\n } else {\n const results = await search(query, collection, n, mode);\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(results, null, 2) + \"\\n\");\n return;\n }\n\n console.log(\"\");\n console.log(\n console.bold(\n `Search results for: \"${query}\" (${collection ?? \"all\"})`,\n ),\n );\n\n if (results.length === 0) {\n console.info(\"No results found.\");\n } else {\n for (const r of results) {\n printResult(r);\n }\n }\n }\n console.log(\"\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Search failed: ${msg}`);\n process.exit(1);\n }\n },\n );\n}\n\nfunction printResult(r: { file: string; title?: string; snippet: string; score?: number }): void {\n const score = r.score != null ? console.dim(` (${r.score.toFixed(2)})`) : \"\";\n console.log(` ${console.bold(r.title ?? r.file)}${score}`);\n console.log(` ${console.dim(r.snippet.slice(0, 120))}`);\n console.log(` ${console.dim(r.file)}`);\n console.log(\"\");\n}\n","import type { Command } from \"commander\";\nimport { updateIndex } from \"../search/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerIndexCommand(program: Command): void {\n program\n .command(\"index\")\n .description(\"Update QMD index (qmd update + qmd embed)\")\n .action(async () => {\n const spin = console.spinner(\"Updating QMD index...\");\n try {\n await updateIndex();\n spin.succeed(\"QMD index updated successfully.\");\n } catch (err) {\n spin.fail(\"Index update failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getRawDir } from \"../config/paths.js\";\nimport { generateRawFilename } from \"../utils/slug.js\";\nimport { createRawFile } from \"../utils/frontmatter.js\";\nimport { addConnectorEntry } from \"../raw/add.js\";\nimport type { RawFrontmatter } from \"../types.js\";\nimport * as console from \"../utils/console.js\";\nimport { encryption } from \"../auth/encryption.js\";\nimport { googleOAuth } from \"../auth/google-oauth.js\";\n\nexport function registerImportCommand(program: Command): void {\n program\n .command(\"import\")\n .description(\"Import data from connector or directory (use --source mac for system scan)\")\n .requiredOption(\"--source <source>\", \"Data source (mac, gmail, calendar, twitter, etc.)\")\n .option(\"--path <dir>\", \"Path to data directory or file (not needed for mac, gmail, calendar)\")\n .option(\"--dry-run\", \"Preview what would be imported without writing\")\n .option(\"--days <n>\", \"For gmail/calendar: last N days to fetch (default 30)\", \"30\")\n .option(\"--query <q>\", \"For gmail: Gmail search query (e.g. is:important)\")\n .action(\n async (opts: {\n source: string;\n path?: string;\n dryRun?: boolean;\n days?: string;\n query?: string;\n }) => {\n try {\n // --- Mac connector: auto-discover, no --path needed ---\n if (opts.source === \"mac\") {\n const { scanMac } = await import(\"../connectors/mac/index.js\");\n const result = await scanMac({ dryRun: opts.dryRun });\n\n if (opts.dryRun) return;\n\n console.log(\"\");\n console.success(\n `Mac scan complete: ${result.created} created, ${result.updated} updated, ${result.skipped} unchanged`,\n );\n if (result.failed.length > 0) {\n console.warn(`${result.failed.length} failed: ${result.failed.join(\", \")}`);\n }\n console.info('Run \"pai distill\" to process scanned data.');\n return;\n }\n\n // --- Gmail connector ---\n if (opts.source === \"gmail\") {\n const { syncGmail } = await import(\"../connectors/google/gmail.js\");\n const parsedDays = parseInt(opts.days ?? \"30\", 10);\n const days = Number.isNaN(parsedDays) ? 30 : parsedDays;\n const doSync = () =>\n syncGmail({ days, query: opts.query, maxResults: 100 });\n await encryption.loadKey();\n await googleOAuth.init();\n let entries: Awaited<ReturnType<typeof syncGmail>>;\n const spinGmail =\n opts.dryRun ? null : console.spinner(\"Syncing Gmail...\");\n try {\n entries = await doSync();\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"Not authenticated\")\n ) {\n spinGmail?.stop();\n console.info(\"Not authenticated; starting Google OAuth...\");\n await googleOAuth.authorize();\n const spinRetry =\n opts.dryRun ? null : console.spinner(\"Syncing Gmail...\");\n entries = await doSync();\n spinRetry?.stop();\n } else {\n throw err;\n }\n } finally {\n spinGmail?.stop();\n }\n if (opts.dryRun) {\n console.info(`Would import ${entries.length} Gmail message(s).`);\n return;\n }\n let created = 0;\n let updated = 0;\n let skipped = 0;\n for (const entry of entries) {\n const status = await addConnectorEntry(\"gmail\", entry);\n if (status === \"created\") created++;\n else if (status === \"updated\") updated++;\n else skipped++;\n }\n console.success(\n `Gmail import: ${created} created, ${updated} updated, ${skipped} unchanged`,\n );\n console.info('Run \"pai distill\" to process imported data.');\n return;\n }\n\n // --- Calendar connector ---\n if (opts.source === \"calendar\") {\n const { syncCalendar } = await import(\"../connectors/google/calendar.js\");\n const parsedDays = parseInt(opts.days ?? \"30\", 10);\n const days = Number.isNaN(parsedDays) ? 30 : parsedDays;\n const doSync = () =>\n syncCalendar({ lookbackDays: days, lookforwardDays: 90 });\n await encryption.loadKey();\n await googleOAuth.init();\n let entries: Awaited<ReturnType<typeof syncCalendar>>;\n const spinCal =\n opts.dryRun ? null : console.spinner(\"Syncing Calendar...\");\n try {\n entries = await doSync();\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"Not authenticated\")\n ) {\n spinCal?.stop();\n console.info(\"Not authenticated; starting Google OAuth...\");\n await googleOAuth.authorize();\n const spinRetry =\n opts.dryRun ? null : console.spinner(\"Syncing Calendar...\");\n entries = await doSync();\n spinRetry?.stop();\n } else {\n throw err;\n }\n } finally {\n spinCal?.stop();\n }\n if (opts.dryRun) {\n console.info(`Would import ${entries.length} calendar event(s).`);\n return;\n }\n let created = 0;\n let updated = 0;\n let skipped = 0;\n for (const entry of entries) {\n const status = await addConnectorEntry(\"calendar\", entry);\n if (status === \"created\") created++;\n else if (status === \"updated\") updated++;\n else skipped++;\n }\n console.success(\n `Calendar import: ${created} created, ${updated} updated, ${skipped} unchanged`,\n );\n console.info('Run \"pai distill\" to process imported data.');\n return;\n }\n\n // --- Path-based import (existing behavior) ---\n if (!opts.path) {\n console.error(\n \"--path is required for this source. Usage: pai import --source <name> --path <dir>\",\n );\n process.exit(1);\n }\n\n const sourcePath = opts.path;\n const source = `connector/${opts.source}`;\n const stat = await fs.stat(sourcePath);\n\n const outDir = path.join(getRawDir(), \"connector\", opts.source);\n await fs.mkdir(outDir, { recursive: true });\n\n let imported = 0;\n\n if (stat.isDirectory()) {\n // Import all markdown/text files from directory\n const entries = await fs.readdir(sourcePath, { withFileTypes: true });\n for (const entry of entries) {\n if (\n entry.isFile() &&\n (entry.name.endsWith(\".md\") ||\n entry.name.endsWith(\".txt\") ||\n entry.name.endsWith(\".json\"))\n ) {\n const content = await fs.readFile(\n path.join(sourcePath, entry.name),\n \"utf-8\",\n );\n const title = path.basename(entry.name, path.extname(entry.name));\n const filename = generateRawFilename(title);\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n await fs.writeFile(\n path.join(outDir, filename),\n fileContent,\n \"utf-8\",\n );\n imported++;\n }\n }\n } else {\n // Import single file\n const content = await fs.readFile(sourcePath, \"utf-8\");\n const title = path.basename(sourcePath, path.extname(sourcePath));\n const filename = generateRawFilename(title);\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n await fs.writeFile(\n path.join(outDir, filename),\n fileContent,\n \"utf-8\",\n );\n imported = 1;\n }\n\n console.success(`Imported ${imported} file(s) from ${opts.source}`);\n console.info('Run \"pai distill\" to process imported data.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Import failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getVaultDir, getSkillsDir } from \"../config/paths.js\";\nimport { listPending, listAll } from \"../raw/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Show pai data status overview\")\n .option(\"--json\", \"Output as JSON (for agent consumption)\")\n .action(async (opts: { json?: boolean }) => {\n try {\n // Count raw files\n const allRaw = await listAll();\n const pendingRaw = await listPending();\n\n // Count vault files\n const vaultFiles = await countFiles(getVaultDir());\n\n // Count skill profiles\n const skillFiles = await countFiles(getSkillsDir());\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n raw: { total: allRaw.length, pending: pendingRaw.length },\n vault: { files: vaultFiles },\n profiles: skillFiles,\n },\n null,\n 2,\n ) + \"\\n\",\n );\n return;\n }\n\n console.log(\"\");\n console.log(console.bold(\"pai status\"));\n console.log(\"─\".repeat(40));\n console.log(` Raw files: ${allRaw.length} total, ${pendingRaw.length} pending`);\n console.log(` Vault files: ${vaultFiles}`);\n console.log(` SKILL profiles: ${skillFiles}`);\n console.log(\"\");\n\n if (pendingRaw.length > 0) {\n console.info(\n `${pendingRaw.length} file(s) waiting to be distilled. Run \"pai distill\" to process.`,\n );\n }\n\n if (vaultFiles === 0) {\n console.info(\n 'No vault content yet. Run \"pai add <text>\" then \"pai distill\".',\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Status check failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\nasync function countFiles(dir: string): Promise<number> {\n let count = 0;\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n count += await countFiles(full);\n } else if (entry.name.endsWith(\".md\")) {\n count++;\n }\n }\n } catch {\n // Directory doesn't exist\n }\n return count;\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport readline from \"node:readline\";\nimport { getPaiHome } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nfunction askConfirm(question: string): Promise<boolean> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(/^y|yes$/i.test(answer.trim()));\n });\n });\n}\n\nexport function registerResetCommand(program: Command): void {\n program\n .command(\"reset\")\n .description(\"Clear all pai data (remove ~/.pai directory)\")\n .option(\"--force\", \"Skip confirmation prompt\")\n .action(async (opts: { force?: boolean }) => {\n const paiHome = getPaiHome();\n\n if (!opts.force) {\n const confirmed = await askConfirm(\n `Remove ALL data at ${paiHome}? [y/N] `,\n );\n if (!confirmed) {\n console.info(\"Reset cancelled.\");\n return;\n }\n }\n\n const spin = console.spinner(`Clearing pai data at ${paiHome}...`);\n try {\n await fs.rm(paiHome, { recursive: true, force: true });\n spin.succeed(`Cleared ${paiHome}`);\n console.info(\"Run \\\"pai init\\\" to set up again.\");\n } catch (err) {\n spin.fail(\"Reset failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","import os from \"node:os\";\nimport { getPaiHome } from \"../config/paths.js\";\n\n/**\n * System prompt for the ask agent (personal AI secretary).\n * Built in sections, similar to clawdbot system-prompt.ts.\n */\nexport function buildAskSystemPrompt(): string {\n const sections: string[] = [];\n const paiHome = getPaiHome();\n const homeDir = os.homedir();\n\n sections.push(\n `You are a personal AI secretary who knows the user intimately.\nYour job: answer questions about the user accurately, specifically, and honestly.\nToday's date: ${new Date().toISOString().split(\"T\")[0]}\nUser home: ${homeDir}\nPAI data directory: ${paiHome}`,\n );\n\n sections.push(`## Data Layout\n${paiHome}/\n├── profile.md # Compiled user profile (identity, env, projects)\n├── raw/ # Original immutable data with timestamps\n│ ├── local/ # Manual input\n│ ├── web/ # URL scrapes\n│ └── connector/ # Imported data from connectors (dynamic — use queryConnector to discover)\n├── vault/ # Distilled knowledge (experiences, preferences, lessons)\n│ ├── coding/\n│ ├── context/\n│ ├── life/\n│ └── preferences/\n└── config/ # Configuration files`);\n\n sections.push(`## Tool Strategy\n\n### Knowledge tools (use first — fast, structured)\n- readProfile: Read compiled user profile — fastest way to understand who the user is\n- searchVault: Search distilled knowledge. Ranked by RELEVANCE, not recency.\n- searchRaw: Search original raw data. Has timestamps in frontmatter. Ranked by RELEVANCE, not recency.\n\n### Connector data tool (for time/fact-based questions — calendar, email, etc.)\n- queryConnector: Query imported data from ANY connector source.\n - Call WITHOUT source → discovers available sources (calendar, gmail, mac, or any future connector)\n - Call WITH source + range → returns entries sorted by date, with structured fields\n - Supports range: upcoming/today/this_week/past_week/past_month/all\n - ALWAYS use this for schedule, email, or any time-sensitive imported data questions\n - Examples: \"下一个会议\" → queryConnector(source=\"calendar\", range=\"upcoming\", limit=3)\n - Examples: \"最近邮件\" → queryConnector(source=\"gmail\", range=\"past_week\", sort=\"desc\")\n\n### Filesystem tools (use to go deeper)\n- readFile: Read full content of any file. ALWAYS use this after search finds a relevant file — search snippets are too short to answer well.\n- grep: Search file contents with regex (ripgrep)\n- glob: Find files by pattern\n- ls: List directory contents\n- bash: Execute shell commands\n\n### CRITICAL: Always read files after searching\nSearch tools return short snippets. When you find relevant results:\n1. Use readFile to read the full content of the top results\n2. Extract specific details (names, dates, numbers) from the full text\n3. NEVER answer based on search snippets alone — they are incomplete\n\n### Recommended flow\n1. For calendar/email/time-sensitive data → use queryConnector FIRST (time-aware, structured)\n2. readProfile → understand who the user is\n3. searchVault/searchRaw → find relevant topics\n4. readFile → read full content of promising results\n5. grep/glob/bash → dig deeper if needed\n\n### Time-sensitive queries\nsearchVault and searchRaw rank by relevance, NOT by recency.\nWhen the user asks about schedules, recent events, emails, or activity:\n- Use queryConnector — it parses dates from content and supports time range filtering\n- For system state: use bash (e.g. \\`git -C <project_dir> log --oneline -20 --since=\"1 week ago\"\\`)\n- Do NOT rely solely on search for time-based questions — search has no time ranking`);\n\n sections.push(`## Rules\n- ONLY answer based on data from tools — NEVER fabricate information\n- If you cannot find the answer after thorough searching, say so honestly\n- Be specific: use real names, versions, dates, numbers from the data\n- Be concise but substantive: answer like a knowledgeable personal secretary\n- NEVER give empty or placeholder answers — if data is sparse, say what you found and what's missing\n- When citing sources, prefer qmd:// paths from vault/raw searches`);\n\n return sections.join(\"\\n\\n\");\n}\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { execFile } from \"node:child_process\";\nimport type { Dirent } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst BASH_TIMEOUT_MS = 30_000;\nconst BASH_OUTPUT_MAX_BYTES = 10 * 1024;\n\nfunction resolvePath(raw: string): string {\n const trimmed = raw.trim();\n if (trimmed.startsWith(\"~\")) {\n return path.join(os.homedir(), trimmed.slice(1).replace(/^\\//, \"\"));\n }\n return path.resolve(trimmed);\n}\n\n// ---------- readFile ----------\n\nconst ReadFileParams = Type.Object({\n path: Type.String({ description: \"Absolute or relative file path\" }),\n offset: Type.Optional(Type.Number({ description: \"Start line (1-based)\" })),\n limit: Type.Optional(\n Type.Number({ description: \"Number of lines to read\" }),\n ),\n});\n\nexport const readFileTool: AgentTool<typeof ReadFileParams> = {\n name: \"readFile\",\n label: \"Read File\",\n description:\n \"Read file contents. Supports offset/limit for large files. Path can be absolute or relative; ~ expands to home directory.\",\n parameters: ReadFileParams,\n execute: async (_toolCallId, params) => {\n const resolved = resolvePath(params.path);\n try {\n const raw = await fs.readFile(resolved, \"utf-8\");\n const lines = raw.split(\"\\n\");\n const start = Math.min(\n lines.length,\n params.offset != null ? Math.max(0, params.offset - 1) : 0,\n );\n const end =\n params.limit != null\n ? Math.min(lines.length, start + params.limit)\n : lines.length;\n const slice = lines.slice(start, end).join(\"\\n\");\n return {\n content: [{ type: \"text\", text: slice }],\n details: {\n path: resolved,\n lines: end - start,\n totalLines: lines.length,\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg, path: resolved },\n };\n }\n },\n};\n\n// ---------- grep ----------\n\nconst GrepParams = Type.Object({\n pattern: Type.String({ description: \"Regex pattern to search\" }),\n path: Type.Optional(\n Type.String({ description: \"File or directory to search in\" }),\n ),\n glob: Type.Optional(\n Type.String({ description: \"File glob filter, e.g. '*.ts'\" }),\n ),\n maxResults: Type.Optional(\n Type.Number({\n description: \"Max matching lines to return (default 20)\",\n }),\n ),\n});\n\nexport const grepTool: AgentTool<typeof GrepParams> = {\n name: \"grep\",\n label: \"Grep\",\n description:\n \"Search file contents using ripgrep (rg). Returns matching lines with context.\",\n parameters: GrepParams,\n execute: async (_toolCallId, params) => {\n const maxResults = params.maxResults ?? 20;\n const args = [\"-n\", \"-m\", String(maxResults), params.pattern];\n if (params.path) {\n args.push(resolvePath(params.path));\n }\n if (params.glob) {\n args.push(\"--glob\", params.glob);\n }\n return new Promise((resolve) => {\n execFile(\n \"rg\",\n args,\n { encoding: \"utf-8\", timeout: 10_000, maxBuffer: 512 * 1024 },\n (err, stdout, stderr) => {\n if (err) {\n if ((err as NodeJS.ErrnoException).code === \"1\" || err.code === 1) {\n resolve({\n content: [{ type: \"text\", text: \"No matches found.\" }],\n details: { matches: [], count: 0 },\n });\n return;\n }\n resolve({\n content: [\n {\n type: \"text\",\n text: `Error: ${stderr?.trim() || err.message}`,\n },\n ],\n details: { error: stderr?.trim() || err.message },\n });\n return;\n }\n const lines = stdout.trim().split(\"\\n\").slice(0, maxResults);\n resolve({\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { matches: lines, count: lines.length },\n });\n },\n );\n });\n },\n};\n\n// ---------- glob ----------\n\nasync function globWalk(\n dir: string,\n pattern: string,\n results: string[],\n baseDir: string,\n): Promise<void> {\n const { minimatch } = await import(\"minimatch\");\n const mm = new minimatch.Minimatch(pattern, { dot: true });\n let entries: Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const full = path.join(dir, e.name);\n const relative = path.relative(baseDir, full);\n if (e.isDirectory()) {\n await globWalk(full, pattern, results, baseDir);\n } else if (mm.match(relative) || mm.match(e.name)) {\n results.push(full);\n }\n }\n}\n\nconst GlobParams = Type.Object({\n pattern: Type.String({ description: \"Glob pattern, e.g. '**/*.md'\" }),\n cwd: Type.Optional(\n Type.String({ description: \"Base directory (default: current)\" }),\n ),\n});\n\nexport const globTool: AgentTool<typeof GlobParams> = {\n name: \"glob\",\n label: \"Find Files\",\n description:\n \"Find files matching a glob pattern. Returns file paths. Pattern e.g. '**/*.md' or '*.ts'.\",\n parameters: GlobParams,\n execute: async (_toolCallId, params) => {\n const baseDir = params.cwd ? resolvePath(params.cwd) : process.cwd();\n const results: string[] = [];\n await globWalk(baseDir, params.pattern, results, baseDir);\n const limited = results.slice(0, 200);\n return {\n content: [\n {\n type: \"text\",\n text:\n limited.length > 0\n ? limited.join(\"\\n\")\n : \"No files matched the pattern.\",\n },\n ],\n details: { paths: limited, count: results.length },\n };\n },\n};\n\n// ---------- ls ----------\n\nconst LsParams = Type.Object({\n path: Type.String({ description: \"Directory path\" }),\n});\n\nexport const lsTool: AgentTool<typeof LsParams> = {\n name: \"ls\",\n label: \"List Directory\",\n description:\n \"List directory contents with file types and sizes. Path can be absolute or ~/...\",\n parameters: LsParams,\n execute: async (_toolCallId, params) => {\n const resolved = resolvePath(params.path);\n try {\n const entries = await fs.readdir(resolved, { withFileTypes: true });\n const items: { name: string; type: string; size?: number }[] = [];\n for (const e of entries) {\n const item: { name: string; type: string; size?: number } = {\n name: e.name,\n type: e.isDirectory() ? \"dir\" : \"file\",\n };\n if (e.isFile()) {\n try {\n const stat = await fs.stat(path.join(resolved, e.name));\n item.size = stat.size;\n } catch {\n // ignore\n }\n }\n items.push(item);\n }\n const lines = items.map(\n (i) =>\n `${i.type === \"dir\" ? \"📁\" : \"📄\"} ${i.name}${i.size != null ? ` (${i.size}B)` : \"\"}`,\n );\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { path: resolved, entries: items },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg, path: resolved },\n };\n }\n },\n};\n\n// ---------- bash ----------\n\nconst BashParams = Type.Object({\n command: Type.String({ description: \"Shell command to execute\" }),\n cwd: Type.Optional(Type.String({ description: \"Working directory\" })),\n});\n\nexport const bashTool: AgentTool<typeof BashParams> = {\n name: \"bash\",\n label: \"Shell\",\n description:\n \"Execute a shell command. Use for checking system state, running pai commands, etc. Timeout: 30s. Output truncated to 10KB.\",\n parameters: BashParams,\n execute: async (_toolCallId, params) => {\n const workDir = params.cwd ? resolvePath(params.cwd) : process.cwd();\n return new Promise((resolve) => {\n execFile(\n \"bash\",\n [\"-c\", params.command],\n {\n encoding: \"utf-8\",\n timeout: BASH_TIMEOUT_MS,\n maxBuffer: BASH_OUTPUT_MAX_BYTES,\n cwd: workDir,\n },\n (err, stdout, stderr) => {\n if (err) {\n const out = [\n stdout ? `stdout: ${stdout.slice(0, 2000)}` : \"\",\n stderr ? `stderr: ${stderr.slice(0, 2000)}` : \"\",\n `error: ${err.message}`,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n resolve({\n content: [{ type: \"text\", text: out }],\n details: {\n exitCode: err.code ?? -1,\n error: err.message,\n },\n });\n return;\n }\n const output = (stdout ?? \"\").slice(0, BASH_OUTPUT_MAX_BYTES);\n const stderrTrimmed = (stderr ?? \"\").trim();\n const text = stderrTrimmed\n ? `${output}\\nstderr: ${stderrTrimmed}`\n : output;\n resolve({\n content: [{ type: \"text\", text }],\n details: { exitCode: 0 },\n });\n },\n );\n });\n },\n};\n","/**\n * Generic connector data query tool for the ask agent.\n * Dynamically discovers available data sources under ~/.pai/raw/connector/\n * and provides unified time-sorted access to any connector's data.\n *\n * No hardcoded connector types — works with calendar, gmail, mac, or any future source.\n */\n\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPaiHome } from \"../config/paths.js\";\n\n// ---------- Generic date extraction ----------\n\n/**\n * Try to extract a meaningful content date from markdown body.\n * Checks common field patterns across connector types:\n * **Time**: ... (calendar events)\n * **Date**: ... (emails, generic)\n * **Created**: ...\n * Falls back to frontmatter timestamp (import time).\n */\nfunction extractContentDate(\n content: string,\n frontmatter: Record<string, unknown>,\n): Date | null {\n // Try content fields in priority order\n const patterns = [\n /\\*\\*Time\\*\\*:\\s*(\\S+)/, // calendar: \"**Time**: 2026-02-12T09:00:00+08:00 (tz)\"\n /\\*\\*Date\\*\\*:\\s*(.+)/, // gmail: \"**Date**: Sat, 31 Jan 2026 03:29:45 +0000\"\n /\\*\\*Created\\*\\*:\\s*(.+)/, // generic\n ];\n\n for (const re of patterns) {\n const m = content.match(re);\n if (m) {\n // For **Date** style, the entire value is a date string\n // For **Time** style, only the first token is the date\n const raw = re === patterns[0] ? m[1] : m[1].trim();\n const d = new Date(raw);\n if (!isNaN(d.getTime())) return d;\n }\n }\n\n // Fallback: frontmatter timestamp (import time)\n if (typeof frontmatter.timestamp === \"string\") {\n const d = new Date(frontmatter.timestamp);\n if (!isNaN(d.getTime())) return d;\n }\n\n return null;\n}\n\n/** Extract title from \"# Title\" in markdown content */\nfunction extractTitle(content: string): string {\n const m = content.match(/^#\\s+(.+)/m);\n return m ? m[1].trim() : \"(no title)\";\n}\n\n/** Extract all \"- **Field**: value\" pairs from markdown content */\nfunction extractFields(content: string): Record<string, string> {\n const fields: Record<string, string> = {};\n const re = /\\*\\*(\\w[\\w\\s]*?)\\*\\*:\\s*(.+)/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(content)) !== null) {\n fields[match[1].trim()] = match[2].trim();\n }\n return fields;\n}\n\n// ---------- File reading ----------\n\ninterface ParsedEntry {\n file: string;\n title: string;\n date: Date | null;\n fields: Record<string, string>;\n frontmatter: Record<string, unknown>;\n}\n\n/** Read and parse all markdown files in a connector subdirectory */\nasync function readConnectorDir(subdir: string): Promise<ParsedEntry[]> {\n const dir = path.join(getPaiHome(), \"raw\", \"connector\", subdir);\n let fileNames: string[];\n try {\n fileNames = (await fs.readdir(dir)).filter((f) => f.endsWith(\".md\"));\n } catch {\n return [];\n }\n\n const results: ParsedEntry[] = [];\n const BATCH = 50;\n\n for (let i = 0; i < fileNames.length; i += BATCH) {\n const batch = fileNames.slice(i, i + BATCH);\n const parsed = await Promise.all(\n batch.map(async (f) => {\n const filePath = path.join(dir, f);\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { data, content } = matter(raw);\n const fm = data as Record<string, unknown>;\n return {\n file: filePath,\n title: extractTitle(content),\n date: extractContentDate(content, fm),\n fields: extractFields(content),\n frontmatter: fm,\n };\n } catch {\n return null;\n }\n }),\n );\n for (const p of parsed) {\n if (p) results.push(p);\n }\n }\n\n return results;\n}\n\n// ---------- queryConnector tool ----------\n\nconst QueryConnectorParams = Type.Object({\n source: Type.Optional(\n Type.String({\n description:\n \"Connector source to query (e.g. 'calendar', 'gmail', 'mac'). Omit to list all available sources.\",\n }),\n ),\n range: Type.Optional(\n Type.Union(\n [\n Type.Literal(\"upcoming\"),\n Type.Literal(\"today\"),\n Type.Literal(\"this_week\"),\n Type.Literal(\"past_week\"),\n Type.Literal(\"past_month\"),\n Type.Literal(\"all\"),\n ],\n {\n description:\n \"Time range filter. 'upcoming' = future events only, 'today' = today, 'this_week' = ±7 days, 'past_week' = last 7 days, 'past_month' = last 30 days, 'all' = no filter. Default: 'all'.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Number({ description: \"Max entries to return (default 10)\" }),\n ),\n query: Type.Optional(\n Type.String({\n description: \"Optional text filter on title/content (case-insensitive)\",\n }),\n ),\n sort: Type.Optional(\n Type.Union([Type.Literal(\"asc\"), Type.Literal(\"desc\")], {\n description:\n \"Sort by date: 'asc' = oldest first (good for upcoming events), 'desc' = newest first (good for emails). Default: auto-detected based on range.\",\n }),\n ),\n});\n\nexport const queryConnectorTool: AgentTool<typeof QueryConnectorParams> = {\n name: \"queryConnector\",\n label: \"Query Connector Data\",\n description:\n \"Query imported data from any connector source (calendar, gmail, mac, etc.). \" +\n \"Call WITHOUT 'source' to discover available connectors. \" +\n \"Call WITH 'source' to query entries sorted by date. \" +\n \"Supports time range filtering and text search. \" +\n \"Use this for ALL time-sensitive questions (schedules, recent emails, upcoming events).\",\n parameters: QueryConnectorParams,\n execute: async (_toolCallId, params) => {\n const connectorDir = path.join(getPaiHome(), \"raw\", \"connector\");\n\n // --- Discovery mode: list available sources ---\n if (!params.source) {\n let subdirs: string[];\n try {\n const entries = await fs.readdir(connectorDir, { withFileTypes: true });\n subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return {\n content: [\n {\n type: \"text\",\n text: \"No connector data found. Run 'pai import' to import data.\",\n },\n ],\n details: { sources: [] },\n };\n }\n\n if (subdirs.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No connector sources found. Run 'pai import --source <source>' to import data.\",\n },\n ],\n details: { sources: [] },\n };\n }\n\n // Get file counts and date ranges for each source\n const summaries: { name: string; files: number; sample: string }[] = [];\n for (const sub of subdirs) {\n const subPath = path.join(connectorDir, sub);\n try {\n const files = (await fs.readdir(subPath)).filter((f) =>\n f.endsWith(\".md\"),\n );\n // Peek at the latest file for a sample title\n let sample = \"\";\n if (files.length > 0) {\n try {\n const latest = path.join(subPath, files[files.length - 1]);\n const raw = await fs.readFile(latest, \"utf-8\");\n const { content } = matter(raw);\n sample = extractTitle(content);\n } catch {\n // ignore\n }\n }\n summaries.push({ name: sub, files: files.length, sample });\n } catch {\n summaries.push({ name: sub, files: 0, sample: \"\" });\n }\n }\n\n const text = summaries\n .map(\n (s) =>\n `- **${s.name}**: ${s.files} files${s.sample ? ` (latest: \"${s.sample}\")` : \"\"}`,\n )\n .join(\"\\n\");\n\n return {\n content: [\n {\n type: \"text\",\n text: `Available connector sources:\\n${text}\\n\\nCall queryConnector with source=\"<name>\" to query a specific source.`,\n },\n ],\n details: {\n sources: summaries.map((s) => ({\n name: s.name,\n files: s.files,\n })),\n },\n };\n }\n\n // --- Query mode: read and filter entries from a specific source ---\n const entries = await readConnectorDir(params.source);\n if (entries.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No data found for source \"${params.source}\". Check available sources by calling queryConnector without a source parameter.`,\n },\n ],\n details: { results: [], totalImported: 0 },\n };\n }\n\n const now = new Date();\n const range = params.range ?? \"all\";\n const limit = params.limit ?? 10;\n const queryLower = params.query?.toLowerCase();\n\n // Filter\n const filtered = entries.filter((e) => {\n // Text filter\n if (queryLower) {\n const haystack = (\n e.title +\n \" \" +\n Object.values(e.fields).join(\" \")\n ).toLowerCase();\n if (!haystack.includes(queryLower)) return false;\n }\n\n // Time filter (skip entries without dates for time-filtered queries)\n if (range !== \"all\") {\n if (!e.date) return false;\n const t = e.date.getTime();\n const nowMs = now.getTime();\n const DAY = 24 * 60 * 60 * 1000;\n\n switch (range) {\n case \"upcoming\":\n if (t < nowMs) return false;\n break;\n case \"today\": {\n const startOfDay = new Date(now);\n startOfDay.setHours(0, 0, 0, 0);\n const endOfDay = new Date(now);\n endOfDay.setHours(23, 59, 59, 999);\n if (t < startOfDay.getTime() || t > endOfDay.getTime())\n return false;\n break;\n }\n case \"this_week\":\n if (t < nowMs - 7 * DAY || t > nowMs + 7 * DAY) return false;\n break;\n case \"past_week\":\n if (t < nowMs - 7 * DAY || t > nowMs) return false;\n break;\n case \"past_month\":\n if (t < nowMs - 30 * DAY || t > nowMs) return false;\n break;\n }\n }\n\n return true;\n });\n\n // Sort — auto-detect direction based on range\n const sortDir =\n params.sort ??\n (range === \"upcoming\" || range === \"today\" || range === \"this_week\"\n ? \"asc\"\n : \"desc\");\n\n filtered.sort((a, b) => {\n const ta = a.date?.getTime() ?? 0;\n const tb = b.date?.getTime() ?? 0;\n return sortDir === \"asc\" ? ta - tb : tb - ta;\n });\n\n const limited = filtered.slice(0, limit);\n\n if (limited.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No entries matched for source \"${params.source}\", range \"${range}\". Total imported: ${entries.length}`,\n },\n ],\n details: { results: [], totalImported: entries.length },\n };\n }\n\n // Format output — show title, date, and all extracted fields\n const text = limited\n .map((e, i) => {\n const parts = [`${i + 1}. **${e.title}**`];\n // Show all structured fields from content\n for (const [key, val] of Object.entries(e.fields)) {\n parts.push(` ${key}: ${val}`);\n }\n // Add date if not already shown via fields\n if (e.date && !e.fields[\"Time\"] && !e.fields[\"Date\"]) {\n parts.push(` Date: ${e.date.toISOString()}`);\n }\n return parts.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${filtered.length} entries (showing ${limited.length}) from \"${params.source}\":\\n\\n${text}`,\n },\n ],\n details: {\n results: limited.map((e) => ({\n file: e.file,\n title: e.title,\n date: e.date?.toISOString() ?? null,\n fields: e.fields,\n })),\n totalMatched: filtered.length,\n totalImported: entries.length,\n },\n };\n },\n};\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { loadProfile } from \"../profile/index.js\";\nimport { search } from \"../search/index.js\";\nimport { isQmdAvailable } from \"../utils/process.js\";\n\n// ---------- searchVault ----------\n\nconst SearchParams = Type.Object({\n query: Type.String({ description: \"Search query\" }),\n limit: Type.Optional(\n Type.Number({ description: \"Max results (default 5)\" }),\n ),\n});\n\nexport const searchVaultTool: AgentTool<typeof SearchParams> = {\n name: \"searchVault\",\n label: \"Search Vault\",\n description:\n \"Search user's distilled knowledge vault (experiences, preferences, lessons). Hybrid search: BM25 + vector + reranking. Ranked by RELEVANCE, not recency. For time-sensitive queries, supplement with bash/grep on raw files.\",\n parameters: SearchParams,\n execute: async (_toolCallId, params) => {\n if (!(await isQmdAvailable())) {\n return {\n content: [{ type: \"text\", text: \"Error: QMD not installed.\" }],\n details: { error: \"QMD not installed\" },\n };\n }\n try {\n const results = await search(\n params.query,\n \"vault\",\n params.limit ?? 5,\n \"query\",\n );\n const text = results\n .map(\n (r) =>\n `## ${r.title ?? \"Untitled\"}\\nFile: ${r.file}\\n${r.snippet ?? \"\"}`,\n )\n .join(\"\\n\\n\");\n return {\n content: [{ type: \"text\", text: text || \"No results found.\" }],\n details: {\n results: results.map((r) => ({\n file: r.file,\n title: r.title,\n snippet: r.snippet,\n })),\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg },\n };\n }\n },\n};\n\n// ---------- searchRaw ----------\n\nexport const searchRawTool: AgentTool<typeof SearchParams> = {\n name: \"searchRaw\",\n label: \"Search Raw\",\n description:\n \"Search user's original raw data (for tracing back to sources). Raw files have timestamps in frontmatter. Ranked by RELEVANCE, not recency.\",\n parameters: SearchParams,\n execute: async (_toolCallId, params) => {\n if (!(await isQmdAvailable())) {\n return {\n content: [{ type: \"text\", text: \"Error: QMD not installed.\" }],\n details: { error: \"QMD not installed\" },\n };\n }\n try {\n const results = await search(\n params.query,\n \"raw\",\n params.limit ?? 5,\n \"query\",\n );\n const text = results\n .map(\n (r) =>\n `## ${r.title ?? \"Untitled\"}\\nFile: ${r.file}\\n${r.snippet ?? \"\"}`,\n )\n .join(\"\\n\\n\");\n return {\n content: [{ type: \"text\", text: text || \"No results found.\" }],\n details: {\n results: results.map((r) => ({\n file: r.file,\n title: r.title,\n snippet: r.snippet,\n })),\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg },\n };\n }\n },\n};\n\n// ---------- readProfile ----------\n\nconst EmptyParams = Type.Object({});\n\nexport const readProfileTool: AgentTool<typeof EmptyParams> = {\n name: \"readProfile\",\n label: \"Read Profile\",\n description:\n \"Read the user's compiled profile (identity, environment, tools, projects, habits). This is the fastest way to understand who the user is.\",\n parameters: EmptyParams,\n execute: async () => {\n const profile = await loadProfile();\n const text = profile ?? \"No profile found. Run 'pai init' first.\";\n return {\n content: [{ type: \"text\", text }],\n details: { hasProfile: !!profile },\n };\n },\n};\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport {\n bashTool,\n globTool,\n grepTool,\n lsTool,\n readFileTool,\n} from \"./tools-meta.js\";\nimport { queryConnectorTool } from \"./tools-connector.js\";\nimport {\n readProfileTool,\n searchRawTool,\n searchVaultTool,\n} from \"./tools-pai.js\";\n\nexport interface CreateAskToolsOptions {\n /** Enable bash tool (prompt-injection risk — only enable when you trust all data sources) */\n allowBash?: boolean;\n}\n\n/** Create the full ask agent tool set: pai knowledge tools + filesystem meta tools */\nexport function createAskTools(opts?: CreateAskToolsOptions): AgentTool<any>[] {\n const tools: AgentTool<any>[] = [\n // pai knowledge tools (use first — fast, structured)\n searchVaultTool,\n searchRawTool,\n readProfileTool,\n // connector data tool (calendar, gmail, mac, or any future source)\n queryConnectorTool,\n // filesystem meta tools (use when knowledge tools aren't enough)\n readFileTool,\n grepTool,\n globTool,\n lsTool,\n ];\n if (opts?.allowBash) {\n tools.push(bashTool);\n }\n return tools;\n}\n","import { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { AgentEvent } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport fs from \"node:fs/promises\";\nimport { loadConfig } from \"../config/index.js\";\nimport { getPreferencesPath } from \"../config/paths.js\";\nimport type { AskResult, AskUsage } from \"../types.js\";\nimport { buildAskSystemPrompt } from \"./system-prompt.js\";\nimport { createAskTools } from \"./tools.js\";\n\n/**\n * Resolve a model from pai config.\n * - If custom baseUrl is set, construct an openai-completions Model for compatibility.\n * - Otherwise try the pi-ai registry first, then fallback to a manual Model.\n */\nfunction resolveModel(modelId: string, baseUrl?: string): Model<any> {\n const trimmedBase = baseUrl?.trim() || undefined;\n\n // Custom baseUrl → always use openai-completions for third-party compatibility\n if (trimmedBase) {\n return {\n id: modelId,\n name: modelId,\n api: \"openai-completions\" as const,\n provider: \"openai\",\n baseUrl: trimmedBase,\n reasoning: false,\n input: [\"text\" as const],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 16384,\n };\n }\n\n // Try pi-ai registry (typed, but we cast for dynamic model IDs)\n const registered = getModel(\"openai\", modelId as any);\n if (registered) return registered;\n\n // Fallback: unknown model on standard OpenAI\n return {\n id: modelId,\n name: modelId,\n api: \"openai-completions\" as const,\n provider: \"openai\",\n baseUrl: \"https://api.openai.com/v1\",\n reasoning: false,\n input: [\"text\" as const],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 16384,\n };\n}\n\n/** Build full system prompt with user preferences injected */\nasync function getSystemPrompt(): Promise<string> {\n const base = buildAskSystemPrompt();\n try {\n const prefs = await fs.readFile(getPreferencesPath(), \"utf-8\");\n if (prefs.trim()) {\n return `${base}\\n\\n---USER PREFERENCES (always respect these)---\\n${prefs}`;\n }\n } catch {\n // no preferences file\n }\n return base;\n}\n\n/** Knowledge tool names whose results count as citable sources */\nconst KNOWLEDGE_TOOLS = new Set([\n \"searchVault\",\n \"searchRaw\",\n \"readProfile\",\n \"readFile\",\n \"queryConnector\",\n]);\n\n/** Extract source file paths from pai knowledge tool results only (skip glob/ls/bash noise) */\nfunction extractSources(messages: any[]): string[] {\n const sources = new Set<string>();\n for (const msg of messages) {\n if (msg.role !== \"toolResult\") continue;\n if (!KNOWLEDGE_TOOLS.has(msg.toolName)) continue;\n const details = msg.details;\n if (!details) continue;\n // searchVault / searchRaw results\n if (Array.isArray(details.results)) {\n for (const r of details.results) {\n if (typeof r.file === \"string\" && r.file) sources.add(r.file);\n }\n }\n // readFile path\n if (typeof details.path === \"string\" && details.path) {\n sources.add(details.path);\n }\n }\n return [...sources];\n}\n\n/** Aggregate token usage from all assistant messages */\nfunction aggregateUsage(messages: any[]): AskUsage {\n const usage: AskUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n totalTokens: 0,\n cost: 0,\n };\n for (const msg of messages) {\n if (msg.role === \"assistant\" && msg.usage) {\n const u = msg.usage;\n usage.inputTokens += u.input ?? 0;\n usage.outputTokens += u.output ?? 0;\n usage.cacheReadTokens += u.cacheRead ?? 0;\n usage.cacheWriteTokens += u.cacheWrite ?? 0;\n usage.totalTokens += u.totalTokens ?? 0;\n if (u.cost) {\n usage.cost += u.cost.total ?? 0;\n }\n }\n }\n return usage;\n}\n\n/** Extract text answer from the last assistant message */\nfunction extractAnswer(messages: any[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n const texts: string[] = [];\n for (const c of msg.content) {\n if (c.type === \"text\") texts.push(c.text);\n }\n if (texts.length > 0) return texts.join(\"\");\n }\n }\n return \"\";\n}\n\nexport interface AskAgentOptions {\n maxSteps?: number;\n model?: string;\n /** Enable the bash tool (disabled by default due to prompt-injection risk) */\n allowBash?: boolean;\n onEvent?: (event: AgentEvent) => void;\n}\n\nexport async function runAskAgent(\n question: string,\n opts?: AskAgentOptions,\n): Promise<AskResult> {\n const config = await loadConfig();\n const apiKey = process.env[config.llm.apiKeyEnv];\n if (!apiKey) {\n throw new Error(\n `Missing API key: set ${config.llm.apiKeyEnv} environment variable`,\n );\n }\n\n const modelId = opts?.model ?? config.llm.cheapModel;\n const model = resolveModel(modelId, config.llm.baseUrl);\n const system = await getSystemPrompt();\n const tools = createAskTools({ allowBash: opts?.allowBash });\n\n const agent = new Agent({\n initialState: {\n systemPrompt: system,\n model,\n tools,\n },\n getApiKey: async () => apiKey,\n });\n\n // Subscribe to events for verbose / streaming output.\n // Enforce maxSteps by aborting the agent when the turn limit is reached.\n const maxSteps = opts?.maxSteps ?? 10;\n let turnCount = 0;\n let toolCallCount = 0;\n const unsubscribe = agent.subscribe((event) => {\n if (event.type === \"turn_end\") {\n turnCount++;\n if (turnCount >= maxSteps) {\n agent.abort();\n }\n }\n if (event.type === \"tool_execution_start\") {\n toolCallCount++;\n }\n opts?.onEvent?.(event);\n });\n\n const startTime = performance.now();\n try {\n await agent.prompt(question);\n } finally {\n unsubscribe();\n }\n const durationMs = Math.round(performance.now() - startTime);\n\n const messages = agent.state.messages;\n const answer = extractAnswer(messages);\n const sources = extractSources(messages);\n const usage = aggregateUsage(messages);\n\n // Extract model and stopReason from the last assistant message\n let actualModel = modelId;\n let stopReason = \"unknown\";\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i] as any;\n if (m.role === \"assistant\") {\n if (m.model) actualModel = m.model;\n if (m.stopReason) stopReason = m.stopReason;\n break;\n }\n }\n\n return {\n answer,\n sources,\n steps: turnCount,\n durationMs,\n usage,\n model: actualModel,\n toolCalls: toolCallCount,\n stopReason,\n };\n}\n","import type { Command } from \"commander\";\nimport type { AgentEvent } from \"@mariozechner/pi-agent-core\";\nimport { runAskAgent } from \"../ask/index.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface AskOpts {\n json?: boolean;\n steps?: number;\n model?: string;\n verbose?: boolean;\n unsafeBash?: boolean;\n}\n\nexport function registerAskCommand(program: Command): void {\n program\n .command(\"ask <question>\")\n .description(\n \"Ask a question about the user; agent uses tools (search, read, bash) to find the answer\",\n )\n .option(\"--json\", \"Output as JSON (answer, sources, steps)\")\n .option(\"--steps <n>\", \"Max tool-call steps\", \"10\")\n .option(\"--model <name>\", \"Override LLM model\")\n .option(\"--verbose\", \"Show each tool step\")\n .option(\n \"--unsafe-bash\",\n \"Enable bash tool (risk: model can execute arbitrary commands)\",\n )\n .action(async (question: string, opts: AskOpts) => {\n if (!question?.trim()) {\n console.error(\"Usage: pai ask <question>\");\n process.exit(1);\n }\n try {\n const onEvent = opts.verbose\n ? (event: AgentEvent) => {\n if (event.type === \"tool_execution_start\") {\n console.log(\n `[tool] ${event.toolName}(${JSON.stringify(event.args ?? {}).slice(0, 80)}...)`,\n );\n }\n if (event.type === \"tool_execution_end\") {\n const status = event.isError ? \"✗\" : \"✓\";\n console.log(`[tool] ${event.toolName} ${status}`);\n }\n }\n : undefined;\n\n const result = await runAskAgent(question.trim(), {\n maxSteps: parseInt(String(opts.steps), 10) || 10,\n model: opts.model,\n allowBash: opts.unsafeBash,\n onEvent,\n });\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n answer: result.answer,\n sources: result.sources,\n model: result.model,\n steps: result.steps,\n toolCalls: result.toolCalls,\n stopReason: result.stopReason,\n durationMs: result.durationMs,\n usage: result.usage,\n },\n null,\n 2,\n ) + \"\\n\",\n );\n } else {\n process.stdout.write(result.answer + \"\\n\");\n if (result.sources.length > 0) {\n process.stdout.write(\n \"\\n_Sources: \" + result.sources.join(\", \") + \"_\\n\",\n );\n }\n // Display metrics\n const { usage, durationMs, steps, model, toolCalls } = result;\n const secs = (durationMs / 1000).toFixed(1);\n const tps =\n durationMs > 0\n ? ((usage.outputTokens / durationMs) * 1000).toFixed(1)\n : \"–\";\n const costStr =\n usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : \"\";\n const cacheStr =\n usage.cacheReadTokens > 0\n ? ` · cache: ${usage.cacheReadTokens} read`\n : \"\";\n\n const parts = [\n model,\n `${secs}s`,\n `${steps} steps · ${toolCalls} tool calls`,\n `${usage.totalTokens} tokens (in: ${usage.inputTokens}, out: ${usage.outputTokens})`,\n `${tps} tok/s`,\n ];\n if (cacheStr) parts.push(cacheStr.replace(\" · \", \"\"));\n if (costStr) parts.push(costStr);\n\n process.stdout.write(`\\n---\\n${parts.join(\" · \")}\\n`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Ask failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getSkillsDir } from \"../config/paths.js\";\nimport { search } from \"../search/index.js\";\nimport { isQmdAvailable } from \"../utils/process.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface ContextOpts {\n task?: string;\n profile?: string;\n json?: boolean;\n}\n\nexport function registerContextCommand(program: Command): void {\n program\n .command(\"context\")\n .description(\n \"Output personal context for AI agents (identity + task-relevant memories)\",\n )\n .option(\"--task <description>\", \"Current task — searches vault for relevant memories\")\n .option(\"--profile <name>\", \"Profile to use\", \"full-context\")\n .option(\"--json\", \"Output as JSON for machine consumption\")\n .action(async (opts: ContextOpts) => {\n try {\n const profileName = opts.profile ?? \"full-context\";\n const profileContent = await readProfile(profileName);\n\n if (!profileContent) {\n console.error(\n `Profile \"${profileName}\" not found. Run \"pai generate\" first.`,\n );\n process.exit(1);\n }\n\n // Extract compact identity from profile (Who I Am + Preferences sections)\n const identity = extractCompactIdentity(profileContent);\n\n // If --task given, search vault for relevant memories\n // Use \"query\" mode (hybrid: BM25 + vector + reranking) for best recall\n let memories: { file: string; title?: string; snippet: string; score?: number }[] = [];\n if (opts.task) {\n const qmdOk = await isQmdAvailable();\n if (qmdOk) {\n memories = await search(opts.task, \"vault\", 5, \"query\");\n }\n }\n\n if (opts.json) {\n const output = {\n profile: profileName,\n identity,\n memories: memories.map((m) => ({\n file: m.file,\n title: m.title ?? null,\n snippet: m.snippet,\n score: m.score ?? null,\n })),\n };\n // Write raw JSON to stdout (bypass chalk/ora)\n process.stdout.write(JSON.stringify(output, null, 2) + \"\\n\");\n } else {\n // Human-readable markdown output to stdout\n process.stdout.write(identity + \"\\n\");\n\n if (memories.length > 0) {\n process.stdout.write(\"\\n## Relevant Memories\\n\\n\");\n for (const m of memories) {\n const title = m.title ?? path.basename(m.file);\n const snippet = m.snippet.slice(0, 160).replace(/\\n/g, \" \");\n process.stdout.write(`- **${title}**: ${snippet}\\n`);\n process.stdout.write(` _source: ${m.file}_\\n\\n`);\n }\n } else if (opts.task) {\n process.stdout.write(\n \"\\n_No matching memories found for this task._\\n\",\n );\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Context failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\n/** Read a profile file from ~/.pai/skills/profiles/ */\nasync function readProfile(name: string): Promise<string | null> {\n const profilePath = path.join(getSkillsDir(), `${name}.md`);\n try {\n return await fs.readFile(profilePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/**\n * Extract compact identity from a full profile.\n * Keeps: ## Who I Am, ## Preferences, ## Current Work sections\n * Drops: ## Hard-won Lessons (those come from search results instead)\n */\nfunction extractCompactIdentity(profile: string): string {\n const lines = profile.split(\"\\n\");\n const sections: { heading: string; lines: string[] }[] = [];\n let current: { heading: string; lines: string[] } | null = null;\n\n for (const line of lines) {\n if (line.startsWith(\"## \")) {\n if (current) sections.push(current);\n current = { heading: line, lines: [] };\n } else if (line.startsWith(\"# \") && !line.startsWith(\"## \")) {\n // Keep H1 title\n if (current) sections.push(current);\n current = { heading: line, lines: [] };\n } else if (current) {\n current.lines.push(line);\n }\n }\n if (current) sections.push(current);\n\n // Keep identity-relevant sections, drop \"Hard-won Lessons\"\n const keep = sections.filter(\n (s) => !s.heading.toLowerCase().includes(\"hard-won lessons\"),\n );\n\n return keep.map((s) => [s.heading, ...s.lines].join(\"\\n\")).join(\"\\n\");\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { getSkillsDir } from \"../config/paths.js\";\nimport { loadProfile } from \"../profile/index.js\";\nimport * as console from \"../utils/console.js\";\n\nconst MANAGED_TAG = \"<!-- managed by pai distribute — do not edit manually -->\";\n\ninterface DistributeOpts {\n target?: string;\n profile?: string;\n}\n\nexport function registerDistributeCommand(program: Command): void {\n program\n .command(\"distribute\")\n .description(\n \"Deploy personal profile + agent instructions to Cursor rules and other agent configs\",\n )\n .option(\n \"--target <name>\",\n \"Target platform (cursor). Default: all\",\n )\n .option(\"--profile <name>\", \"Legacy: use a generated SKILL.md profile instead of profile.md\")\n .action(async (opts: DistributeOpts) => {\n try {\n // Priority: profile.md (fast path) → legacy SKILL.md profiles (fallback)\n let profileContent: string | null = null;\n let source = \"\";\n\n if (opts.profile) {\n // Explicit --profile flag: use legacy SKILL.md path\n profileContent = await readLegacyProfile(opts.profile);\n source = `skills/profiles/${opts.profile}.md`;\n } else {\n // Default: try profile.md first\n profileContent = await loadProfile();\n source = \"profile.md\";\n\n if (!profileContent) {\n // Fallback to legacy full-context profile\n profileContent = await readLegacyProfile(\"full-context\");\n source = \"skills/profiles/full-context.md\";\n }\n }\n\n if (!profileContent) {\n console.error(\n 'No profile found. Run \"pai init\" or \"pai profile --rebuild\" first.',\n );\n process.exit(1);\n }\n\n console.info(`Source: ${source}`);\n\n const ruleContent = buildAgentRule(profileContent);\n const targets = opts.target ? [opts.target] : [\"cursor\"];\n const results: string[] = [];\n\n for (const target of targets) {\n switch (target) {\n case \"cursor\": {\n const wrote = await distributeToCursor(ruleContent);\n if (wrote) results.push(wrote);\n break;\n }\n default:\n console.warn(`Unknown target: ${target}`);\n }\n }\n\n if (results.length > 0) {\n console.success(\n `Distributed to ${results.length} target(s):`,\n );\n for (const r of results) {\n console.log(` → ${r}`);\n }\n } else {\n console.warn(\"No targets were written.\");\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Distribute failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\n/** Read a legacy SKILL.md profile file from skills/profiles/ */\nasync function readLegacyProfile(name: string): Promise<string | null> {\n const profilePath = path.join(getSkillsDir(), `${name}.md`);\n try {\n return await fs.readFile(profilePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/**\n * Build the full agent rule file content.\n * Combines: user profile (who you are) + skill instructions (how to use pai).\n */\nfunction buildAgentRule(profileContent: string): string {\n return `${MANAGED_TAG}\n# Personal AI Context\n\n${profileContent.trim()}\n\n---\n\n## Agent Skill — pai CLI\n\nYou have access to the user's personal knowledge base via \\`pai\\` CLI commands.\nUse these to retrieve context, search knowledge, and remember new lessons.\n\n### Retrieve task-relevant memories (RECOMMENDED before starting work)\n\n\\`\\`\\`bash\npai context --task \"<brief description of current task>\"\n\\`\\`\\`\n\nThis returns the user's identity + vault memories relevant to the task.\n\n### Search specific knowledge\n\n\\`\\`\\`bash\npai search \"<query>\" # hybrid search (best quality, ~5s)\npai search \"<query>\" --fast # keyword search (instant)\npai search \"<query>\" --json # machine-readable output\n\\`\\`\\`\n\n### Remember new lessons\n\nWhen you discover something the user should remember (a lesson, preference, or tip):\n\n\\`\\`\\`bash\npai add \"<lesson or experience>\"\n\\`\\`\\`\n\n### Rebuild profile after changes\n\n\\`\\`\\`bash\npai profile --rebuild # re-scan local machine and recompile\npai distribute # redeploy updated profile\n\\`\\`\\`\n`;\n}\n\n/** Deploy to ~/.cursor/rules/pai-context.mdc */\nasync function distributeToCursor(content: string): Promise<string | null> {\n const cursorRulesDir = path.join(os.homedir(), \".cursor\", \"rules\");\n const targetPath = path.join(cursorRulesDir, \"pai-context.mdc\");\n\n try {\n await fs.mkdir(cursorRulesDir, { recursive: true });\n await fs.writeFile(targetPath, content, \"utf-8\");\n return targetPath;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Failed to write Cursor rule: ${msg}`);\n return null;\n }\n}\n","import type { Command } from \"commander\";\nimport { loadProfile, rebuildProfile } from \"../profile/index.js\";\nimport { getProfilePath } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface ProfileOpts {\n rebuild?: boolean;\n export?: boolean;\n json?: boolean;\n}\n\nexport function registerProfileCommand(program: Command): void {\n program\n .command(\"profile\")\n .description(\"View, rebuild, or export your personal profile\")\n .option(\"--rebuild\", \"Re-scan local machine and rebuild profile\")\n .option(\"--export\", \"Output profile in copy-paste friendly format\")\n .option(\"--json\", \"Output profile metadata as JSON\")\n .action(async (opts: ProfileOpts) => {\n try {\n if (opts.rebuild) {\n const { profilePath, results } = await rebuildProfile({ verbose: true });\n console.success(`Profile rebuilt → ${profilePath}`);\n console.info(`Compiled from ${results.length} data sources.`);\n return;\n }\n\n const profile = await loadProfile();\n\n if (!profile) {\n console.error(\n 'No profile found. Run \"pai init\" or \"pai profile --rebuild\" first.',\n );\n process.exit(1);\n }\n\n if (opts.json) {\n const lines = profile.split(\"\\n\");\n const sections: { heading: string; lineCount: number }[] = [];\n for (const line of lines) {\n if (line.startsWith(\"## \")) {\n sections.push({ heading: line.replace(\"## \", \"\"), lineCount: 0 });\n } else if (sections.length > 0) {\n sections[sections.length - 1].lineCount++;\n }\n }\n\n const meta = {\n path: getProfilePath(),\n totalLines: lines.length,\n sections,\n };\n\n process.stdout.write(JSON.stringify(meta, null, 2) + \"\\n\");\n return;\n }\n\n if (opts.export) {\n // Clean output for copy-paste (no extra decorations)\n process.stdout.write(profile);\n return;\n }\n\n // Default: display profile with some formatting context\n console.log(\"\");\n console.info(`Profile: ${getProfilePath()}`);\n console.log(\"\");\n process.stdout.write(profile);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Profile command failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { appendJournal, readJournal, getJournalStatus } from \"../memory/index.js\";\nimport { getTodayDate } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerLogCommand(program: Command): void {\n program\n .command(\"log [text...]\")\n .description(\"Quick-log to today's journal (memory/YYYY-MM-DD.md)\")\n .option(\"--clip\", \"Read from clipboard (macOS pbpaste)\")\n .option(\"--show\", \"Show today's journal\")\n .option(\"--date <date>\", \"Target date (YYYY-MM-DD), default today\")\n .action(async (textParts: string[], opts: { clip?: boolean; show?: boolean; date?: string }) => {\n try {\n const date = opts.date ?? getTodayDate();\n\n // Show mode: display journal\n if (opts.show) {\n const content = await readJournal(date);\n if (!content) {\n console.info(`No journal for ${date}.`);\n return;\n }\n console.log(content);\n const status = await getJournalStatus(date);\n if (status) {\n console.log(console.dim(`(${status.entries} entries${status.hasDigest ? \", has digest\" : \"\"})`));\n }\n return;\n }\n\n // Get text from args or clipboard\n let text = textParts.join(\" \").trim();\n\n if (opts.clip || !text) {\n if (opts.clip) {\n // Read from clipboard\n const { execFile } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execFileP = promisify(execFile);\n try {\n const { stdout } = await execFileP(\"pbpaste\");\n text = stdout.trim();\n } catch {\n console.error(\"Failed to read clipboard. Is pbpaste available?\");\n process.exit(1);\n }\n }\n\n if (!text) {\n console.error('Usage: pai log \"your note\" or pai log --clip');\n process.exit(1);\n }\n }\n\n const { path: journalPath, entryCount } = await appendJournal(text, date);\n console.success(`Logged to ${date} (entry #${entryCount})`);\n console.log(console.dim(` ${journalPath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Log failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { readJournal, appendDigest } from \"../memory/index.js\";\nimport { listVaultEntries } from \"../vault/index.js\";\nimport { llmCall } from \"../llm/index.js\";\nimport { digestSystemPrompt, digestUserPrompt } from \"../prompts/extract.js\";\nimport { getTodayDate } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerDigestCommand(program: Command): void {\n program\n .command(\"digest\")\n .description(\"Generate daily AI digest for a journal day\")\n .option(\"--date <date>\", \"Target date (YYYY-MM-DD), default today\")\n .option(\"--dry-run\", \"Preview digest without writing\")\n .action(async (opts: { date?: string; dryRun?: boolean }) => {\n try {\n const date = opts.date ?? getTodayDate();\n\n // Read journal\n const journal = await readJournal(date);\n if (!journal) {\n console.info(`No journal for ${date}. Use \"pai log\" to add entries first.`);\n return;\n }\n\n const spin = console.spinner(`Generating digest for ${date}...`);\n\n // Collect today's PINData entries from vault\n const allEntries = await listVaultEntries();\n const todayEntries = allEntries\n .filter((e) => e.parsed?.date === date)\n .map((e) => e.line);\n\n const pinDataText = todayEntries.length > 0\n ? todayEntries.join(\"\\n\")\n : \"\";\n\n // Generate digest via LLM\n const system = digestSystemPrompt();\n const prompt = digestUserPrompt(journal, pinDataText, date);\n const digest = await llmCall(prompt, system);\n\n if (opts.dryRun) {\n spin.succeed(`[DRY RUN] Digest for ${date}:`);\n console.log(\"\");\n console.log(digest);\n return;\n }\n\n // Append digest to journal\n const journalPath = await appendDigest(digest, date);\n spin.succeed(`Digest written for ${date}`);\n console.log(\"\");\n console.log(digest);\n console.log(\"\");\n console.log(console.dim(` ${journalPath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Digest failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { findGaps } from \"../memory/journal.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerGapsCommand(program: Command): void {\n program\n .command(\"gaps\")\n .description(\"Check for missing journal entries in the last N days\")\n .option(\"--days <n>\", \"Number of days to check\", \"7\")\n .action(async (opts: { days: string }) => {\n try {\n const days = parseInt(opts.days, 10);\n if (isNaN(days) || days < 1) {\n console.error(\"--days must be a positive number.\");\n process.exit(1);\n }\n\n const gaps = await findGaps(days);\n\n if (gaps.length === 0) {\n console.success(`No gaps in the last ${days} day(s). All journals present.`);\n return;\n }\n\n console.warn(`Found ${gaps.length} missing journal(s) in the last ${days} days:`);\n for (const date of gaps) {\n console.log(` ${date}`);\n }\n console.log(\"\");\n console.info('Use \"pai log --date YYYY-MM-DD \\'note\\'\" to backfill.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Gaps check failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { registerAuthCommand } from \"./register.auth.js\";\nimport { registerInitCommand } from \"./register.init.js\";\nimport { registerAddCommand } from \"./register.add.js\";\nimport { registerDistillCommand } from \"./register.distill.js\";\nimport { registerGenerateCommand } from \"./register.generate.js\";\nimport { registerSearchCommand } from \"./register.search.js\";\nimport { registerIndexCommand } from \"./register.index.js\";\nimport { registerImportCommand } from \"./register.import.js\";\nimport { registerStatusCommand } from \"./register.status.js\";\nimport { registerResetCommand } from \"./register.reset.js\";\nimport { registerAskCommand } from \"./register.ask.js\";\nimport { registerContextCommand } from \"./register.context.js\";\nimport { registerDistributeCommand } from \"./register.distribute.js\";\nimport { registerProfileCommand } from \"./register.profile.js\";\nimport { registerLogCommand } from \"./register.log.js\";\nimport { registerDigestCommand } from \"./register.digest.js\";\nimport { registerGapsCommand } from \"./register.gaps.js\";\n\nexport type CommandRegistration = {\n id: string;\n register: (program: Command) => void;\n};\n\nexport const commandRegistry: CommandRegistration[] = [\n { id: \"auth\", register: (p) => registerAuthCommand(p) },\n { id: \"init\", register: (p) => registerInitCommand(p) },\n { id: \"reset\", register: (p) => registerResetCommand(p) },\n { id: \"add\", register: (p) => registerAddCommand(p) },\n { id: \"log\", register: (p) => registerLogCommand(p) },\n { id: \"profile\", register: (p) => registerProfileCommand(p) },\n { id: \"distill\", register: (p) => registerDistillCommand(p) },\n { id: \"digest\", register: (p) => registerDigestCommand(p) },\n { id: \"gaps\", register: (p) => registerGapsCommand(p) },\n { id: \"generate\", register: (p) => registerGenerateCommand(p) },\n { id: \"search\", register: (p) => registerSearchCommand(p) },\n { id: \"index\", register: (p) => registerIndexCommand(p) },\n { id: \"import\", register: (p) => registerImportCommand(p) },\n { id: \"status\", register: (p) => registerStatusCommand(p) },\n { id: \"context\", register: (p) => registerContextCommand(p) },\n { id: \"ask\", register: (p) => registerAskCommand(p) },\n { id: \"distribute\", register: (p) => registerDistributeCommand(p) },\n];\n\nexport function registerAllCommands(program: Command): void {\n for (const entry of commandRegistry) {\n entry.register(program);\n }\n}\n","import { Command } from \"commander\";\nimport { registerAllCommands } from \"./command-registry.js\";\n\nexport function buildProgram(): Command {\n const program = new Command()\n .name(\"pai\")\n .description(\"Personal AI Identity Provider — local-first AI agent identity & memory system\")\n .version(PKG_VERSION);\n\n registerAllCommands(program);\n return program;\n}\n","import { buildProgram } from \"./cli/build-program.js\";\n\nconst program = buildProgram();\n\nprocess.on(\"uncaughtException\", (error) => {\n console.error(\"[pai] Uncaught exception:\", error.message);\n process.exit(1);\n});\n\nprogram.parseAsync(process.argv).catch((err: Error) => {\n console.error(\"[pai] Error:\", err.message);\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"entry.mjs","names":["URL","extractTitle","console.spinner","console.spinner","console.spinner","console.spinner","console.bold","console.spinner","console.spinner","console.bold","console.dim","console.spinner","console.spinner","console.bold","console.spinner","console.dim","console.spinner","console.dim"],"sources":["../src/config/paths.ts","../src/auth/encryption.ts","../src/utils/console.ts","../src/auth/google-oauth.ts","../src/cli/register.auth.ts","../src/config/defaults.ts","../src/config/schema.ts","../src/config/io.ts","../src/utils/process.ts","../src/utils/slug.ts","../src/utils/frontmatter.ts","../src/raw/add.ts","../src/profile/compile.ts","../src/connectors/sanitize.ts","../src/connectors/mac/collectors.ts","../src/profile/index.ts","../src/cli/register.init.ts","../src/scraper/index.ts","../src/cli/register.add.ts","../src/llm/client.ts","../src/prompts/extract.ts","../src/distill/extract.ts","../src/vault/writer.ts","../src/memory/journal.ts","../src/distill/index.ts","../src/cli/register.distill.ts","../src/prompts/generate.ts","../src/generate/index.ts","../src/cli/register.generate.ts","../src/search/index.ts","../src/cli/register.search.ts","../src/cli/register.index.ts","../src/cli/register.import.ts","../src/cli/register.status.ts","../src/cli/register.reset.ts","../src/ask/system-prompt.ts","../src/ask/tools-meta.ts","../src/ask/tools-connector.ts","../src/ask/tools-pai.ts","../src/ask/tools.ts","../src/ask/agent.ts","../src/cli/register.ask.ts","../src/cli/register.context.ts","../src/cli/register.distribute.ts","../src/cli/register.profile.ts","../src/cli/register.log.ts","../src/cli/register.digest.ts","../src/cli/register.gaps.ts","../src/cli/command-registry.ts","../src/cli/build-program.ts","../src/entry.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\n\n/** Root data directory for pai */\nexport function getPaiHome(): string {\n return process.env.PAI_HOME ?? path.join(os.homedir(), \".pai\");\n}\n\nexport function getRawDir(): string {\n return path.join(getPaiHome(), \"raw\");\n}\n\nexport function getVaultDir(): string {\n return path.join(getPaiHome(), \"vault\");\n}\n\nexport function getMemoryDir(): string {\n return path.join(getPaiHome(), \"memory\");\n}\n\nexport function getWeeklyDir(): string {\n return path.join(getMemoryDir(), \"weekly\");\n}\n\n/** Get the journal file path for a given date (YYYY-MM-DD) */\nexport function getJournalPath(date: string): string {\n return path.join(getMemoryDir(), `${date}.md`);\n}\n\n/** Get today's date as YYYY-MM-DD */\nexport function getTodayDate(): string {\n return new Date().toISOString().split(\"T\")[0]!;\n}\n\nexport function getSkillsDir(): string {\n return path.join(getPaiHome(), \"skills\", \"profiles\");\n}\n\nexport function getConfigDir(): string {\n return path.join(getPaiHome(), \"config\");\n}\n\nexport function getConfigPath(): string {\n return path.join(getConfigDir(), \"pai.json5\");\n}\n\nexport function getProfilesPath(): string {\n return path.join(getConfigDir(), \"profiles.json5\");\n}\n\nexport function getPreferencesPath(): string {\n return path.join(getConfigDir(), \"preferences.md\");\n}\n\n/** Compiled user profile — the core output of pai */\nexport function getProfilePath(): string {\n return path.join(getPaiHome(), \"profile.md\");\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getPaiHome } from \"../config/paths.js\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32;\nconst IV_LENGTH = 16;\n\nexport class Encryption {\n private keyPath: string;\n private key: Buffer | null = null;\n\n constructor() {\n this.keyPath = path.join(getPaiHome(), \".encryption-key\");\n }\n\n async generateKey(): Promise<void> {\n const key = crypto.randomBytes(KEY_LENGTH);\n await fs.mkdir(path.dirname(this.keyPath), { recursive: true });\n await fs.writeFile(this.keyPath, key.toString(\"hex\"), { mode: 0o600 });\n this.key = key;\n }\n\n async loadKey(): Promise<void> {\n try {\n const keyHex = await fs.readFile(this.keyPath, \"utf-8\");\n this.key = Buffer.from(keyHex.trim(), \"hex\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await this.generateKey();\n } else {\n throw err;\n }\n }\n }\n\n private ensureKey(): Buffer {\n if (!this.key) {\n throw new Error(\"Encryption key not loaded. Call loadKey() first.\");\n }\n return this.key;\n }\n\n encrypt(data: string): string {\n const key = this.ensureKey();\n const iv = crypto.randomBytes(IV_LENGTH);\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\n let encrypted = cipher.update(data, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n const authTag = cipher.getAuthTag();\n return iv.toString(\"hex\") + \":\" + authTag.toString(\"hex\") + \":\" + encrypted;\n }\n\n decrypt(encryptedData: string): string {\n const key = this.ensureKey();\n const parts = encryptedData.split(\":\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid encrypted data format\");\n }\n const iv = Buffer.from(parts[0], \"hex\");\n const authTag = Buffer.from(parts[1], \"hex\");\n const encrypted = parts[2];\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n let decrypted = decipher.update(encrypted, \"hex\", \"utf8\");\n decrypted += decipher.final(\"utf8\");\n return decrypted;\n }\n\n async encryptFile(filePath: string, data: unknown): Promise<void> {\n const jsonData = JSON.stringify(data, null, 2);\n const encrypted = this.encrypt(jsonData);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, encrypted, { mode: 0o600 });\n }\n\n async decryptFile(filePath: string): Promise<unknown> {\n const encrypted = await fs.readFile(filePath, \"utf-8\");\n const decrypted = this.decrypt(encrypted);\n return JSON.parse(decrypted) as unknown;\n }\n}\n\nexport const encryption = new Encryption();\n","import chalk from \"chalk\";\nimport ora, { type Ora } from \"ora\";\n\nexport function log(message: string): void {\n // eslint-disable-next-line no-console\n console.log(message);\n}\n\nexport function info(message: string): void {\n // eslint-disable-next-line no-console\n console.log(chalk.blue(\"ℹ\"), message);\n}\n\nexport function success(message: string): void {\n // eslint-disable-next-line no-console\n console.log(chalk.green(\"✓\"), message);\n}\n\nexport function warn(message: string): void {\n // eslint-disable-next-line no-console\n console.warn(chalk.yellow(\"⚠\"), message);\n}\n\nexport function error(message: string): void {\n // eslint-disable-next-line no-console\n console.error(chalk.red(\"✗\"), message);\n}\n\nexport function spinner(text: string): Ora {\n return ora({ text, color: \"cyan\" }).start();\n}\n\nexport function dim(message: string): string {\n return chalk.dim(message);\n}\n\nexport function bold(message: string): string {\n return chalk.bold(message);\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport http from \"node:http\";\nimport path from \"node:path\";\nimport { URL } from \"node:url\";\nimport { google } from \"googleapis\";\nimport open from \"open\";\nimport { getPaiHome } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\nimport { encryption } from \"./encryption.js\";\n\nconst SCOPES = [\n \"https://www.googleapis.com/auth/gmail.readonly\",\n \"https://www.googleapis.com/auth/gmail.send\",\n \"https://www.googleapis.com/auth/calendar.readonly\",\n];\n\nconst REDIRECT_URI = \"http://localhost:8888/callback\";\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Embedded OAuth client credentials for Desktop/CLI app.\n * Google explicitly states that for \"installed\" (desktop) applications,\n * the client_secret is NOT treated as a secret.\n * Users can override by placing their own client_secret.json in ~/.pai/credentials/.\n */\nconst DEFAULT_CLIENT_SECRETS = {\n installed: {\n client_id: \"973253759191-ihev26p7n9t1faksavdei36fbii46js7.apps.googleusercontent.com\",\n project_id: \"pinai-428200\",\n auth_uri: \"https://accounts.google.com/o/oauth2/auth\",\n token_uri: \"https://oauth2.googleapis.com/token\",\n auth_provider_x509_cert_url: \"https://www.googleapis.com/oauth2/v1/certs\",\n client_secret: \"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\",\n redirect_uris: [\"http://localhost\"],\n },\n} as const;\n\ntype OAuth2Client = InstanceType<typeof google.auth.OAuth2>;\n\nexport class GoogleOAuth {\n private oauth2Client: OAuth2Client | null = null;\n private credentialsPath: string;\n private clientSecretsPath: string;\n\n constructor() {\n const baseDir = path.join(getPaiHome(), \"credentials\");\n this.credentialsPath = path.join(baseDir, \"google-oauth.json.enc\");\n this.clientSecretsPath = path.join(baseDir, \"client_secret.json\");\n }\n\n async init(): Promise<void> {\n // Resolve client secrets with fallback chain:\n // 1. ~/.pai/credentials/client_secret.json (user override)\n // 2. cwd/credentials/client_secret.json (dev repo)\n // 3. Embedded default (bundled with npm package)\n let clientSecrets: {\n installed?: { client_id: string; client_secret: string };\n web?: { client_id: string; client_secret: string };\n } | null = null;\n\n // Try user-provided file first\n try {\n const content = await fs.readFile(this.clientSecretsPath, \"utf-8\");\n clientSecrets = JSON.parse(content);\n } catch {\n // Try copying from cwd (dev scenario)\n const fromCwd = path.join(process.cwd(), \"credentials\", \"client_secret.json\");\n try {\n const content = await fs.readFile(fromCwd, \"utf-8\");\n clientSecrets = JSON.parse(content);\n // Also persist to ~/.pai/credentials/ for future use\n await fs.mkdir(path.dirname(this.clientSecretsPath), { recursive: true });\n await fs.copyFile(fromCwd, this.clientSecretsPath);\n console.info(`Copied credentials from ${fromCwd} to ~/.pai/credentials/`);\n } catch {\n // Fall through to embedded default\n }\n }\n\n // Fallback to embedded default\n if (!clientSecrets) {\n clientSecrets = DEFAULT_CLIENT_SECRETS;\n }\n\n try {\n const { client_id, client_secret } =\n clientSecrets.installed ?? clientSecrets.web ?? {};\n\n if (!client_id || !client_secret) {\n throw new Error(\"Missing client_id or client_secret in client_secret.json\");\n }\n\n this.oauth2Client = new google.auth.OAuth2(\n client_id,\n client_secret,\n REDIRECT_URI,\n );\n\n await this.loadCredentials();\n } catch {\n console.warn(\"OAuth client initialization failed. Run: pai auth google\");\n }\n }\n\n private async loadCredentials(): Promise<void> {\n try {\n await encryption.loadKey();\n const credentials = await encryption.decryptFile(this.credentialsPath);\n this.oauth2Client!.setCredentials(credentials as Record<string, unknown>);\n\n this.oauth2Client!.on(\"tokens\", (tokens: unknown) => {\n if (tokens && typeof tokens === \"object\" && \"refresh_token\" in tokens && tokens.refresh_token) {\n this.saveCredentials(tokens);\n }\n });\n } catch {\n // No existing credentials\n }\n }\n\n private async saveCredentials(credentials: unknown): Promise<void> {\n await encryption.loadKey();\n await encryption.encryptFile(this.credentialsPath, credentials);\n }\n\n async authorize(): Promise<void> {\n if (!this.oauth2Client) {\n throw new Error(\n \"OAuth client not initialized. Place client_secret.json in ~/.pai/credentials/ and run again.\",\n );\n }\n\n const state = crypto.randomBytes(16).toString(\"hex\");\n const authUrl = this.oauth2Client.generateAuthUrl({\n access_type: \"offline\",\n scope: SCOPES,\n prompt: \"consent\",\n state,\n });\n\n console.log(\"\\nPlease visit this URL to authorize the application:\\n\");\n console.log(authUrl);\n console.log(\"\\nOpening browser...\\n\");\n\n await open(authUrl);\n\n const code = await this.startCallbackServer(state);\n const { tokens } = await this.oauth2Client.getToken(code);\n this.oauth2Client.setCredentials(tokens);\n await this.saveCredentials(tokens);\n\n console.success(\"Google OAuth authorization successful.\");\n }\n\n private startCallbackServer(expectedState: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = http.createServer((req, res) => {\n if (req.url?.startsWith(\"/callback\")) {\n const url = new URL(req.url ?? \"\", `http://${req.headers.host}`);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n\n if (code && state === expectedState) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>\",\n );\n server.close();\n resolve(code);\n } else {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Authorization failed: Invalid callback parameters\");\n server.close();\n reject(new Error(\"Authorization failed: invalid callback parameters\"));\n }\n }\n });\n\n server.listen(8888, () => {});\n\n server.on(\"error\", (err) => {\n reject(err);\n });\n\n const timeout = setTimeout(() => {\n server.close();\n reject(\n new Error(\n \"Authorization timed out. Please retry: pai auth google\",\n ),\n );\n }, CALLBACK_TIMEOUT_MS);\n\n server.on(\"close\", () => {\n clearTimeout(timeout);\n });\n });\n }\n\n getClient(): OAuth2Client {\n if (!this.oauth2Client) {\n throw new Error(\"OAuth client not initialized\");\n }\n return this.oauth2Client;\n }\n\n /** Returns true if user has valid credentials (access_token or refresh_token). */\n isAuthenticated(): boolean {\n if (!this.oauth2Client) return false;\n const creds = this.oauth2Client.credentials;\n return Boolean(creds.access_token || creds.refresh_token);\n }\n\n async ensureAuthenticated(): Promise<void> {\n if (!this.oauth2Client || !this.oauth2Client.credentials.access_token) {\n throw new Error(\"Not authenticated. Run: pai auth google\");\n }\n\n const now = Date.now();\n const expiry = this.oauth2Client.credentials.expiry_date;\n if (expiry != null && expiry < now) {\n await this.oauth2Client.refreshAccessToken();\n }\n }\n}\n\nexport const googleOAuth = new GoogleOAuth();\n","import type { Command } from \"commander\";\nimport { encryption } from \"../auth/encryption.js\";\nimport { googleOAuth } from \"../auth/google-oauth.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerAuthCommand(program: Command): void {\n program\n .command(\"auth\")\n .description(\"Authenticate with Google (for gmail/calendar connectors)\")\n .argument(\"<provider>\", \"Provider (google)\")\n .action(async (provider: string) => {\n if (provider !== \"google\") {\n console.error('Only \"google\" provider is supported.');\n process.exit(1);\n }\n\n try {\n await encryption.loadKey();\n await googleOAuth.init();\n await googleOAuth.authorize();\n console.success(\"Authentication successful.\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Authentication failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","/** Default content for pai.json5 */\nexport const DEFAULT_CONFIG = `{\n // pai configuration\n version: \"0.1\",\n\n qmd: {\n rawCollection: \"raw\",\n vaultCollection: \"vault\",\n },\n\n llm: {\n apiKeyEnv: \"OPENAI_API_KEY\",\n baseUrl: \"\", // leave empty for OpenAI, or set for compatible services\n cheapModel: \"gpt-4o-mini\",\n expensiveModel: \"gpt-4o\",\n },\n\n scraper: {\n timeout: 30000,\n },\n\n vault: {\n maxFileTokens: 4000,\n warnFileTokens: 3000,\n },\n}\n`;\n\n/** Default content for preferences.md — injected into ALL LLM calls */\nexport const DEFAULT_PREFERENCES = `# User Preferences\n\nThese preferences are automatically included in every AI operation (triage, distill, generate).\nEdit this file directly — changes take effect on the next pai command.\n\n## Language\n\n- My native language is: 中文 (Chinese)\n- All distilled knowledge and vault content should prefer: 中文\n- Raw data should be preserved in its original language\n- SKILL.md profiles should be written in the same language as the vault content\n\n## Output Style\n\n- Be concise and actionable — no filler\n- Use bullet points for experience entries\n- Preserve technical terms in English (e.g. API, CORS, TypeScript)\n`;\n\n/** Default content for profiles.json5 */\nexport const DEFAULT_PROFILES = `{\n profiles: {\n \"coding-assistant\": {\n scope: [\n \"vault/coding/**\",\n \"vault/preferences/**\",\n \"vault/context/**\",\n ],\n maxLines: 60,\n },\n\n \"full-context\": {\n scope: [\"vault/**\"],\n maxLines: 120,\n },\n\n \"life-assistant\": {\n scope: [\n \"vault/life/**\",\n \"vault/work/**\",\n \"vault/preferences/**\",\n \"vault/context/**\",\n ],\n maxLines: 60,\n },\n },\n}\n`;\n","import { z } from \"zod\";\n\nexport const QmdConfigSchema = z.object({\n rawCollection: z.string().default(\"raw\"),\n vaultCollection: z.string().default(\"vault\"),\n});\n\nexport const LlmConfigSchema = z.object({\n apiKeyEnv: z.string().default(\"OPENAI_API_KEY\"),\n baseUrl: z.string().optional(),\n cheapModel: z.string().default(\"gpt-4o-mini\"),\n expensiveModel: z.string().default(\"gpt-4o\"),\n});\n\nexport const ScraperConfigSchema = z.object({\n timeout: z.number().default(30_000),\n});\n\nexport const VaultConfigSchema = z.object({\n maxFileTokens: z.number().default(4000),\n warnFileTokens: z.number().default(3000),\n});\n\nexport const PaiConfigSchema = z\n .object({\n version: z.string().default(\"0.1\"),\n qmd: QmdConfigSchema.default(() => QmdConfigSchema.parse({})),\n llm: LlmConfigSchema.default(() => LlmConfigSchema.parse({})),\n scraper: ScraperConfigSchema.default(() => ScraperConfigSchema.parse({})),\n vault: VaultConfigSchema.default(() => VaultConfigSchema.parse({})),\n })\n .strict();\n\nexport type PaiConfig = z.infer<typeof PaiConfigSchema>;\n","import fs from \"node:fs/promises\";\nimport JSON5 from \"json5\";\nimport { PaiConfigSchema, type PaiConfig } from \"./schema.js\";\nimport { getConfigPath, getProfilesPath } from \"./paths.js\";\nimport type { ProfilesConfig } from \"../types.js\";\n\n/** Load and validate pai.json5 config */\nexport async function loadConfig(): Promise<PaiConfig> {\n const configPath = getConfigPath();\n try {\n const raw = await fs.readFile(configPath, \"utf-8\");\n const parsed = JSON5.parse(raw);\n return PaiConfigSchema.parse(parsed);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // Return defaults if config file doesn't exist\n return PaiConfigSchema.parse({});\n }\n throw err;\n }\n}\n\n/** Load profiles.json5 */\nexport async function loadProfiles(): Promise<ProfilesConfig> {\n const profilesPath = getProfilesPath();\n try {\n const raw = await fs.readFile(profilesPath, \"utf-8\");\n return JSON5.parse(raw) as ProfilesConfig;\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { profiles: {} };\n }\n throw err;\n }\n}\n\n/** Write config file (with JSON5 formatting) */\nexport async function saveConfig(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, \"utf-8\");\n}\n","import { execFile } from \"node:child_process\";\n\n/** Execute QMD CLI command and return stdout */\nexport async function execQmd(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\"qmd\", args, { encoding: \"utf-8\", timeout: 60_000 }, (err, stdout, stderr) => {\n if (err) {\n const msg = stderr?.trim() || err.message;\n reject(new Error(`qmd ${args[0]} failed: ${msg}`));\n } else {\n resolve(stdout);\n }\n });\n });\n}\n\n/** Check if QMD is available on PATH */\nexport async function isQmdAvailable(): Promise<boolean> {\n try {\n await execQmd([\"status\"]);\n return true;\n } catch {\n return false;\n }\n}\n","/** Generate a URL-safe slug from a title string */\nexport function generateSlug(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\") // remove special chars\n .replace(/\\s+/g, \"-\") // spaces to hyphens\n .replace(/-+/g, \"-\") // collapse multiple hyphens\n .replace(/^-|-$/g, \"\") // trim leading/trailing hyphens\n .slice(0, 50); // max 50 chars\n}\n\n/** Generate a timestamped filename: YYYY-MM-DDTHH-MM-{slug}.md */\nexport function generateRawFilename(title: string): string {\n const now = new Date();\n const ts = now\n .toISOString()\n .replace(/:\\d{2}\\.\\d{3}Z$/, \"\")\n .replace(/:/g, \"-\");\n const slug = generateSlug(title) || \"untitled\";\n return `${ts}-${slug}.md`;\n}\n","import matter from \"gray-matter\";\nimport type { RawFrontmatter } from \"../types.js\";\n\n/** Parse a markdown file with frontmatter */\nexport function parseFrontmatter<T = Record<string, unknown>>(\n content: string,\n): { data: T; content: string } {\n const result = matter(content);\n return { data: result.data as T, content: result.content };\n}\n\n/** Stringify data + content into a frontmatter markdown string */\nexport function stringifyFrontmatter(\n data: Record<string, unknown>,\n content: string,\n): string {\n return matter.stringify(content, data);\n}\n\n/** Create a raw file with proper frontmatter */\nexport function createRawFile(\n frontmatter: RawFrontmatter,\n title: string,\n body: string,\n): string {\n const content = `\\n# ${title}\\n\\n${body}\\n`;\n return stringifyFrontmatter(frontmatter as unknown as Record<string, unknown>, content);\n}\n\n/** Update frontmatter fields in a raw file */\nexport function updateRawFrontmatter(\n fileContent: string,\n updates: Partial<RawFrontmatter>,\n): string {\n const { data, content } = parseFrontmatter<RawFrontmatter>(fileContent);\n const updated = { ...data, ...updates };\n return stringifyFrontmatter(updated as unknown as Record<string, unknown>, content);\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getRawDir } from \"../config/paths.js\";\nimport { generateRawFilename } from \"../utils/slug.js\";\nimport { createRawFile, parseFrontmatter } from \"../utils/frontmatter.js\";\nimport type { CollectorResult, RawFrontmatter } from \"../types.js\";\n\n/** Add plain text content to raw/local/ */\nexport async function addText(\n content: string,\n source: string = \"local\",\n): Promise<string> {\n const title = extractTitle(content);\n const filename = generateRawFilename(title);\n const dir = path.join(getRawDir(), \"local\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return filePath;\n}\n\n/** Add a local file's content to raw/local/ */\nexport async function addFile(\n filePath: string,\n source: string = \"local\",\n): Promise<string> {\n const content = await fs.readFile(filePath, \"utf-8\");\n const basename = path.basename(filePath, path.extname(filePath));\n const filename = generateRawFilename(basename);\n const dir = path.join(getRawDir(), \"local\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, basename, content);\n const outPath = path.join(dir, filename);\n await fs.writeFile(outPath, fileContent, \"utf-8\");\n return outPath;\n}\n\n/** Add URL-scraped content to raw/web/ */\nexport async function addUrl(\n url: string,\n title: string,\n markdown: string,\n): Promise<string> {\n const filename = generateRawFilename(title);\n const dir = path.join(getRawDir(), \"web\");\n await fs.mkdir(dir, { recursive: true });\n\n const fm: RawFrontmatter = {\n source: \"web\",\n timestamp: new Date().toISOString(),\n url,\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, markdown);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return filePath;\n}\n\n/**\n * Add or update a connector-scanned entry to raw/connector/{name}/.\n * Uses scan_id in frontmatter for dedup:\n * - If no existing file with same scan_id → create new\n * - If existing file with same content body → skip (\"skipped\")\n * - If existing file with different content → overwrite (\"updated\")\n * Returns \"created\" | \"updated\" | \"skipped\".\n */\nexport async function addConnectorEntry(\n connectorName: string,\n entry: CollectorResult,\n): Promise<\"created\" | \"updated\" | \"skipped\"> {\n const dir = path.join(getRawDir(), \"connector\", connectorName);\n await fs.mkdir(dir, { recursive: true });\n\n const scanId = `${connectorName}/${entry.id}`;\n\n // Check for existing file with same scan_id\n const existingPath = await findByScanId(dir, scanId);\n\n const fm: RawFrontmatter = {\n source: `connector/${connectorName}`,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n scan_id: scanId,\n };\n\n if (existingPath) {\n // Compare content body\n const oldRaw = await fs.readFile(existingPath, \"utf-8\");\n const { content: oldBody } = parseFrontmatter(oldRaw);\n const newBody = `\\n# ${entry.title}\\n\\n${entry.content}\\n`;\n\n if (oldBody.trim() === newBody.trim()) {\n return \"skipped\";\n }\n\n // Overwrite with updated content (preserve filename)\n const fileContent = createRawFile(fm, entry.title, entry.content);\n await fs.writeFile(existingPath, fileContent, \"utf-8\");\n return \"updated\";\n }\n\n // Create new entry\n const filename = generateRawFilename(entry.id);\n const fileContent = createRawFile(fm, entry.title, entry.content);\n const filePath = path.join(dir, filename);\n await fs.writeFile(filePath, fileContent, \"utf-8\");\n return \"created\";\n}\n\n/** Find a raw file in a directory whose frontmatter scan_id matches */\nasync function findByScanId(dir: string, scanId: string): Promise<string | null> {\n try {\n const entries = await fs.readdir(dir);\n for (const name of entries) {\n if (!name.endsWith(\".md\")) continue;\n const filePath = path.join(dir, name);\n const content = await fs.readFile(filePath, \"utf-8\");\n if (content.includes(`scan_id: ${scanId}`) || content.includes(`scan_id: '${scanId}'`)) {\n return filePath;\n }\n }\n } catch {\n // Directory doesn't exist or read error\n }\n return null;\n}\n\n/** List all pending raw files (status: pending) */\nexport async function listPending(): Promise<string[]> {\n const rawDir = getRawDir();\n const pending: string[] = [];\n\n const subdirs = [\"local\", \"web\", \"connector\"];\n for (const sub of subdirs) {\n const dir = path.join(rawDir, sub);\n try {\n const entries = await walkDir(dir);\n for (const entry of entries) {\n if (entry.endsWith(\".md\")) {\n const content = await fs.readFile(entry, \"utf-8\");\n if (content.includes(\"status: pending\")) {\n pending.push(entry);\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n return pending;\n}\n\n/** List all raw files regardless of status */\nexport async function listAll(): Promise<string[]> {\n const rawDir = getRawDir();\n const files: string[] = [];\n\n const subdirs = [\"local\", \"web\", \"connector\"];\n for (const sub of subdirs) {\n const dir = path.join(rawDir, sub);\n try {\n const entries = await walkDir(dir);\n files.push(...entries.filter((e) => e.endsWith(\".md\")));\n } catch {\n // Directory might not exist\n }\n }\n return files;\n}\n\n/** Recursively walk a directory and return file paths */\nasync function walkDir(dir: string): Promise<string[]> {\n const results: string[] = [];\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await walkDir(fullPath)));\n } else {\n results.push(fullPath);\n }\n }\n } catch {\n // Ignore errors (dir doesn't exist, etc.)\n }\n return results;\n}\n\n/** Extract a title from content (first line or first N words) */\nfunction extractTitle(content: string): string {\n const firstLine = content.split(\"\\n\")[0]?.trim() ?? \"\";\n // Remove markdown heading syntax\n const cleaned = firstLine.replace(/^#+\\s*/, \"\");\n if (cleaned.length > 0 && cleaned.length <= 100) {\n return cleaned;\n }\n // Fall back to first 8 words\n return content.split(/\\s+/).slice(0, 8).join(\" \").slice(0, 100) || \"untitled\";\n}\n","/**\n * Profile compiler — transforms raw CollectorResult[] into a structured profile.md.\n * Zero LLM dependency: pure template + data formatting.\n */\n\nimport type { CollectorResult } from \"../types.js\";\n\n/** Mapping from collector IDs to profile sections */\ninterface SectionDef {\n heading: string;\n /** Collector IDs that feed this section (in order) */\n collectors: string[];\n}\n\nconst PROFILE_SECTIONS: SectionDef[] = [\n {\n heading: \"Identity\",\n collectors: [\"identity-profile\", \"github-profile\"],\n },\n {\n heading: \"Environment & Tools\",\n collectors: [\"dev-environment\", \"dev-preferences\"],\n },\n {\n heading: \"Work Style & Habits\",\n collectors: [\"shell-habits\", \"coding-rules\"],\n },\n {\n heading: \"Active Projects & Recent Focus\",\n collectors: [\"active-projects\", \"recent-focus\"],\n },\n {\n heading: \"Digital Footprint\",\n collectors: [\"browser-bookmarks\", \"browser-domains\", \"productivity-setup\"],\n },\n {\n heading: \"Registry & Cloud Accounts\",\n collectors: [\"social-profiles\"],\n },\n {\n heading: \"Context\",\n collectors: [\"calendar-context\", \"file-organization\"],\n },\n];\n\n/**\n * Compile CollectorResult[] into a structured profile markdown string.\n * No LLM involved — pure formatting.\n */\nexport function compileProfile(results: CollectorResult[]): string {\n const index = new Map<string, CollectorResult>();\n for (const r of results) {\n index.set(r.id, r);\n }\n\n const sections: string[] = [\"# Personal Profile\", \"\"];\n\n for (const section of PROFILE_SECTIONS) {\n const parts: string[] = [];\n\n for (const cid of section.collectors) {\n const result = index.get(cid);\n if (result?.content?.trim()) {\n parts.push(result.content.trim());\n }\n }\n\n if (parts.length === 0) continue;\n\n sections.push(`## ${section.heading}`, \"\");\n sections.push(parts.join(\"\\n\\n\"));\n sections.push(\"\");\n }\n\n // Append any collectors not covered by the predefined sections\n const coveredIds = new Set(PROFILE_SECTIONS.flatMap((s) => s.collectors));\n const extras: string[] = [];\n\n for (const r of results) {\n if (!coveredIds.has(r.id) && r.content?.trim()) {\n extras.push(`## ${r.title}`, \"\", r.content.trim(), \"\");\n }\n }\n\n if (extras.length > 0) {\n sections.push(...extras);\n }\n\n // Footer\n sections.push(\"---\");\n sections.push(`Last updated: ${new Date().toISOString()}`);\n sections.push(\"\");\n\n return sections.join(\"\\n\");\n}\n\n/**\n * Generate a compact profile summary suitable for agent injection.\n * Strips verbose sub-sections and keeps high-signal data only.\n */\nexport function compileCompactProfile(results: CollectorResult[]): string {\n const index = new Map<string, CollectorResult>();\n for (const r of results) {\n index.set(r.id, r);\n }\n\n const parts: string[] = [\"# Personal Profile (Compact)\", \"\"];\n\n // Identity — always include in full\n const identity = index.get(\"identity-profile\");\n if (identity?.content?.trim()) {\n parts.push(identity.content.trim(), \"\");\n }\n\n // Dev environment — keep runtime versions and package managers\n const devEnv = index.get(\"dev-environment\");\n if (devEnv?.content?.trim()) {\n parts.push(devEnv.content.trim(), \"\");\n }\n\n // Active projects — keep repo list\n const projects = index.get(\"active-projects\");\n if (projects?.content?.trim()) {\n parts.push(projects.content.trim(), \"\");\n }\n\n // Shell habits — keep top commands only\n const habits = index.get(\"shell-habits\");\n if (habits?.content?.trim()) {\n // Take only the first 15 lines (header + top commands)\n const lines = habits.content.trim().split(\"\\n\");\n parts.push(lines.slice(0, 17).join(\"\\n\"), \"\");\n }\n\n parts.push(\"---\");\n parts.push(`Last updated: ${new Date().toISOString()}`);\n parts.push(\"\");\n\n return parts.join(\"\\n\");\n}\n","/**\n * Security sanitization utilities for connector-scanned content.\n * Strips secrets, credentials, and sensitive paths before writing to raw layer.\n */\n\n/** Pattern matching shell export lines containing secrets */\nconst SECRET_EXPORT_RE =\n /^(\\s*export\\s+)\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|PASSWD)\\w*\\s*=.*/i;\n\n/** Pattern matching lines that look like inline secret assignments (no export) */\nconst SECRET_ASSIGN_RE =\n /^\\s*\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|PASSWD)\\w*\\s*=\\s*[\"']?\\S+/i;\n\n/**\n * Strip secret export/assignment lines from shell config content.\n * Preserves comments and non-secret lines intact.\n */\nexport function stripSecretExports(content: string): string {\n return content\n .split(\"\\n\")\n .map((line) => {\n if (SECRET_EXPORT_RE.test(line) || SECRET_ASSIGN_RE.test(line)) {\n return \"# [REDACTED by pai — secret removed]\";\n }\n return line;\n })\n .join(\"\\n\");\n}\n\n/**\n * Strip [credential] sections from git config content.\n * Removes the section header and all lines until the next section.\n */\nexport function stripGitCredentials(content: string): string {\n const lines = content.split(\"\\n\");\n const result: string[] = [];\n let inCredentialSection = false;\n\n for (const line of lines) {\n // Detect section headers like [credential] or [credential \"...\"]\n if (/^\\s*\\[credential/i.test(line)) {\n inCredentialSection = true;\n result.push(\"# [REDACTED by pai — credential section removed]\");\n continue;\n }\n // New section starts → exit credential section\n if (inCredentialSection && /^\\s*\\[/.test(line)) {\n inCredentialSection = false;\n }\n if (!inCredentialSection) {\n result.push(line);\n }\n }\n\n return result.join(\"\\n\");\n}\n\n/**\n * Strip IdentityFile lines from SSH config.\n * Keeps Host, HostName, User, Port — removes key paths.\n */\nexport function stripSshIdentityFiles(content: string): string {\n return content\n .split(\"\\n\")\n .map((line) => {\n if (/^\\s*IdentityFile\\s/i.test(line)) {\n return \" # [REDACTED by pai — IdentityFile removed]\";\n }\n return line;\n })\n .join(\"\\n\");\n}\n","/**\n * Mac system context collectors.\n * Each collector gathers a specific category of personalized context\n * from the local macOS environment and returns a CollectorResult.\n */\n\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { CollectorResult } from \"../../types.js\";\nimport {\n stripSecretExports,\n stripGitCredentials,\n stripSshIdentityFiles,\n} from \"../sanitize.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Run a shell command and return stdout (empty string on failure) */\nasync function exec(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve) => {\n execFile(cmd, args, { encoding: \"utf-8\", timeout: 15_000 }, (err, stdout) => {\n resolve(err ? \"\" : stdout.trim());\n });\n });\n}\n\n/** Read a file, return empty string on failure */\nasync function readSafe(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\n/** Check if path exists */\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run osascript and return output */\nasync function osascript(script: string): Promise<string> {\n return exec(\"osascript\", [\"-e\", script]);\n}\n\n/** Run defaults read and return output */\nasync function defaultsRead(...args: string[]): Promise<string> {\n return exec(\"defaults\", [\"read\", ...args]);\n}\n\nconst HOME = os.homedir();\n\n// ---------------------------------------------------------------------------\n// 1. Identity Profile\n// ---------------------------------------------------------------------------\n\nexport async function collectIdentityProfile(): Promise<CollectorResult> {\n const lines: string[] = [\"## User Identity\"];\n\n // macOS username\n const username = await exec(\"id\", [\"-un\"]);\n if (username) lines.push(`- macOS Username: ${username}`);\n\n // Real name from directory service\n const realName = await exec(\"dscl\", [\".\", \"-read\", `/Users/${username}`, \"RealName\"]);\n const nameMatch = realName.split(\"\\n\").find((l) => l.trim() && !l.includes(\"RealName\"));\n if (nameMatch?.trim()) lines.push(`- Real Name (local): ${nameMatch.trim()}`);\n\n // Apple ID / iCloud\n const mobileMe = await defaultsRead(\"MobileMeAccounts\");\n const appleIdMatch = mobileMe.match(/AccountID\\s*=\\s*\"?([^\";\\n]+)/);\n const displayNameMatch = mobileMe.match(/DisplayName\\s*=\\s*\"?([^\";\\n]+)/);\n if (displayNameMatch) lines.push(`- Apple ID Display Name: ${displayNameMatch[1].trim()}`);\n if (appleIdMatch) lines.push(`- Apple ID: ${appleIdMatch[1].trim()}`);\n\n // Computer name\n const computerName = await exec(\"scutil\", [\"--get\", \"ComputerName\"]);\n if (computerName) lines.push(`- Computer Name: ${computerName}`);\n\n // Git identity\n const gitName = await exec(\"git\", [\"config\", \"--global\", \"user.name\"]);\n const gitEmail = await exec(\"git\", [\"config\", \"--global\", \"user.email\"]);\n if (gitName) lines.push(`- Git Name: ${gitName}`);\n if (gitEmail) lines.push(`- Git Email: ${gitEmail}`);\n\n // Locale & Language\n lines.push(\"\", \"## Locale & Language\");\n const langs = await defaultsRead(\"NSGlobalDomain\", \"AppleLanguages\");\n const langList = langs.match(/\"([^\"]+)\"/g)?.map((s) => s.replace(/\"/g, \"\")) ?? [];\n if (langList.length > 0) lines.push(`- Languages: ${langList.join(\", \")}`);\n\n const locale = await defaultsRead(\"NSGlobalDomain\", \"AppleLocale\");\n if (locale) lines.push(`- Locale: ${locale}`);\n\n // Input methods\n const inputSources = await defaultsRead(\"com.apple.HIToolbox\", \"AppleSelectedInputSources\");\n const inputMethods = inputSources.match(/\"Input Mode\"\\s*=\\s*\"([^\"]+)\"/g) ?? [];\n const bundleIds = inputSources.match(/\"Bundle ID\"\\s*=\\s*\"([^\"]+)\"/g) ?? [];\n const inputs = [...inputMethods, ...bundleIds]\n .map((s) => s.replace(/.*\"([^\"]+)\"$/, \"$1\"))\n .filter((s) => !s.includes(\"PressAndHold\"));\n if (inputs.length > 0) lines.push(`- Input Methods: ${inputs.join(\", \")}`);\n\n // System appearance\n const appearance = await defaultsRead(\"-g\", \"AppleInterfaceStyle\");\n lines.push(`- System Appearance: ${appearance || \"Light\"}`);\n\n // Timezone\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n lines.push(`- Timezone: ${tz}`);\n\n return {\n id: \"identity-profile\",\n title: \"Mac User Identity Profile\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 2. Calendar Context\n// ---------------------------------------------------------------------------\n\nexport async function collectCalendarContext(): Promise<CollectorResult> {\n let calNames: string[] = [];\n const raw = await osascript(\n 'tell application \"Calendar\" to get name of every calendar',\n );\n if (raw) {\n calNames = raw.split(\", \").map((s) => s.trim()).filter(Boolean);\n }\n\n const lines = [\n \"## Calendar Subscriptions\",\n \"\",\n `Total calendars: ${calNames.length}`,\n \"\",\n ...calNames.map((name) => `- ${name}`),\n ];\n\n return {\n id: \"calendar-context\",\n title: \"Calendar Context\",\n content: calNames.length > 0\n ? lines.join(\"\\n\")\n : \"No calendar data accessible (Calendar app may not be running).\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 3. File Organization\n// ---------------------------------------------------------------------------\n\nexport async function collectFileOrganization(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Documents top-level folders\n const docsDir = path.join(HOME, \"Documents\");\n try {\n const entries = await fs.readdir(docsDir, { withFileTypes: true });\n const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n lines.push(\"## Documents Folders\", \"\", ...dirs.map((d) => `- ${d}`));\n } catch {\n lines.push(\"## Documents Folders\", \"\", \"(not accessible)\");\n }\n\n // Desktop items\n const desktopDir = path.join(HOME, \"Desktop\");\n try {\n const entries = await fs.readdir(desktopDir);\n lines.push(\"\", \"## Desktop Items\", \"\", ...entries.slice(0, 20).map((e) => `- ${e}`));\n if (entries.length > 20) lines.push(`- ... and ${entries.length - 20} more`);\n } catch {\n lines.push(\"\", \"## Desktop Items\", \"\", \"(not accessible)\");\n }\n\n // Recent downloads (file names only)\n const dlDir = path.join(HOME, \"Downloads\");\n try {\n const entries = await fs.readdir(dlDir);\n // Sort by name only (stat would be slow), take first 15\n lines.push(\n \"\",\n \"## Recent Downloads (latest 15 by name)\",\n \"\",\n ...entries.slice(0, 15).map((e) => `- ${e}`),\n );\n } catch {\n lines.push(\"\", \"## Recent Downloads\", \"\", \"(not accessible)\");\n }\n\n return {\n id: \"file-organization\",\n title: \"File Organization Structure\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 4. Dev Environment\n// ---------------------------------------------------------------------------\n\nexport async function collectDevEnvironment(): Promise<CollectorResult> {\n const lines: string[] = [\"## Runtime Versions\"];\n\n // Language runtimes\n const runtimes: [string, string, string[]][] = [\n [\"Node.js\", \"node\", [\"--version\"]],\n [\"Python\", \"python3\", [\"--version\"]],\n [\"Go\", \"go\", [\"version\"]],\n [\"Rust\", \"rustc\", [\"--version\"]],\n [\"Java\", \"java\", [\"--version\"]],\n ];\n\n for (const [name, cmd, args] of runtimes) {\n const ver = await exec(cmd, args);\n if (ver) {\n // Extract just the version string\n const first = ver.split(\"\\n\")[0] ?? ver;\n lines.push(`- ${name}: ${first}`);\n }\n }\n\n // Package managers\n lines.push(\"\", \"## Package Managers\");\n const pkgMgrs = [\"brew\", \"pnpm\", \"npm\", \"pip3\", \"cargo\"];\n for (const pm of pkgMgrs) {\n const which = await exec(\"which\", [pm]);\n if (which) lines.push(`- ${pm}: ${which}`);\n }\n\n // Docker\n const docker = await exec(\"docker\", [\"--version\"]);\n if (docker) lines.push(\"\", \"## Docker\", \"\", `- ${docker}`);\n\n // Shell info\n lines.push(\"\", \"## Shell\");\n const shell = process.env.SHELL ?? \"\";\n lines.push(`- Shell: ${shell}`);\n const zshrc = await readSafe(path.join(HOME, \".zshrc\"));\n const themeMatch = zshrc.match(/^ZSH_THEME=\"?([^\"\\n]+)/m);\n const pluginsMatch = zshrc.match(/^plugins=\\(([^)]+)\\)/m);\n if (themeMatch) lines.push(`- Oh-My-Zsh Theme: ${themeMatch[1]}`);\n if (pluginsMatch) lines.push(`- Oh-My-Zsh Plugins: ${pluginsMatch[1].trim()}`);\n\n // Global npm packages\n const npmGlobal = await exec(\"npm\", [\"list\", \"-g\", \"--depth=0\"]);\n if (npmGlobal) {\n const pkgs = npmGlobal\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"├\") || l.startsWith(\"└\"))\n .map((l) => l.replace(/^[├└─┬│\\s]+/, \"\").trim())\n .filter(Boolean);\n if (pkgs.length > 0) {\n lines.push(\"\", \"## Global npm Packages\", \"\", ...pkgs.map((p) => `- ${p}`));\n }\n }\n\n // Homebrew casks (installed apps via brew)\n const casks = await exec(\"brew\", [\"list\", \"--cask\"]);\n if (casks) {\n const caskList = casks.split(\"\\n\").filter(Boolean);\n lines.push(\"\", \"## Homebrew Casks\", \"\", ...caskList.map((c) => `- ${c}`));\n }\n\n return {\n id: \"dev-environment\",\n title: \"Development Environment\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 5. Dev Preferences\n// ---------------------------------------------------------------------------\n\nexport async function collectDevPreferences(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Shell aliases (sanitized)\n const zshrc = await readSafe(path.join(HOME, \".zshrc\"));\n if (zshrc) {\n const sanitized = stripSecretExports(zshrc);\n const aliases = sanitized\n .split(\"\\n\")\n .filter((l) => l.match(/^\\s*alias\\s/));\n if (aliases.length > 0) {\n lines.push(\"## Shell Aliases\", \"\", ...aliases);\n }\n\n // PATH additions\n const pathLines = sanitized\n .split(\"\\n\")\n .filter((l) => l.match(/^\\s*export\\s+PATH/) && !l.includes(\"REDACTED\"));\n if (pathLines.length > 0) {\n lines.push(\"\", \"## PATH Additions\", \"\", ...pathLines);\n }\n }\n\n // Git config (sanitized)\n const gitconfigPath = path.join(HOME, \".gitconfig\");\n const gitconfig = await readSafe(gitconfigPath);\n if (gitconfig) {\n const sanitized = stripGitCredentials(gitconfig);\n lines.push(\"\", \"## Git Config\", \"\", \"```\", sanitized.trim(), \"```\");\n }\n\n // Git aliases\n const gitAliases = await exec(\"git\", [\"config\", \"--global\", \"--get-regexp\", \"alias\"]);\n if (gitAliases) {\n lines.push(\"\", \"## Git Aliases\", \"\", ...gitAliases.split(\"\\n\").map((l) => `- ${l}`));\n }\n\n // Default branch\n const defaultBranch = await exec(\"git\", [\"config\", \"--global\", \"init.defaultBranch\"]);\n if (defaultBranch) lines.push(\"\", `## Default Git Branch: ${defaultBranch}`);\n\n // Cursor extensions\n const extDir = path.join(HOME, \".cursor\", \"extensions\");\n try {\n const entries = await fs.readdir(extDir);\n const exts = entries.filter((e) => !e.startsWith(\".\") && e !== \"extensions.json\");\n if (exts.length > 0) {\n lines.push(\"\", \"## Cursor Extensions\", \"\", ...exts.map((e) => `- ${e}`));\n }\n } catch {\n // No Cursor extensions dir\n }\n\n return {\n id: \"dev-preferences\",\n title: \"Development Preferences\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 6. Shell Habits\n// ---------------------------------------------------------------------------\n\nexport async function collectShellHabits(): Promise<CollectorResult> {\n const histPath = path.join(HOME, \".zsh_history\");\n const raw = await readSafe(histPath);\n\n if (!raw) {\n return {\n id: \"shell-habits\",\n title: \"Shell Command Habits\",\n content: \"No zsh history found.\",\n };\n }\n\n const rawLines = raw.split(\"\\n\");\n const totalEntries = rawLines.length;\n\n // Parse commands: zsh history format is `: timestamp:0;command`\n const freq = new Map<string, number>();\n for (const line of rawLines) {\n // Strip zsh extended history prefix\n const cmd = line.replace(/^:\\s*\\d+:\\d+;/, \"\").trim();\n const firstWord = cmd.split(/\\s+/)[0];\n if (firstWord && firstWord.length > 0 && firstWord.length < 50) {\n freq.set(firstWord, (freq.get(firstWord) ?? 0) + 1);\n }\n }\n\n const top30 = [...freq.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 30);\n\n const lines = [\n `Total history entries: ${totalEntries}`,\n \"\",\n \"## Most Used Commands (Top 30)\",\n \"\",\n ...top30.map(([cmd, count]) => `- ${cmd}: ${count} times`),\n ];\n\n return {\n id: \"shell-habits\",\n title: \"Shell Command Habits\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 7. Coding Rules\n// ---------------------------------------------------------------------------\n\nexport async function collectCodingRules(): Promise<CollectorResult> {\n const sections: string[] = [];\n\n // Global CLAUDE.md files\n const claudePaths = [\n path.join(HOME, \"CLAUDE.md\"),\n path.join(HOME, \".claude\", \"CLAUDE.md\"),\n ];\n\n for (const p of claudePaths) {\n const content = await readSafe(p);\n if (content) {\n const relPath = p.replace(HOME, \"~\");\n sections.push(`## ${relPath}`, \"\", content.trim(), \"\");\n }\n }\n\n // Claude custom commands\n const cmdDir = path.join(HOME, \".claude\", \"commands\");\n try {\n const entries = await fs.readdir(cmdDir);\n const mdFiles = entries.filter((e) => e.endsWith(\".md\"));\n if (mdFiles.length > 0) {\n sections.push(\"## Claude Custom Commands\", \"\");\n for (const f of mdFiles) {\n const content = await readSafe(path.join(cmdDir, f));\n if (content) {\n sections.push(`### ${f}`, \"\", content.trim(), \"\");\n }\n }\n }\n } catch {\n // No commands dir\n }\n\n // Scan active project repos for CLAUDE.md / AGENTS.md / .cursorrules\n const activeRepos = await findRecentGitRepos(30);\n const projectRules: string[] = [];\n\n for (const repo of activeRepos.slice(0, 10)) {\n // Only scan top 10 most recent\n for (const ruleFile of [\"CLAUDE.md\", \"AGENTS.md\", \".cursorrules\"]) {\n const rulePath = path.join(repo, ruleFile);\n const content = await readSafe(rulePath);\n if (content && content.length > 10) {\n const repoName = path.basename(repo);\n projectRules.push(\n `### ${repoName}/${ruleFile}`,\n \"\",\n content.trim().slice(0, 2000), // Cap at 2000 chars per file\n \"\",\n );\n }\n }\n }\n\n if (projectRules.length > 0) {\n sections.push(\"## Project-Level Rules\", \"\", ...projectRules);\n }\n\n // Cursor rules directory\n const cursorRulesDir = path.join(HOME, \".cursor\", \"rules\");\n try {\n const entries = await fs.readdir(cursorRulesDir);\n const mdFiles = entries.filter((e) => e.endsWith(\".md\") || e.endsWith(\".mdc\"));\n if (mdFiles.length > 0) {\n sections.push(\"## Global Cursor Rules\", \"\");\n for (const f of mdFiles) {\n const content = await readSafe(path.join(cursorRulesDir, f));\n if (content) {\n sections.push(`### ${f}`, \"\", content.trim().slice(0, 1000), \"\");\n }\n }\n }\n } catch {\n // No cursor rules dir\n }\n\n return {\n id: \"coding-rules\",\n title: \"Coding Rules and AI Agent Config\",\n content: sections.length > 0\n ? sections.join(\"\\n\")\n : \"No coding rules found.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 8. Active Projects\n// ---------------------------------------------------------------------------\n\nexport async function collectActiveProjects(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Recent git repos\n const repos = await findRecentGitRepos(30);\n if (repos.length > 0) {\n lines.push(\"## Active Git Repositories (last 30 days)\", \"\");\n for (const repo of repos) {\n const name = repo.replace(HOME, \"~\");\n lines.push(`- ${name}`);\n }\n }\n\n // SSH config hosts\n const sshConfig = await readSafe(path.join(HOME, \".ssh\", \"config\"));\n if (sshConfig) {\n const sanitized = stripSshIdentityFiles(sshConfig);\n const hosts = sanitized\n .split(\"\\n\")\n .filter((l) => /^\\s*Host\\s/i.test(l) && !l.includes(\"*\"))\n .map((l) => l.replace(/^\\s*Host\\s+/i, \"\").trim());\n if (hosts.length > 0) {\n lines.push(\"\", \"## SSH Hosts\", \"\", ...hosts.map((h) => `- ${h}`));\n }\n }\n\n // Cloud storage\n const cloudDir = path.join(HOME, \"Library\", \"CloudStorage\");\n try {\n const entries = await fs.readdir(cloudDir);\n if (entries.length > 0) {\n lines.push(\"\", \"## Cloud Storage\", \"\", ...entries.map((e) => `- ${e}`));\n }\n } catch {\n // No cloud storage\n }\n\n return {\n id: \"active-projects\",\n title: \"Active Projects and Infrastructure\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 9. Productivity Setup\n// ---------------------------------------------------------------------------\n\nexport async function collectProductivitySetup(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Installed applications\n try {\n const entries = await fs.readdir(\"/Applications\");\n const apps = entries\n .filter((e) => e.endsWith(\".app\"))\n .map((e) => e.replace(\".app\", \"\"));\n lines.push(\"## Installed Applications\", \"\", ...apps.map((a) => `- ${a}`));\n } catch {\n lines.push(\"## Installed Applications\", \"\", \"(not accessible)\");\n }\n\n // Dock persistent apps\n const dockApps = await defaultsRead(\"com.apple.dock\", \"persistent-apps\");\n if (dockApps) {\n const labels = [...dockApps.matchAll(/\"file-label\"\\s*=\\s*\"?([^\";\\n}]+)/g)]\n .map((m) => m[1].trim())\n .filter(Boolean);\n if (labels.length > 0) {\n lines.push(\"\", \"## Dock Apps (pinned)\", \"\", ...labels.map((a) => `- ${a}`));\n }\n }\n\n // Default browser\n const handlers = await defaultsRead(\n \"com.apple.LaunchServices/com.apple.launchservices.secure\",\n \"LSHandlers\",\n );\n const browserMatch = handlers.match(/LSHandlerRoleAll\\s*=\\s*\"([^\"]+)\"/);\n if (browserMatch) {\n const bundleId = browserMatch[1];\n const browserName = bundleId.includes(\"edge\")\n ? \"Microsoft Edge\"\n : bundleId.includes(\"chrome\")\n ? \"Google Chrome\"\n : bundleId.includes(\"firefox\")\n ? \"Firefox\"\n : bundleId.includes(\"safari\")\n ? \"Safari\"\n : bundleId;\n lines.push(\"\", `## Default Browser: ${browserName}`);\n }\n\n // Screen resolution\n const displayInfo = await exec(\"system_profiler\", [\"SPDisplaysDataType\"]);\n const resolutions = [...displayInfo.matchAll(/Resolution:\\s*(.+)/g)].map((m) => m[1].trim());\n if (resolutions.length > 0) {\n lines.push(\"\", \"## Screen Resolution\", \"\", ...resolutions.map((r) => `- ${r}`));\n }\n\n return {\n id: \"productivity-setup\",\n title: \"Productivity Setup\",\n content: lines.join(\"\\n\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// 10. Browser Bookmarks\n// ---------------------------------------------------------------------------\n\nexport async function collectBrowserBookmarks(): Promise<CollectorResult> {\n const bmPath = path.join(\n HOME,\n \"Library\",\n \"Application Support\",\n \"Microsoft Edge\",\n \"Default\",\n \"Bookmarks\",\n );\n\n const raw = await readSafe(bmPath);\n if (!raw) {\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: \"No Edge bookmarks found.\",\n };\n }\n\n try {\n const data = JSON.parse(raw);\n const folders: { name: string; count: number; depth: number }[] = [];\n let totalUrls = 0;\n\n function walk(\n node: Record<string, unknown>,\n depth: number,\n ): void {\n if (node.type === \"folder\") {\n const children = (node.children ?? []) as Record<string, unknown>[];\n const name = (node.name as string) ?? \"\";\n if (children.length > 0) {\n folders.push({ name, count: children.length, depth });\n }\n for (const child of children) {\n walk(child, depth + 1);\n }\n } else if (node.type === \"url\") {\n totalUrls++;\n }\n }\n\n const roots = data.roots as Record<string, unknown>;\n for (const root of Object.values(roots)) {\n if (root && typeof root === \"object\" && \"children\" in (root as Record<string, unknown>)) {\n walk(root as Record<string, unknown>, 0);\n }\n }\n\n // Sort by count descending\n folders.sort((a, b) => b.count - a.count);\n\n const lines = [\n `Total bookmarks: ${totalUrls}`,\n `Total folders: ${folders.length}`,\n \"\",\n \"## Top Bookmark Folders (by item count)\",\n \"\",\n ...folders.slice(0, 40).map((f) => `- ${\" \".repeat(Math.min(f.depth, 2))}${f.name}: ${f.count} items`),\n ];\n\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: lines.join(\"\\n\"),\n };\n } catch {\n return {\n id: \"browser-bookmarks\",\n title: \"Browser Bookmark Structure\",\n content: \"Failed to parse Edge bookmarks.\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// 11. Browser Domains\n// ---------------------------------------------------------------------------\n\nexport async function collectBrowserDomains(): Promise<CollectorResult> {\n const histPath = path.join(\n HOME,\n \"Library\",\n \"Application Support\",\n \"Microsoft Edge\",\n \"Default\",\n \"History\",\n );\n\n if (!(await exists(histPath))) {\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: \"No Edge history found.\",\n };\n }\n\n // Copy the locked DB to a temp file for reading\n const tmpDb = path.join(os.tmpdir(), `pai-edge-history-${Date.now()}.db`);\n try {\n await fs.copyFile(histPath, tmpDb);\n\n const query = `\n SELECT\n REPLACE(REPLACE(SUBSTR(url, 1, INSTR(SUBSTR(url, 9), '/') + 8), 'https://', ''), 'http://', '') as domain,\n COUNT(*) as visits\n FROM urls\n WHERE last_visit_time > (strftime('%s', 'now', '-30 days') + 11644473600) * 1000000\n GROUP BY domain\n ORDER BY visits DESC\n LIMIT 30;\n `.trim();\n\n const output = await exec(\"sqlite3\", [tmpDb, query]);\n\n if (!output) {\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: \"Could not query Edge history (DB may be locked).\",\n };\n }\n\n const lines = [\n \"## Top 30 Domains (last 30 days)\",\n \"\",\n ...output.split(\"\\n\").map((line) => {\n const [domain, visits] = line.split(\"|\");\n return `- ${domain}: ${visits} visits`;\n }),\n ];\n\n return {\n id: \"browser-domains\",\n title: \"Browser Top Domains (30 days)\",\n content: lines.join(\"\\n\"),\n };\n } finally {\n try {\n await fs.unlink(tmpDb);\n } catch {\n // Cleanup failure is ok\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// 12. GitHub Profile\n// ---------------------------------------------------------------------------\n\nexport async function collectGitHubProfile(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Check if gh CLI is available\n const ghVersion = await exec(\"gh\", [\"--version\"]);\n if (!ghVersion) {\n return {\n id: \"github-profile\",\n title: \"GitHub Profile\",\n content: \"GitHub CLI (gh) not installed.\",\n };\n }\n\n // Check auth status\n const authStatus = await exec(\"gh\", [\"auth\", \"status\"]);\n if (!authStatus) {\n return {\n id: \"github-profile\",\n title: \"GitHub Profile\",\n content: \"GitHub CLI not authenticated. Run `gh auth login` to enable.\",\n };\n }\n\n // Get user profile\n const userJson = await exec(\"gh\", [\n \"api\", \"user\",\n \"--jq\", '[.login, .name, .bio, .company, .location, .blog, .public_repos, .followers, .following, .created_at] | @tsv',\n ]);\n\n if (userJson) {\n const parts = userJson.split(\"\\t\");\n const [login, name, bio, company, location, blog, publicRepos, followers, following, createdAt] = parts;\n lines.push(\"## GitHub Profile\", \"\");\n if (login) lines.push(`- Username: ${login}`);\n if (name) lines.push(`- Name: ${name}`);\n if (bio) lines.push(`- Bio: ${bio}`);\n if (company) lines.push(`- Company: ${company}`);\n if (location) lines.push(`- Location: ${location}`);\n if (blog) lines.push(`- Website: ${blog}`);\n if (publicRepos) lines.push(`- Public Repos: ${publicRepos}`);\n if (followers || following) lines.push(`- Followers/Following: ${followers}/${following}`);\n if (createdAt) lines.push(`- Member since: ${createdAt}`);\n }\n\n // Get recent repos (owned, sorted by push date)\n const reposJson = await exec(\"gh\", [\n \"repo\", \"list\", \"--limit\", \"10\", \"--sort\", \"updated\", \"--json\",\n \"name,description,primaryLanguage,pushedAt,isPrivate\",\n \"--jq\", '.[] | [.name, .description, (.primaryLanguage.name // \"\"), .pushedAt, .isPrivate] | @tsv',\n ]);\n\n if (reposJson) {\n const repos = reposJson.split(\"\\n\").filter(Boolean);\n if (repos.length > 0) {\n lines.push(\"\", \"## Recent GitHub Repos (by push date)\", \"\");\n for (const repo of repos) {\n const [name, desc, lang, _pushed, isPrivate] = repo.split(\"\\t\");\n const visibility = isPrivate === \"true\" ? \"private\" : \"public\";\n const langTag = lang ? ` [${lang}]` : \"\";\n const descTag = desc ? ` — ${desc}` : \"\";\n lines.push(`- ${name}${langTag} (${visibility})${descTag}`);\n }\n }\n }\n\n // Get starred repos (recent 20 for interest signals)\n const starsJson = await exec(\"gh\", [\n \"api\", \"user/starred?per_page=20&sort=created&direction=desc\",\n \"--jq\", '.[].full_name',\n ]);\n\n if (starsJson) {\n const stars = starsJson.split(\"\\n\").filter(Boolean);\n if (stars.length > 0) {\n lines.push(\"\", \"## Recently Starred Repos (interest signals)\", \"\");\n lines.push(stars.map((s) => `- ${s}`).join(\"\\n\"));\n }\n }\n\n return {\n id: \"github-profile\",\n title: \"GitHub Profile & Activity\",\n content: lines.length > 0 ? lines.join(\"\\n\") : \"No GitHub data accessible.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 13. Recent Focus (inferred from git commits + shell history)\n// ---------------------------------------------------------------------------\n\nexport async function collectRecentFocus(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // Analyze recent commit messages across active repos for topic signals\n const repos = await findRecentGitRepos(14); // Last 2 weeks\n const commitTopics = new Map<string, number>();\n const recentMessages: string[] = [];\n\n for (const repo of repos.slice(0, 8)) {\n // Get recent commit subjects from this repo\n const log = await exec(\"git\", [\n \"-C\", repo,\n \"log\", \"--oneline\", \"--since=14 days ago\",\n \"--format=%s\", \"-n\", \"20\",\n ]);\n if (log) {\n for (const msg of log.split(\"\\n\").filter(Boolean)) {\n recentMessages.push(msg);\n // Extract first word (often a conventional commit type)\n const type = msg.match(/^(feat|fix|refactor|test|docs|chore|perf|ci|build|style)/i);\n if (type) {\n const key = type[1].toLowerCase();\n commitTopics.set(key, (commitTopics.get(key) ?? 0) + 1);\n }\n }\n }\n }\n\n if (commitTopics.size > 0) {\n const sorted = [...commitTopics.entries()].sort((a, b) => b[1] - a[1]);\n lines.push(\"## Recent Commit Activity (last 2 weeks)\", \"\");\n lines.push(`Total commits analyzed: ${recentMessages.length}`);\n lines.push(\"\", \"Commit types:\");\n for (const [type, count] of sorted) {\n lines.push(`- ${type}: ${count}`);\n }\n }\n\n // Extract recent commit subjects for topic inference\n if (recentMessages.length > 0) {\n lines.push(\"\", \"## Recent Commit Messages (sample)\", \"\");\n // Take a diverse sample (first 15 unique-ish messages)\n const unique = [...new Set(recentMessages)].slice(0, 15);\n for (const msg of unique) {\n lines.push(`- ${msg}`);\n }\n }\n\n // Recent shell commands that indicate current work context\n const histPath = path.join(HOME, \".zsh_history\");\n const histRaw = await readSafe(histPath);\n if (histRaw) {\n const histLines = histRaw.split(\"\\n\");\n // Take last 200 history entries and extract project-related patterns\n const recent = histLines.slice(-200);\n const cdPaths = new Map<string, number>();\n const tools = new Map<string, number>();\n\n for (const line of recent) {\n const cmd = line.replace(/^:\\s*\\d+:\\d+;/, \"\").trim();\n // Track cd destinations\n const cdMatch = cmd.match(/^cd\\s+(.+)/);\n if (cdMatch) {\n const dest = cdMatch[1].trim().replace(/^~/, HOME);\n cdPaths.set(dest, (cdPaths.get(dest) ?? 0) + 1);\n }\n // Track tool usage\n const toolMatch = cmd.match(/^(docker|kubectl|terraform|aws|gcloud|firebase|vercel|netlify|npm|pnpm|yarn|bun|cargo|go|python|pip|uv)\\b/);\n if (toolMatch) {\n tools.set(toolMatch[1], (tools.get(toolMatch[1]) ?? 0) + 1);\n }\n }\n\n if (cdPaths.size > 0) {\n const topDirs = [...cdPaths.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);\n lines.push(\"\", \"## Recent Working Directories\", \"\");\n for (const [dir, count] of topDirs) {\n lines.push(`- ${dir.replace(HOME, \"~\")}: ${count}x`);\n }\n }\n\n if (tools.size > 0) {\n const topTools = [...tools.entries()].sort((a, b) => b[1] - a[1]);\n lines.push(\"\", \"## Recently Used Tools (from history)\", \"\");\n for (const [tool, count] of topTools) {\n lines.push(`- ${tool}: ${count}x`);\n }\n }\n }\n\n return {\n id: \"recent-focus\",\n title: \"Recent Focus & Activity\",\n content: lines.length > 0\n ? lines.join(\"\\n\")\n : \"No recent activity data found.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// 14. Social & Registry Profiles\n// ---------------------------------------------------------------------------\n\nexport async function collectSocialProfiles(): Promise<CollectorResult> {\n const lines: string[] = [];\n\n // npm registry identity\n const npmrc = await readSafe(path.join(HOME, \".npmrc\"));\n if (npmrc) {\n // Extract registry URL (but not tokens)\n const registryMatch = npmrc.match(/^registry\\s*=\\s*(.+)/m);\n if (registryMatch) lines.push(`- npm registry: ${registryMatch[1].trim()}`);\n // Check for scope configs\n const scopes = npmrc.match(/^@[\\w-]+:registry/gm);\n if (scopes) {\n for (const s of scopes) {\n lines.push(`- npm scope: ${s.split(\":\")[0]}`);\n }\n }\n }\n\n // npm whoami\n const npmUser = await exec(\"npm\", [\"whoami\"]);\n if (npmUser) lines.push(`- npm username: ${npmUser}`);\n\n // PyPI config\n const pypirc = await readSafe(path.join(HOME, \".pypirc\"));\n if (pypirc) {\n const repoMatch = pypirc.match(/repository\\s*=\\s*(.+)/);\n if (repoMatch) lines.push(`- PyPI repository: ${repoMatch[1].trim()}`);\n const usernameMatch = pypirc.match(/username\\s*=\\s*(.+)/);\n if (usernameMatch) lines.push(`- PyPI username: ${usernameMatch[1].trim()}`);\n }\n\n // Docker Hub\n const dockerConfig = await readSafe(path.join(HOME, \".docker\", \"config.json\"));\n if (dockerConfig) {\n try {\n const cfg = JSON.parse(dockerConfig);\n const auths = Object.keys(cfg.auths ?? {});\n if (auths.length > 0) {\n lines.push(`- Docker registries: ${auths.join(\", \")}`);\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n // Cargo / crates.io\n const cargoConfig = await readSafe(path.join(HOME, \".cargo\", \"config.toml\"));\n if (cargoConfig) {\n const registries = cargoConfig.match(/\\[registries\\.(\\w+)\\]/g);\n if (registries) {\n lines.push(`- Cargo registries: ${registries.map((r) => r.replace(/\\[registries\\.|\\]/g, \"\")).join(\", \")}`);\n }\n }\n\n // Cloud provider CLIs\n const awsIdentity = await exec(\"aws\", [\"sts\", \"get-caller-identity\", \"--query\", \"Account\", \"--output\", \"text\"]);\n if (awsIdentity) lines.push(`- AWS Account: ${awsIdentity}`);\n\n const gcpProject = await exec(\"gcloud\", [\"config\", \"get-value\", \"project\"]);\n if (gcpProject && !gcpProject.includes(\"unset\")) lines.push(`- GCP Project: ${gcpProject}`);\n\n const vercelUser = await exec(\"vercel\", [\"whoami\"]);\n if (vercelUser) lines.push(`- Vercel: ${vercelUser}`);\n\n return {\n id: \"social-profiles\",\n title: \"Registry & Cloud Profiles\",\n content: lines.length > 0\n ? [\"## Registry & Cloud Accounts\", \"\", ...lines].join(\"\\n\")\n : \"No registry or cloud profiles detected.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\n/** Find git repos under ~/Documents modified in the last N days */\nasync function findRecentGitRepos(days: number): Promise<string[]> {\n const output = await exec(\"find\", [\n path.join(HOME, \"Documents\"),\n \"-maxdepth\", \"4\",\n \"-name\", \".git\",\n \"-type\", \"d\",\n \"-mtime\", `-${days}`,\n ]);\n\n if (!output) return [];\n return output\n .split(\"\\n\")\n .filter(Boolean)\n .map((p) => p.replace(/\\/.git$/, \"\"));\n}\n\n// ---------------------------------------------------------------------------\n// Export all collectors\n// ---------------------------------------------------------------------------\n\n/** All available Mac collectors in execution order */\nexport const ALL_COLLECTORS = [\n collectIdentityProfile,\n collectCalendarContext,\n collectFileOrganization,\n collectDevEnvironment,\n collectDevPreferences,\n collectShellHabits,\n collectCodingRules,\n collectActiveProjects,\n collectProductivitySetup,\n collectBrowserBookmarks,\n collectBrowserDomains,\n collectGitHubProfile,\n collectRecentFocus,\n collectSocialProfiles,\n] as const;\n","/**\n * Profile module — compile, load, and rebuild user profiles.\n */\n\nimport fs from \"node:fs/promises\";\nimport { getProfilePath } from \"../config/paths.js\";\nimport { addConnectorEntry } from \"../raw/add.js\";\nimport * as console from \"../utils/console.js\";\nimport { compileProfile, compileCompactProfile } from \"./compile.js\";\nimport type { CollectorResult } from \"../types.js\";\nimport { ALL_COLLECTORS } from \"../connectors/mac/collectors.js\";\n\nexport { compileProfile, compileCompactProfile } from \"./compile.js\";\n\n/** Load the current profile from disk. Returns null if not found. */\nexport async function loadProfile(): Promise<string | null> {\n try {\n return await fs.readFile(getProfilePath(), \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** Save profile markdown to disk. */\nexport async function saveProfile(content: string): Promise<string> {\n const profilePath = getProfilePath();\n await fs.writeFile(profilePath, content, \"utf-8\");\n return profilePath;\n}\n\n/**\n * Full rebuild: scan local machine → compile profile → write to disk.\n * Also writes raw files for the scan data (for vault/distill pipeline compatibility).\n */\nexport async function rebuildProfile(opts?: {\n /** If true, skip writing raw files (profile-only mode) */\n skipRaw?: boolean;\n /** If true, show progress spinners */\n verbose?: boolean;\n}): Promise<{ profilePath: string; results: CollectorResult[] }> {\n const verbose = opts?.verbose ?? true;\n\n // 1. Run all collectors concurrently\n const spin = verbose ? console.spinner(\"Scanning local machine...\") : null;\n\n const settled = await Promise.allSettled(\n ALL_COLLECTORS.map((collector) => collector()),\n );\n\n const results: CollectorResult[] = [];\n const failed: string[] = [];\n\n for (let i = 0; i < settled.length; i++) {\n const s = settled[i];\n if (s.status === \"fulfilled\") {\n results.push(s.value);\n } else {\n const name = ALL_COLLECTORS[i].name;\n failed.push(name);\n if (verbose) console.warn(`Collector ${name} failed: ${String(s.reason)}`);\n }\n }\n\n spin?.succeed(`Scanned ${results.length} sources (${failed.length} failed)`);\n\n // 2. Optionally write raw files (for backward compat with vault/distill)\n if (!opts?.skipRaw) {\n for (const entry of results) {\n try {\n await addConnectorEntry(\"mac\", entry);\n } catch {\n // Non-critical — profile compilation doesn't depend on raw writes\n }\n }\n }\n\n // 3. Compile and save profile\n const profileContent = compileProfile(results);\n const profilePath = await saveProfile(profileContent);\n\n return { profilePath, results };\n}\n","import type { Command } from \"commander\";\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n getPaiHome,\n getRawDir,\n getVaultDir,\n getMemoryDir,\n getWeeklyDir,\n getSkillsDir,\n getConfigDir,\n getConfigPath,\n getProfilesPath,\n getPreferencesPath,\n} from \"../config/paths.js\";\nimport { DEFAULT_CONFIG, DEFAULT_PROFILES, DEFAULT_PREFERENCES } from \"../config/defaults.js\";\nimport { saveConfig } from \"../config/io.js\";\nimport { execQmd, isQmdAvailable } from \"../utils/process.js\";\nimport { rebuildProfile } from \"../profile/index.js\";\nimport * as console from \"../utils/console.js\";\n\n/**\n * Extract key highlights from profile.md for display after init.\n * Pulls: identity, active repos, recent commit stats, top tools.\n */\nfunction extractProfileHighlights(content: string): string | null {\n const lines = content.split(\"\\n\");\n const highlights: string[] = [];\n\n // 1. Identity (name, email, etc.)\n let inIdentity = false;\n let identityCount = 0;\n for (const line of lines) {\n if (line.startsWith(\"## \") && /identity/i.test(line)) { inIdentity = true; continue; }\n if (inIdentity && line.startsWith(\"## \")) break;\n if (inIdentity && line.trim()) {\n highlights.push(line);\n identityCount++;\n if (identityCount >= 8) break;\n }\n }\n\n // 2. Active Git Repos — show project folder names\n let inRepos = false;\n let repoCount = 0;\n for (const line of lines) {\n if (line.startsWith(\"## Active Git Repositories\")) {\n inRepos = true;\n highlights.push(\"\");\n highlights.push(\"Recent Projects:\");\n continue;\n }\n if (inRepos && line.startsWith(\"## \")) break;\n if (inRepos && line.trim().startsWith(\"-\")) {\n const repoPath = line.trim().replace(/^-\\s*/, \"\");\n const name = repoPath.split(\"/\").pop() || repoPath;\n highlights.push(` - ${name}`);\n repoCount++;\n if (repoCount >= 6) break;\n }\n }\n\n // 3. Recent Commit Activity — one-line summary\n let inCommits = false;\n for (const line of lines) {\n if (line.startsWith(\"## Recent Commit Activity\")) { inCommits = true; continue; }\n if (inCommits && line.startsWith(\"## \")) break;\n if (inCommits && /^total commits/i.test(line.trim())) {\n highlights.push(\"\");\n highlights.push(`Recent Activity: ${line.trim()}`);\n break;\n }\n }\n\n // 4. Top tools\n let inTools = false;\n const tools: string[] = [];\n for (const line of lines) {\n if (line.startsWith(\"## Recently Used Tools\")) { inTools = true; continue; }\n if (inTools && line.startsWith(\"## \")) break;\n if (inTools && line.trim().startsWith(\"-\")) {\n const tool = line.trim().replace(/^-\\s*/, \"\").split(\":\")[0].trim();\n tools.push(tool);\n if (tools.length >= 8) break;\n }\n }\n if (tools.length > 0) {\n highlights.push(`Top Tools: ${tools.join(\", \")}`);\n }\n\n return highlights.length > 0 ? highlights.join(\"\\n\") : null;\n}\n\n/** Try to install QMD globally via npm */\nasync function installQmd(): Promise<boolean> {\n const spin = console.spinner(\"Installing QMD (search engine)...\");\n return new Promise((resolve) => {\n execFile(\"npm\", [\"install\", \"-g\", \"https://github.com/tobi/qmd\"], {\n encoding: \"utf-8\",\n timeout: 120_000,\n }, (err) => {\n if (err) {\n spin.fail(\"QMD installation failed.\");\n console.warn(\"Install manually: npm install -g https://github.com/tobi/qmd\");\n resolve(false);\n } else {\n spin.succeed(\"QMD installed.\");\n resolve(true);\n }\n });\n });\n}\n\n/** Options for runInit (used by both init and reset) */\nexport interface InitOptions {\n /** If true, overwrite config/profiles/preferences; if false, skip when file exists */\n overwriteConfig?: boolean;\n /** If true, skip local machine scan and all steps 3-8 (for CI/testing) */\n skipScan?: boolean;\n /** If true, skip Google OAuth (for agents; no browser) */\n nonInteractive?: boolean;\n}\n\nconst TOTAL_STEPS = 8;\n\n/** Create directory structure, write default configs, register QMD, scan, profile, Google import, index. */\nexport async function runInit(options: InitOptions = {}): Promise<void> {\n const { overwriteConfig = false, skipScan = false, nonInteractive = false } = options;\n const paiHome = getPaiHome();\n\n let profileLines = 0;\n let profileSources = 0;\n let gmailCount = 0;\n let calendarCount = 0;\n let googleAuthed = false;\n let qmdReady = false;\n\n // [1/8] Create directory structure\n const dirs = [\n path.join(getRawDir(), \"local\"),\n path.join(getRawDir(), \"web\"),\n path.join(getRawDir(), \"connector\"),\n path.join(getVaultDir(), \"coding\"),\n path.join(getVaultDir(), \"work\"),\n path.join(getVaultDir(), \"life\"),\n path.join(getVaultDir(), \"preferences\"),\n path.join(getVaultDir(), \"context\"),\n getMemoryDir(),\n getWeeklyDir(),\n getSkillsDir(),\n getConfigDir(),\n ];\n\n for (const dir of dirs) {\n await fs.mkdir(dir, { recursive: true });\n }\n\n // [1/8] Write default configs\n const configPath = getConfigPath();\n const profilesPath = getProfilesPath();\n const prefsPath = getPreferencesPath();\n\n if (overwriteConfig) {\n await saveConfig(configPath, DEFAULT_CONFIG);\n await saveConfig(profilesPath, DEFAULT_PROFILES);\n await saveConfig(prefsPath, DEFAULT_PREFERENCES);\n } else {\n try {\n await fs.access(configPath);\n console.info(\"Config already exists, skipping.\");\n } catch {\n await saveConfig(configPath, DEFAULT_CONFIG);\n }\n try {\n await fs.access(profilesPath);\n console.info(\"Profiles config already exists, skipping.\");\n } catch {\n await saveConfig(profilesPath, DEFAULT_PROFILES);\n }\n try {\n await fs.access(prefsPath);\n console.info(\"Preferences already exists, skipping.\");\n } catch {\n await saveConfig(prefsPath, DEFAULT_PREFERENCES);\n console.success(\"Created preferences.md — edit to customize AI behavior.\");\n }\n }\n console.success(`[1/${TOTAL_STEPS}] Creating directories & config...`);\n\n // [2/8] QMD: check → auto-install if missing → register collections\n qmdReady = await isQmdAvailable();\n\n if (!qmdReady) {\n qmdReady = await installQmd();\n }\n\n if (qmdReady) {\n try {\n await execQmd([\n \"collection\", \"add\", getRawDir(),\n \"--name\", \"raw\", \"--mask\", \"**/*.md\",\n ]);\n console.success(\"QMD collection 'raw' registered.\");\n } catch {\n console.warn(\"QMD collection 'raw' may already exist, skipping.\");\n }\n try {\n await execQmd([\n \"collection\", \"add\", getVaultDir(),\n \"--name\", \"vault\", \"--mask\", \"**/*.md\",\n ]);\n console.success(\"QMD collection 'vault' registered.\");\n } catch {\n console.warn(\"QMD collection 'vault' may already exist, skipping.\");\n }\n }\n if (qmdReady) {\n console.success(`[2/${TOTAL_STEPS}] Search engine (QMD) ready.`);\n } else {\n console.warn(`[2/${TOTAL_STEPS}] Search engine (QMD) not available; built-in search will be used.`);\n }\n\n if (skipScan) {\n console.log(\"\");\n console.info(\"Directory structure:\");\n console.log(` ${paiHome}/profile.md — (skipped; run pai profile --rebuild)`);\n console.log(` ${paiHome}/raw/ — raw data input`);\n console.log(` ${paiHome}/vault/ — PINData knowledge store`);\n console.log(` ${paiHome}/memory/ — daily journal & digests`);\n console.log(` ${paiHome}/config/ — configuration`);\n console.info('Run \"pai profile --rebuild\" to scan and build your profile.');\n return;\n }\n\n // [3/8] Auto-scan local machine\n try {\n const { profilePath, results } = await rebuildProfile({ verbose: true });\n profileSources = results.length;\n try {\n const content = await fs.readFile(profilePath, \"utf-8\");\n profileLines = content.split(\"\\n\").length;\n } catch {\n profileLines = 0;\n }\n console.success(`[3/${TOTAL_STEPS}] Scanning your machine (${profileSources} sources)...`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[3/${TOTAL_STEPS}] Scan failed: ${msg}`);\n }\n\n // [4/8] Profile already compiled by rebuildProfile\n console.success(`[4/${TOTAL_STEPS}] Compiling profile...`);\n\n // [5/8] Google OAuth (skip if non-interactive)\n if (!nonInteractive) {\n try {\n const { encryption } = await import(\"../auth/encryption.js\");\n const { googleOAuth } = await import(\"../auth/google-oauth.js\");\n await encryption.loadKey();\n await googleOAuth.init();\n if (googleOAuth.isAuthenticated()) {\n googleAuthed = true;\n console.success(`[5/${TOTAL_STEPS}] Connecting Google account... (already connected)`);\n } else {\n await googleOAuth.authorize();\n googleAuthed = true;\n console.success(`[5/${TOTAL_STEPS}] Connecting Google account...`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[5/${TOTAL_STEPS}] Google auth skipped: ${msg}`);\n console.warn(\"Run \\\"pai auth google\\\" later to connect Gmail and Calendar.\");\n }\n } else {\n console.info(`[5/${TOTAL_STEPS}] Connecting Google account... (skipped, non-interactive)`);\n }\n\n // [6/8] Gmail import\n if (googleAuthed) {\n const spinGmail = console.spinner(`[6/${TOTAL_STEPS}] Importing Gmail (latest 100)...`);\n try {\n const { syncGmail } = await import(\"../connectors/google/gmail.js\");\n const { addConnectorEntry } = await import(\"../raw/add.js\");\n const entries = await syncGmail({ days: 365, maxResults: 100 });\n for (const entry of entries) {\n const status = await addConnectorEntry(\"gmail\", entry);\n if (status === \"created\" || status === \"updated\") gmailCount++;\n }\n spinGmail.succeed(`[6/${TOTAL_STEPS}] Importing Gmail... ${gmailCount} message(s)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spinGmail.fail(`[6/${TOTAL_STEPS}] Gmail import failed: ${msg}`);\n }\n } else {\n console.info(`[6/${TOTAL_STEPS}] Importing Gmail... (skipped, no Google auth)`);\n }\n\n // [7/8] Calendar import\n if (googleAuthed) {\n const spinCal = console.spinner(`[7/${TOTAL_STEPS}] Importing Calendar...`);\n try {\n const { syncCalendar } = await import(\"../connectors/google/calendar.js\");\n const { addConnectorEntry } = await import(\"../raw/add.js\");\n const entries = await syncCalendar({ lookbackDays: 30, lookforwardDays: 90 });\n for (const entry of entries) {\n const status = await addConnectorEntry(\"calendar\", entry);\n if (status === \"created\" || status === \"updated\") calendarCount++;\n }\n spinCal.succeed(`[7/${TOTAL_STEPS}] Importing Calendar... ${calendarCount} event(s)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spinCal.fail(`[7/${TOTAL_STEPS}] Calendar import failed: ${msg}`);\n }\n } else {\n console.info(`[7/${TOTAL_STEPS}] Importing Calendar... (skipped, no Google auth)`);\n }\n\n // [8/8] QMD index + embed\n if (qmdReady) {\n try {\n const { updateIndex } = await import(\"../search/index.js\");\n await updateIndex();\n console.success(`[8/${TOTAL_STEPS}] Indexing for search...`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[8/${TOTAL_STEPS}] Indexing failed: ${msg}. Built-in search still works.`);\n }\n } else {\n console.info(`[8/${TOTAL_STEPS}] Indexing for search... (skipped, QMD not available)`);\n }\n\n // Summary\n console.log(\"\");\n console.success(\"Setup complete!\");\n console.log(\"\");\n\n // Print profile highlights so agents can read them directly from init output\n try {\n const profileContent = await fs.readFile(path.join(paiHome, \"profile.md\"), \"utf-8\");\n const highlights = extractProfileHighlights(profileContent);\n if (highlights) {\n console.log(\"── Profile Summary ──\");\n console.log(highlights);\n console.log(\"─────────────────────\");\n console.log(\"\");\n }\n } catch {\n // Profile not readable — skip highlights\n }\n\n console.log(` Profile: ${paiHome}/profile.md (${profileLines} lines, ${profileSources} sources)`);\n const rawCount = gmailCount + calendarCount;\n if (rawCount > 0) {\n console.log(` Raw data: ${rawCount} file(s) (${gmailCount} gmail, ${calendarCount} calendar)`);\n } else {\n console.log(` Raw data: ${paiHome}/raw/`);\n }\n console.log(` Search: ${qmdReady ? \"QMD hybrid ready\" : \"built-in keyword (install QMD for semantic)\"}`);\n console.log(` Google: ${googleAuthed ? \"connected\" : \"not connected\"}`);\n console.log(\"\");\n console.info(\"Next:\");\n console.log(\" pai profile # View full profile\");\n console.log(\" pai distribute # Deploy to Cursor/Claude\");\n console.log(\" pai ask \\\"question\\\" # Ask about yourself\");\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize pai: create dirs, scan, profile, Google import, index (full pipeline)\")\n .option(\"--skip-scan\", \"Skip scan and steps 3-8 (CI/testing)\")\n .option(\"--non-interactive\", \"Skip Google OAuth (for agents; no browser)\")\n .action(async (opts: { skipScan?: boolean; nonInteractive?: boolean }) => {\n const paiHome = getPaiHome();\n const spin = console.spinner(`Initializing pai at ${paiHome}...`);\n try {\n spin.succeed(`Initializing pai at ${paiHome}`);\n await runInit({\n overwriteConfig: false,\n skipScan: opts.skipScan,\n nonInteractive: opts.nonInteractive,\n });\n } catch (err) {\n spin.fail(\"Initialization failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","/**\n * Web scraper: three-tier strategy for content extraction.\n *\n * 1. GitHub blob URLs → convert to raw.githubusercontent.com, fetch raw markdown (zero rendering)\n * 2. General URLs → Playwright headless browser + DOM preprocessing + Defuddle extraction\n * 3. Fallback → plain fetch + Defuddle (for when Playwright is unavailable)\n *\n * Reference: linkmind-master/src/scraper.ts\n */\n\nimport * as console from \"../utils/console.js\";\n\nconst CHROME_UA =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\n\n/** Minimum markdown length to consider a scrape successful */\nconst MIN_CONTENT_LENGTH = 100;\n\nexport interface ScrapeResult {\n url: string;\n title: string;\n markdown: string;\n}\n\n// ---------------------------------------------------------------------------\n// Domain-specific fast paths\n// ---------------------------------------------------------------------------\n\n/** Check if a URL points to a file on GitHub (blob view) */\nfunction isGithubBlobUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.hostname === \"github.com\" && /\\/blob\\//.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n/** Convert GitHub blob URL to raw.githubusercontent.com URL */\nfunction toRawGithubUrl(url: string): string {\n // github.com/OWNER/REPO/blob/BRANCH/PATH\n // → raw.githubusercontent.com/OWNER/REPO/BRANCH/PATH\n return url\n .replace(\"https://github.com/\", \"https://raw.githubusercontent.com/\")\n .replace(\"/blob/\", \"/\");\n}\n\n/** Fast path: fetch raw content directly from GitHub (returns markdown/text as-is) */\nasync function scrapeGithubRaw(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n const rawUrl = toRawGithubUrl(url);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const resp = await fetch(rawUrl, {\n signal: controller.signal,\n headers: { \"User-Agent\": CHROME_UA },\n });\n if (!resp.ok) {\n throw new Error(`GitHub raw fetch failed: HTTP ${resp.status}`);\n }\n const markdown = await resp.text();\n\n // Extract title from first heading or filename\n const titleMatch = markdown.match(/^#\\s+(.+)$/m);\n const pathParts = new URL(url).pathname.split(\"/\");\n const filename = pathParts[pathParts.length - 1] ?? \"Untitled\";\n const title = titleMatch?.[1]?.trim() ?? filename;\n\n return { url, title, markdown };\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Playwright scraper (primary path for general URLs)\n// ---------------------------------------------------------------------------\n\n/**\n * DOM preprocessing script executed inside the browser.\n * Removes navigation, ads, cookie banners, and other non-content elements\n * before Defuddle extraction. (Borrowed from linkmind)\n */\nconst DOM_PREPROCESS_SCRIPT = `(() => {\n // Remove script, style, stylesheet links\n document.querySelectorAll(\"script, style, link[rel='stylesheet']\").forEach(el => el.remove());\n // Remove navigation elements\n document.querySelectorAll(\"nav, footer, aside\").forEach(el => el.remove());\n // Remove headers not inside article/main\n document.querySelectorAll(\"header\").forEach(el => {\n if (!el.closest(\"article\") && !el.closest(\"main\")) el.remove();\n });\n // Remove ARIA landmark roles\n document.querySelectorAll('[role=\"navigation\"], [role=\"banner\"], [role=\"contentinfo\"], [role=\"complementary\"], [role=\"search\"]').forEach(el => el.remove());\n // Remove cookie/share/comment noise\n document.querySelectorAll('[class*=\"cookie-banner\"], [id*=\"cookie-banner\"], [class*=\"cookie-consent\"], [class*=\"share-buttons\"], [class*=\"social-share\"], [class*=\"comment-section\"], [id*=\"comments\"]').forEach(el => el.remove());\n // Remove hidden elements\n document.querySelectorAll('[hidden], [aria-hidden=\"true\"]').forEach(el => el.remove());\n\n return {\n title: document.title,\n html: document.documentElement.outerHTML,\n };\n})()`;\n\n/** Scrape with Playwright headless browser + Defuddle */\nasync function scrapeWithPlaywright(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n // Dynamic import so the module still loads when playwright is not installed\n const pw = await import(\"playwright\");\n const { Defuddle } = await import(\"defuddle/node\");\n\n const browser = await pw.chromium.launch({\n headless: true,\n args: [\"--disable-blink-features=AutomationControlled\"],\n });\n\n try {\n const context = await browser.newContext({\n viewport: { width: 1280, height: 900 },\n userAgent: CHROME_UA,\n locale: \"en-US\",\n });\n\n const page = await context.newPage();\n await page.goto(url, { waitUntil: \"domcontentloaded\", timeout });\n // Wait for JS rendering (dynamic content, SPAs)\n await page.waitForTimeout(2000);\n\n // Preprocess DOM and grab cleaned HTML\n const { title: pageTitle, html } = (await page.evaluate(\n DOM_PREPROCESS_SCRIPT,\n )) as { title: string; html: string };\n\n await browser.close();\n\n // Defuddle content extraction (suppress noisy log)\n const origLog = globalThis.console.log;\n globalThis.console.log = (msg: unknown, ...args: unknown[]) => {\n if (\n typeof msg === \"string\" &&\n msg.includes(\"Initial parse returned very little content\")\n )\n return;\n origLog(msg, ...args);\n };\n const result = await Defuddle(html, url);\n globalThis.console.log = origLog;\n\n const title = result.title || pageTitle || \"Untitled\";\n const markdown = result.content\n ? htmlToSimpleMarkdown(result.content)\n : \"\";\n\n return { url, title, markdown };\n } catch (err) {\n await browser.close().catch(() => {});\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fetch fallback (when Playwright is unavailable)\n// ---------------------------------------------------------------------------\n\n/** Lightweight scrape: plain HTTP fetch + Defuddle (no JS rendering) */\nasync function scrapeWithFetch(\n url: string,\n timeout: number,\n): Promise<ScrapeResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n let html: string;\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n \"User-Agent\": CHROME_UA,\n Accept: \"text/html,application/xhtml+xml\",\n },\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n html = await response.text();\n } finally {\n clearTimeout(timer);\n }\n\n // Try Defuddle\n try {\n const { Defuddle } = await import(\"defuddle/node\");\n const result = await Defuddle(html, url);\n const title = result.title || extractTitleFromHtml(html);\n const markdown = result.content\n ? htmlToSimpleMarkdown(result.content)\n : extractTextFromHtml(html);\n return { url, title, markdown };\n } catch {\n // Defuddle unavailable — raw HTML stripping\n console.warn(\"defuddle not available, using basic HTML extraction\");\n }\n\n const title = extractTitleFromHtml(html);\n const markdown = extractTextFromHtml(html);\n return { url, title, markdown };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Scrape a URL and return title + markdown content.\n *\n * Strategy:\n * 1. GitHub blob URL → raw.githubusercontent.com (instant, perfect fidelity)\n * 2. Playwright + Defuddle (handles JS-rendered pages)\n * 3. Fetch + Defuddle fallback (static pages, or when Playwright missing)\n */\nexport async function scrapeUrl(\n url: string,\n timeout: number = 30_000,\n): Promise<ScrapeResult> {\n // --- Fast path: GitHub blob → raw fetch ---\n if (isGithubBlobUrl(url)) {\n console.info(\"GitHub blob detected — fetching raw content directly\");\n return scrapeGithubRaw(url, timeout);\n }\n\n // --- Primary: Playwright ---\n try {\n const result = await scrapeWithPlaywright(url, timeout);\n\n // Quality check: if Playwright returned too little content, try fetch fallback\n if (result.markdown.length < MIN_CONTENT_LENGTH) {\n console.warn(\n `Playwright extracted only ${result.markdown.length} chars — trying fetch fallback`,\n );\n const fallback = await scrapeWithFetch(url, timeout);\n // Return whichever got more content\n return fallback.markdown.length > result.markdown.length\n ? fallback\n : result;\n }\n\n return result;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Playwright scrape failed (${msg}) — falling back to fetch`);\n }\n\n // --- Fallback: plain fetch ---\n return scrapeWithFetch(url, timeout);\n}\n\n// ---------------------------------------------------------------------------\n// HTML helpers\n// ---------------------------------------------------------------------------\n\n/** Extract <title> from HTML */\nfunction extractTitleFromHtml(html: string): string {\n const match = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n return match?.[1]?.trim() ?? \"Untitled\";\n}\n\n/** Basic HTML to text extraction (last-resort fallback) */\nfunction extractTextFromHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \"\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&nbsp;/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 10000);\n}\n\n/** Convert HTML fragment to simple Markdown (from linkmind, extended) */\nfunction htmlToSimpleMarkdown(html: string): string {\n if (!html) return \"\";\n\n let md = html;\n\n // Headings\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, \"# $1\\n\\n\");\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, \"## $1\\n\\n\");\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, \"### $1\\n\\n\");\n md = md.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, \"#### $1\\n\\n\");\n md = md.replace(/<h5[^>]*>(.*?)<\\/h5>/gi, \"##### $1\\n\\n\");\n md = md.replace(/<h6[^>]*>(.*?)<\\/h6>/gi, \"###### $1\\n\\n\");\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>/gi, \"\\n\\n\");\n md = md.replace(/<\\/p>/gi, \"\");\n md = md.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Bold and italic\n md = md.replace(/<(strong|b)[^>]*>(.*?)<\\/(strong|b)>/gi, \"**$2**\");\n md = md.replace(/<(em|i)[^>]*>(.*?)<\\/(em|i)>/gi, \"*$2*\");\n\n // Links\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, \"[$2]($1)\");\n\n // Code\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, \"`$1`\");\n md = md.replace(/<pre[^>]*>(.*?)<\\/pre>/gis, \"\\n```\\n$1\\n```\\n\");\n\n // Lists\n md = md.replace(/<li[^>]*>/gi, \"- \");\n md = md.replace(/<\\/li>/gi, \"\\n\");\n md = md.replace(/<\\/?[uo]l[^>]*>/gi, \"\\n\");\n\n // Blockquote\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gis, (_, content) => {\n return (content as string)\n .split(\"\\n\")\n .map((line: string) => `> ${line}`)\n .join(\"\\n\");\n });\n\n // Images\n md = md.replace(\n /<img[^>]*src=\"([^\"]*)\"[^>]*alt=\"([^\"]*)\"[^>]*\\/?>/gi,\n \"![$2]($1)\",\n );\n md = md.replace(/<img[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, \"![]($1)\");\n\n // Strip remaining tags\n md = md.replace(/<[^>]+>/g, \"\");\n\n // Decode entities\n md = md.replace(/&amp;/g, \"&\");\n md = md.replace(/&lt;/g, \"<\");\n md = md.replace(/&gt;/g, \">\");\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n md = md.replace(/&nbsp;/g, \" \");\n\n // Clean up whitespace\n md = md.replace(/\\n{3,}/g, \"\\n\\n\");\n md = md.trim();\n\n return md;\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport { addText, addFile, addUrl } from \"../raw/index.js\";\nimport { scrapeUrl } from \"../scraper/index.js\";\nimport { loadConfig } from \"../config/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerAddCommand(program: Command): void {\n program\n .command(\"add <input>\")\n .description(\"Add content to raw layer (text, file path, or URL with --url)\")\n .option(\"--url\", \"Treat input as URL, scrape and save\")\n .option(\"--source <source>\", \"Source tag\", \"local\")\n .action(\n async (input: string, opts: { url?: boolean; source: string }) => {\n try {\n let filePath: string;\n\n if (opts.url) {\n // URL scraping\n const spin = console.spinner(`Scraping ${input}...`);\n try {\n const config = await loadConfig();\n const result = await scrapeUrl(input, config.scraper.timeout);\n filePath = await addUrl(input, result.title, result.markdown);\n spin.succeed(`Scraped: ${result.title}`);\n } catch (err) {\n spin.fail(\"Scraping failed\");\n throw err;\n }\n } else {\n // Check if input is a file path\n try {\n const stat = await fs.stat(input);\n if (stat.isFile()) {\n filePath = await addFile(input, opts.source);\n console.success(`Added file: ${input}`);\n } else {\n throw new Error(\"Not a file\");\n }\n } catch {\n // Treat as plain text\n filePath = await addText(input, opts.source);\n console.success(\"Added text content\");\n }\n }\n\n console.info(`Saved to: ${filePath}`);\n console.info('Run \"pai distill\" to process into vault.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to add: ${msg}`);\n process.exit(1);\n }\n },\n );\n}\n","import fs from \"node:fs/promises\";\nimport OpenAI from \"openai\";\nimport { loadConfig } from \"../config/index.js\";\nimport { getPreferencesPath } from \"../config/paths.js\";\nimport type { PaiConfig } from \"../config/index.js\";\n\nlet cachedConfig: PaiConfig | null = null;\nlet cachedPreferences: string | null = null;\n\nasync function getConfig(): Promise<PaiConfig> {\n if (!cachedConfig) {\n cachedConfig = await loadConfig();\n }\n return cachedConfig;\n}\n\n/** Load user preferences.md (cached per process) */\nasync function getPreferences(): Promise<string> {\n if (cachedPreferences !== null) return cachedPreferences;\n try {\n cachedPreferences = await fs.readFile(getPreferencesPath(), \"utf-8\");\n } catch {\n cachedPreferences = \"\";\n }\n return cachedPreferences;\n}\n\n/** Create an OpenAI-compatible client from config */\nexport async function createLlmClient(): Promise<OpenAI> {\n const config = await getConfig();\n const apiKey = process.env[config.llm.apiKeyEnv];\n if (!apiKey) {\n throw new Error(\n `Missing API key: set ${config.llm.apiKeyEnv} environment variable`,\n );\n }\n return new OpenAI({\n apiKey,\n baseURL: config.llm.baseUrl || undefined,\n });\n}\n\n/**\n * Single LLM call with system + user messages.\n * Automatically prepends user preferences to the system prompt.\n * Retries once on failure.\n */\nexport async function llmCall(\n prompt: string,\n system: string,\n model?: string,\n): Promise<string> {\n const config = await getConfig();\n const client = await createLlmClient();\n const modelName = model ?? config.llm.cheapModel;\n\n // Inject user preferences into system prompt\n const prefs = await getPreferences();\n const fullSystem = prefs\n ? `${system}\\n\\n---USER PREFERENCES (always respect these)---\\n${prefs}`\n : system;\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const response = await client.chat.completions.create({\n model: modelName,\n messages: [\n { role: \"system\", content: fullSystem },\n { role: \"user\", content: prompt },\n ],\n temperature: 0.3,\n });\n return response.choices[0]?.message?.content ?? \"\";\n } catch (err) {\n if (attempt === 0) {\n // Retry once\n await new Promise((r) => setTimeout(r, 1000));\n continue;\n }\n throw err;\n }\n }\n return \"\"; // unreachable\n}\n\n/** Reset cached config and preferences (for testing) */\nexport function resetConfigCache(): void {\n cachedConfig = null;\n cachedPreferences = null;\n}\n","/** Build system prompt for the PINData extract step (replaces triage + distill) */\nexport function extractSystemPrompt(): string {\n return `Extract PINData entries from the input. Each entry is ONE atomic piece of personal knowledge.\n\nEntry types:\n- fact: concrete data point (name, number, date, account, version, error, relationship)\n- pref: implied choice or habit (tool preference, workflow pattern, interest signal)\n- decision: explicit decision with reasoning\n- entity: person, organization, or project with attributes\n- event: time-bound occurrence (meeting, deployment, payment, conference)\n\nTopic routing (pick the best fit):\n- context/identity — user's name, email, linked accounts\n- context/projects — project/repo/app names, deployments, tech stack\n- context/services — services/tools used, account IDs, subscriptions\n- preferences/tools — tool choices and why (e.g. Vercel for hosting, Airtable for PM)\n- preferences/workflow — work habits, communication style, patterns\n- work/activity — tasks, assignments, colleagues, meetings\n- work/finance — invoices, payments, amounts, subscriptions\n- life/interests — interests, events, communities, conferences\n- coding/lessons — coding lessons, debugging insights, tech decisions\n\nRules:\n- Each entry.content = ONE concrete fact with names/numbers/dates. Never generic advice.\n- Return [] (empty array) if input is pure marketing/spam with zero personal signal.\n- Prefer multiple small entries over one big entry.\n- Do NOT extract generic platitudes like \"monitor deployments regularly\".\n\nExamples:\n\nInput: \"Vercel: Failed deployment for pin-sandman on team pinai\"\nOutput: [\n {\"type\":\"fact\",\"content\":\"pin-sandman: Vercel production deployment failed, team pinai\",\"topic\":\"context/projects\",\"tags\":[\"vercel\",\"pin-sandman\"]},\n {\"type\":\"pref\",\"content\":\"Vercel: used for production deployment\",\"topic\":\"preferences/tools\",\"tags\":[\"vercel\"]}\n]\n\nInput: \"50% off all shoes this weekend only!\"\nOutput: []\n\nRespond with valid JSON array only (no markdown fences).`;\n}\n\n/** Build user prompt for extract */\nexport function extractUserPrompt(rawContent: string, source?: string): string {\n const sourceHint = source ? `\\n(Source: ${source})` : \"\";\n return `Extract PINData entries from this input:\n\n---INPUT---\n${rawContent}\n---END INPUT---\n${sourceHint}\nRespond with JSON array only.`;\n}\n\n/** Build system prompt for the daily digest */\nexport function digestSystemPrompt(): string {\n return `Summarize the day's journal entries and extracted knowledge into 3-5 key bullet points.\n\nRules:\n- Each bullet = one concrete takeaway (what happened, what was decided, what matters)\n- Include names, projects, and specific details — never vague\n- If there are action items or follow-ups, list them\n- Keep it under 200 words total\n- Output plain markdown bullets only, no JSON`;\n}\n\n/** Build user prompt for digest */\nexport function digestUserPrompt(\n journalContent: string,\n newPinData: string,\n date: string,\n): string {\n return `Summarize this day (${date}):\n\n---JOURNAL---\n${journalContent || \"(no journal entries)\"}\n---END JOURNAL---\n\n---NEW KNOWLEDGE EXTRACTED---\n${newPinData || \"(no new entries)\"}\n---END KNOWLEDGE---\n\nWrite 3-5 bullet points summarizing the day.`;\n}\n","import { llmCall } from \"../llm/index.js\";\nimport { extractSystemPrompt, extractUserPrompt } from \"../prompts/extract.js\";\nimport * as console from \"../utils/console.js\";\nimport type { PINDataEntry, PINDataType, ExtractResult } from \"../types.js\";\n\nconst VALID_TYPES = new Set<string>([\"fact\", \"pref\", \"decision\", \"entity\", \"event\"]);\n\n/**\n * Budget for the total text sent to LLM.\n * ~10K chars ≈ ~2.5K tokens (English) / ~5K tokens (CJK).\n * PINData extraction only needs gist, not full content.\n */\nconst MAX_INPUT_CHARS = 10_000;\n\n/** Head portion gets the lion's share — title, intro, overview */\nconst HEAD_CHARS = 4_000;\n/** Tail portion — conclusions, takeaways, resource lists */\nconst TAIL_CHARS = 3_000;\n/** Remaining budget goes to random middle samples */\nconst MIDDLE_BUDGET = MAX_INPUT_CHARS - HEAD_CHARS - TAIL_CHARS; // 3000\n/** Number of random middle samples to pick */\nconst MIDDLE_SAMPLES = 2;\n\n// ---------------------------------------------------------------------------\n// Content preparation — sampling > chunking\n// ---------------------------------------------------------------------------\n\n/**\n * For content that fits the budget, return as-is.\n * For long content, sample: head + tail + random middle paragraphs.\n *\n * Rationale: PINData extraction asks \"what does this mean to the USER\",\n * not \"summarize the entire document\". The head (title/intro) and tail\n * (conclusions/resources) carry 80%+ of personal signal. Middle sections\n * of long articles (paper tables, code listings, repetitive data) are\n * mostly noise for personal knowledge extraction.\n */\nfunction prepareContent(text: string): string {\n if (text.length <= MAX_INPUT_CHARS) return text;\n\n const head = text.slice(0, HEAD_CHARS);\n const tail = text.slice(-TAIL_CHARS);\n\n // Middle region: everything between head and tail\n const middleStart = HEAD_CHARS;\n const middleEnd = text.length - TAIL_CHARS;\n const middleText = text.slice(middleStart, middleEnd);\n\n // Split middle into paragraphs (double newline) and pick random samples\n const paragraphs = middleText\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 100); // skip tiny fragments\n\n const samples: string[] = [];\n let sampledChars = 0;\n const perSampleBudget = Math.floor(MIDDLE_BUDGET / MIDDLE_SAMPLES);\n\n if (paragraphs.length > 0) {\n // Pick evenly spaced paragraphs (deterministic, not truly random — reproducible)\n const step = Math.max(1, Math.floor(paragraphs.length / MIDDLE_SAMPLES));\n for (let i = 0; i < MIDDLE_SAMPLES && i * step < paragraphs.length; i++) {\n const para = paragraphs[i * step]!;\n const truncated = para.slice(0, perSampleBudget);\n samples.push(truncated);\n sampledChars += truncated.length;\n }\n }\n\n const middlePart =\n samples.length > 0\n ? `\\n\\n[... middle section sampled — ${(middleEnd - middleStart).toLocaleString()} chars total ...]\\n\\n${samples.join(\"\\n\\n---\\n\\n\")}`\n : \"\";\n\n const assembled = `${head}${middlePart}\\n\\n[... end section ...]\\n\\n${tail}`;\n\n console.info(\n `Content ${text.length.toLocaleString()} chars → sampled to ${assembled.length.toLocaleString()} chars ` +\n `(head:${HEAD_CHARS} + ${samples.length} mid-samples:${sampledChars} + tail:${TAIL_CHARS})`,\n );\n\n return assembled;\n}\n\n// ---------------------------------------------------------------------------\n// LLM response parsing\n// ---------------------------------------------------------------------------\n\n/** Parse one LLM JSON response into validated PINDataEntry[] */\nfunction parseExtractResponse(response: string): PINDataEntry[] {\n const cleaned = response\n .replace(/```json?\\n?/g, \"\")\n .replace(/```/g, \"\")\n .trim();\n const raw = JSON.parse(cleaned) as unknown;\n\n let rawEntries: unknown[];\n if (Array.isArray(raw)) {\n rawEntries = raw;\n } else if (\n raw &&\n typeof raw === \"object\" &&\n \"entries\" in raw &&\n Array.isArray((raw as Record<string, unknown>).entries)\n ) {\n rawEntries = (raw as Record<string, unknown>).entries as unknown[];\n } else {\n return [];\n }\n\n const entries: PINDataEntry[] = [];\n for (const item of rawEntries) {\n if (!item || typeof item !== \"object\") continue;\n const obj = item as Record<string, unknown>;\n\n const type = obj.type as string;\n const entryContent = obj.content as string;\n const topic = obj.topic as string;\n\n if (!type || !entryContent || !topic) continue;\n if (!VALID_TYPES.has(type)) continue;\n\n entries.push({\n type: type as PINDataType,\n content: entryContent.trim(),\n topic: topic.trim(),\n tags: Array.isArray(obj.tags)\n ? (obj.tags as unknown[]).filter(\n (t): t is string => typeof t === \"string\",\n )\n : undefined,\n });\n }\n\n return entries;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Extract PINData entries from raw/journal content via a single LLM call.\n * Long content is sampled (head + tail + middle samples) to fit the budget.\n */\nexport async function extractPinData(\n content: string,\n source?: string,\n): Promise<ExtractResult> {\n const system = extractSystemPrompt();\n const prepared = prepareContent(content);\n const prompt = extractUserPrompt(prepared, source);\n\n try {\n const response = await llmCall(prompt, system);\n const entries = parseExtractResponse(response);\n\n const summary =\n entries.length > 0\n ? entries\n .slice(0, 3)\n .map((e) => e.content)\n .join(\"; \")\n : \"no extractable signal\";\n\n return { entries, summary };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n entries: [],\n summary: `Failed to parse extract response: ${msg.slice(0, 100)}`,\n };\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getVaultDir } from \"../config/paths.js\";\nimport type { PINDataEntry, PINDataType } from \"../types.js\";\n\n/** Format a PINData entry as a markdown bullet line */\nexport function formatEntry(entry: PINDataEntry, date: string, ref?: string): string {\n const parts: string[] = [];\n parts.push(date);\n if (ref) parts.push(`ref:${ref}`);\n const meta = parts.join(\" | \");\n return `- [${entry.type}] ${entry.content} (${meta})`;\n}\n\n/** Parse a PINData line back into structured data */\nexport function parsePinDataLine(line: string): {\n type: PINDataType;\n content: string;\n date?: string;\n verified?: number;\n} | null {\n // Match: - [type] content (date | optional-meta | verified:N)\n const match = line.match(\n /^- \\[(\\w+)\\] (.+?) \\((\\d{4}-\\d{2}-\\d{2})([^)]*)\\)$/,\n );\n if (!match) return null;\n\n const meta = match[4] ?? \"\";\n const verifiedMatch = meta.match(/verified:(\\d+)/);\n\n return {\n type: match[1] as PINDataType,\n content: match[2]!,\n date: match[3],\n verified: verifiedMatch ? parseInt(verifiedMatch[1]!, 10) : undefined,\n };\n}\n\n/** Check if two PINData entries are semantically duplicate (same type + similar content) */\nfunction isDuplicate(existingContent: string, newEntry: PINDataEntry): {\n lineIndex: number;\n line: string;\n} | null {\n const lines = existingContent.split(\"\\n\");\n const newLower = newEntry.content.toLowerCase();\n // Extract key tokens from new entry (remove common words)\n const newTokens = new Set(\n newLower.split(/[\\s:,;]+/).filter((t) => t.length > 2),\n );\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n // Must match the same type tag\n if (!line.startsWith(`- [${newEntry.type}]`)) continue;\n\n const parsed = parsePinDataLine(line);\n if (!parsed) continue;\n\n const existLower = parsed.content.toLowerCase();\n const existTokens = new Set(\n existLower.split(/[\\s:,;]+/).filter((t) => t.length > 2),\n );\n\n // Calculate Jaccard similarity on significant tokens\n const intersection = [...newTokens].filter((t) => existTokens.has(t));\n const union = new Set([...newTokens, ...existTokens]);\n const similarity = union.size > 0 ? intersection.length / union.size : 0;\n\n // Threshold: 60% token overlap = duplicate\n if (similarity >= 0.6) {\n return { lineIndex: i, line };\n }\n }\n return null;\n}\n\n/**\n * Append PINData entries to the appropriate vault files.\n * - Routes each entry to vault/{topic}.md based on entry.topic\n * - Creates file + H1 header if new\n * - Deduplicates: if similar entry exists, bumps verified count + updates date\n * Returns number of new entries added and duplicates updated.\n */\nexport async function appendPinData(\n entries: PINDataEntry[],\n options: { date?: string; ref?: string } = {},\n): Promise<{ added: number; updated: number }> {\n const vaultDir = getVaultDir();\n const date = options.date ?? new Date().toISOString().split(\"T\")[0]!;\n let added = 0;\n let updated = 0;\n\n // Group entries by topic\n const byTopic = new Map<string, PINDataEntry[]>();\n for (const entry of entries) {\n const topic = entry.topic;\n if (!byTopic.has(topic)) byTopic.set(topic, []);\n byTopic.get(topic)!.push(entry);\n }\n\n for (const [topic, topicEntries] of byTopic) {\n const filePath = path.join(vaultDir, `${topic}.md`);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n // Read existing content\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch {\n // Create new file with H1 title derived from topic\n const title = topic\n .split(\"/\")\n .pop()!\n .replace(/-/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n content = `# ${title}\\n`;\n }\n\n for (const entry of topicEntries) {\n const dup = isDuplicate(content, entry);\n if (dup) {\n // Bump verified count and update date\n const parsed = parsePinDataLine(dup.line);\n const newVerified = (parsed?.verified ?? 1) + 1;\n const updatedLine = `- [${entry.type}] ${entry.content} (${date} | verified:${newVerified})`;\n const lines = content.split(\"\\n\");\n lines[dup.lineIndex] = updatedLine;\n content = lines.join(\"\\n\");\n updated++;\n } else {\n // Append new entry\n const line = formatEntry(entry, date, options.ref);\n content = content.trimEnd() + \"\\n\" + line + \"\\n\";\n added++;\n }\n }\n\n await fs.writeFile(filePath, content, \"utf-8\");\n }\n\n return { added, updated };\n}\n\n/** Read a vault file's content. Returns null if not found. */\nexport async function readVaultFile(topic: string): Promise<string | null> {\n const filePath = path.join(getVaultDir(), `${topic}.md`);\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** List all PINData entries across all vault files */\nexport async function listVaultEntries(): Promise<\n Array<{ topic: string; line: string; parsed: ReturnType<typeof parsePinDataLine> }>\n> {\n const vaultDir = getVaultDir();\n const results: Array<{\n topic: string;\n line: string;\n parsed: ReturnType<typeof parsePinDataLine>;\n }> = [];\n\n async function walk(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n const topic = path.relative(vaultDir, fullPath).replace(/\\.md$/, \"\");\n const content = await fs.readFile(fullPath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n if (line.startsWith(\"- [\")) {\n const parsed = parsePinDataLine(line);\n results.push({ topic, line, parsed });\n }\n }\n }\n }\n } catch {\n // Directory might not exist\n }\n }\n\n await walk(vaultDir);\n return results;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getMemoryDir, getJournalPath, getTodayDate } from \"../config/paths.js\";\n\n/** Append a log entry to today's journal file (memory/YYYY-MM-DD.md) */\nexport async function appendJournal(\n text: string,\n date?: string,\n): Promise<{ path: string; entryCount: number }> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n\n await fs.mkdir(getMemoryDir(), { recursive: true });\n\n const now = new Date();\n const time = now.toTimeString().slice(0, 5); // HH:MM\n const entry = `- ${time} ${text}\\n`;\n\n let existed = false;\n let existingContent = \"\";\n try {\n existingContent = await fs.readFile(filePath, \"utf-8\");\n existed = true;\n } catch {\n // File doesn't exist yet\n }\n\n if (!existed) {\n // Create new journal file with header\n const header = `# ${targetDate}\\n\\n`;\n await fs.writeFile(filePath, header + entry, \"utf-8\");\n } else {\n // Insert entry BEFORE extracted marker / digest section, and remove marker\n // so the journal will be re-processed on next distill\n const markerIdx = existingContent.indexOf(\"\\n<!-- extracted -->\");\n const digestIdx = existingContent.indexOf(\"\\n## Digest\");\n const insertIdx = Math.min(\n markerIdx >= 0 ? markerIdx : Infinity,\n digestIdx >= 0 ? digestIdx : Infinity,\n );\n\n if (insertIdx < Infinity) {\n // Insert before marker/digest, remove extracted marker\n const before = existingContent.slice(0, insertIdx).trimEnd();\n const after = existingContent.slice(insertIdx).replace(\"<!-- extracted -->\", \"\").trimStart();\n const newContent = before + \"\\n\" + entry + (after ? \"\\n\" + after : \"\") + \"\\n\";\n await fs.writeFile(filePath, newContent, \"utf-8\");\n } else {\n await fs.appendFile(filePath, entry, \"utf-8\");\n }\n }\n\n // Count entries in file\n const content = await fs.readFile(filePath, \"utf-8\");\n const entryCount = (content.match(/^- \\d{2}:\\d{2} /gm) || []).length;\n\n return { path: filePath, entryCount };\n}\n\n/** Read a journal file's content. Returns null if not found. */\nexport async function readJournal(date?: string): Promise<string | null> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** List all journal dates (YYYY-MM-DD) sorted descending */\nexport async function listJournalDates(): Promise<string[]> {\n const memDir = getMemoryDir();\n try {\n const entries = await fs.readdir(memDir);\n const dates = entries\n .filter((f) => /^\\d{4}-\\d{2}-\\d{2}\\.md$/.test(f))\n .map((f) => f.replace(\".md\", \"\"))\n .sort()\n .reverse();\n return dates;\n } catch {\n return [];\n }\n}\n\n/** Get status of a journal: entry count and whether it has a digest */\nexport async function getJournalStatus(\n date: string,\n): Promise<{ entries: number; hasDigest: boolean } | null> {\n const content = await readJournal(date);\n if (!content) return null;\n\n const entries = (content.match(/^- \\d{2}:\\d{2} /gm) || []).length;\n const hasDigest = content.includes(\"## Digest\");\n return { entries, hasDigest };\n}\n\n/** Find journal dates with missing entries in the last N days */\nexport async function findGaps(days: number = 30): Promise<string[]> {\n const existing = new Set(await listJournalDates());\n const gaps: string[] = [];\n\n const now = new Date();\n for (let i = 0; i < days; i++) {\n const d = new Date(now);\n d.setDate(d.getDate() - i);\n const dateStr = d.toISOString().split(\"T\")[0]!;\n if (!existing.has(dateStr)) {\n gaps.push(dateStr);\n }\n }\n\n return gaps;\n}\n\n/** Append a digest section to a journal file */\nexport async function appendDigest(\n digest: string,\n date?: string,\n): Promise<string> {\n const targetDate = date ?? getTodayDate();\n const filePath = getJournalPath(targetDate);\n\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch {\n // Create the file if it doesn't exist\n content = `# ${targetDate}\\n\\n`;\n }\n\n // Remove existing digest section if present (replace)\n const digestIndex = content.indexOf(\"\\n## Digest\");\n if (digestIndex !== -1) {\n content = content.slice(0, digestIndex);\n }\n\n const digestSection = `\\n## Digest\\n\\n${digest.trim()}\\n`;\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, content.trimEnd() + \"\\n\" + digestSection, \"utf-8\");\n\n return filePath;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { listPending } from \"../raw/index.js\";\nimport { extractPinData } from \"./extract.js\";\nimport { appendPinData } from \"../vault/index.js\";\nimport { readJournal } from \"../memory/index.js\";\nimport { getPaiHome, getTodayDate } from \"../config/paths.js\";\nimport { parseFrontmatter, updateRawFrontmatter } from \"../utils/frontmatter.js\";\nimport * as console from \"../utils/console.js\";\nimport type { RawFrontmatter, ExtractPipelineResult } from \"../types.js\";\n\nexport type { ExtractPipelineResult };\n\n/**\n * Run the PINData extract pipeline on pending raw files + unprocessed journals.\n * Flow: raw/journal content → 1 LLM extract call → PINData[] → code-append to vault\n */\nexport async function distillPipeline(\n options: { dryRun?: boolean; singleFile?: string; today?: boolean } = {},\n): Promise<ExtractPipelineResult> {\n const result: ExtractPipelineResult = {\n processed: 0,\n extracted: 0,\n discarded: 0,\n errors: [],\n };\n\n // Collect inputs: pending raw files + today's journal\n const inputs: Array<{ label: string; content: string; rawPath?: string; source: string }> = [];\n\n // 1) Pending raw files\n if (options.singleFile) {\n const raw = await fs.readFile(options.singleFile, \"utf-8\");\n const { content } = parseFrontmatter<RawFrontmatter>(raw);\n inputs.push({\n label: path.basename(options.singleFile),\n content,\n rawPath: options.singleFile,\n source: options.singleFile,\n });\n } else if (!options.today) {\n const pendingFiles = await listPending();\n for (const filePath of pendingFiles) {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { content } = parseFrontmatter<RawFrontmatter>(raw);\n inputs.push({\n label: path.basename(filePath),\n content,\n rawPath: filePath,\n source: filePath,\n });\n }\n }\n\n // 2) Today's journal (if --today or default run)\n if (!options.singleFile) {\n const todayDate = getTodayDate();\n const journal = await readJournal(todayDate);\n if (journal && journal.trim().length > 0) {\n // Only include if not already processed (check for marker)\n if (!journal.includes(\"<!-- extracted -->\")) {\n inputs.push({\n label: `journal/${todayDate}`,\n content: journal,\n source: `memory/${todayDate}.md`,\n });\n }\n }\n }\n\n if (inputs.length === 0) {\n console.info(\"No pending raw files or journal entries to process.\");\n return result;\n }\n\n console.info(`Found ${inputs.length} input(s) to process.`);\n\n for (const input of inputs) {\n const spin = console.spinner(`Extracting ${input.label}...`);\n\n try {\n // Single LLM call: extract PINData entries\n const extracted = await extractPinData(input.content, input.source);\n result.processed++;\n\n if (extracted.entries.length === 0) {\n spin.succeed(`Skipped: ${input.label} — ${extracted.summary}`);\n result.discarded++;\n\n // Mark raw file as discarded\n if (input.rawPath && !options.dryRun) {\n const rawContent = await fs.readFile(input.rawPath, \"utf-8\");\n const updated = updateRawFrontmatter(rawContent, { status: \"discarded\" });\n await fs.writeFile(input.rawPath, updated, \"utf-8\");\n }\n continue;\n }\n\n if (options.dryRun) {\n const topics = [...new Set(extracted.entries.map((e) => e.topic))];\n spin.succeed(\n `[DRY RUN] ${input.label} → ${extracted.entries.length} entries → ${topics.join(\", \")}`,\n );\n result.extracted += extracted.entries.length;\n continue;\n }\n\n // Code-controlled append to vault files (no LLM merge needed)\n const date = new Date().toISOString().split(\"T\")[0]!;\n const { added, updated } = await appendPinData(extracted.entries, {\n date,\n ref: input.source,\n });\n\n result.extracted += extracted.entries.length;\n const topics = [...new Set(extracted.entries.map((e) => e.topic))];\n\n // Mark raw file as processed\n if (input.rawPath) {\n const rawContent = await fs.readFile(input.rawPath, \"utf-8\");\n const updatedRaw = updateRawFrontmatter(rawContent, {\n status: \"processed\",\n distilled_to: topics.join(\", \"),\n });\n await fs.writeFile(input.rawPath, updatedRaw, \"utf-8\");\n }\n\n // Mark journal as extracted\n if (!input.rawPath && input.source.startsWith(\"memory/\")) {\n const journalPath = path.join(getPaiHome(), input.source);\n try {\n const journalContent = await fs.readFile(journalPath, \"utf-8\");\n if (!journalContent.includes(\"<!-- extracted -->\")) {\n await fs.appendFile(journalPath, \"\\n<!-- extracted -->\\n\", \"utf-8\");\n }\n } catch {\n // Journal file might not exist\n }\n }\n\n spin.succeed(\n `Extracted ${input.label} → ${added} new, ${updated} updated → ${topics.join(\", \")}`,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spin.fail(`Error processing ${input.label}: ${msg}`);\n result.errors.push(`${input.label}: ${msg}`);\n }\n }\n\n return result;\n}\n","import type { Command } from \"commander\";\nimport { distillPipeline } from \"../distill/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerDistillCommand(program: Command): void {\n program\n .command(\"distill\")\n .description(\"Extract PINData from pending raw files and journal into vault\")\n .option(\"--file <path>\", \"Process a single raw file\")\n .option(\"--today\", \"Only process today's journal\")\n .option(\"--dry-run\", \"Preview what would be extracted without writing\")\n .action(async (opts: { file?: string; today?: boolean; dryRun?: boolean }) => {\n try {\n const result = await distillPipeline({\n singleFile: opts.file,\n today: opts.today,\n dryRun: opts.dryRun,\n });\n\n console.log(\"\");\n console.log(console.bold(\"Distill summary:\"));\n console.log(` Processed: ${result.processed}`);\n console.log(` Extracted: ${result.extracted} PINData entries`);\n console.log(` Discarded: ${result.discarded}`);\n\n if (result.errors.length > 0) {\n console.log(` Errors: ${result.errors.length}`);\n for (const e of result.errors) {\n console.error(` ${e}`);\n }\n }\n console.log(\"\");\n\n if (result.extracted > 0 && !opts.dryRun) {\n console.info(\n 'Vault updated with PINData entries. Run \"pai generate\" to update SKILL.md profiles.',\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Distill failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","/** Build system prompt for SKILL.md generation */\nexport function generateSystemPrompt(): string {\n return `You generate a SKILL.md — a minimal, high-density context file for an AI agent to understand the user.\nEvery line MUST be useful to an AI agent. If a line wouldn't change how an agent responds, delete it.\n\nCOMPRESSION RULES:\n- Identity: pack into 2-3 lines max (name | email | role | languages | timezone — merge onto fewer lines)\n- Preferences: write as \"prefers X for Y\" not just \"uses X\". State the WHY or CONTEXT.\n GOOD: \"Deploys on Vercel (team pinai), manages tasks in Airtable, meetings via Zoom (paid)\"\n BAD: \"Tools: Vercel, Zoom, Docker, npm, Airtable\" (just a list, no context)\n- Interests: compress bookmarks/domains/events into TAGS, not raw lists\n GOOD: \"Interests: AI/LLM, Web3 (ETHDenver 2026), GIS, iOS, prediction markets (Kalshi)\"\n BAD: \"Bookmarks: Favorites Bar 106项, Read 78项, Tools 67项...\" (raw data dump)\n- Current Work: what are they ACTIVELY building? Project names + what they do, not directory paths\n GOOD: \"Building personal-ai (v0.1.0, npm published), PINAI ecosystem (PIN-APP-IOS, pin-sandman on Vercel)\"\n BAD: \"Recent Working Directories: ..: 8次, backend: 2次\"\n- Lessons: ONLY copy verbatim lessons that exist in vault/coding/ or vault/work/ files.\n NEVER INVENT OR PARAPHRASE lessons. If the vault has no explicit lesson, write \"None recorded yet.\"\n A deployment failure is a FACT (goes in Current Work), NOT a lesson.\n GOOD: \"FastAPI CORS: must explicitly list allowed_origins, wildcard doesn't work with credentials\"\n BAD: \"生产环境部署失败可能导致项目延误\" ← THIS IS INVENTED, NEVER DO THIS\n BAD: \"及时关注部署通知有助于快速定位问题\" ← THIS IS GENERIC ADVICE, NEVER DO THIS\n- Financial/services: compress into 1-2 lines\n GOOD: \"Paid services: Zoom (acct 5144380543), BytePlus (acct 3000767749, ~200 USD credits)\"\n- Team: mention key collaborators briefly\n GOOD: \"Works with Ethan Liu (pinai team) on Prediction project via Airtable\"\n\nANTI-PATTERNS — never do these:\n- Raw data dumps (bookmark counts, domain visit counts, directory lists)\n- Generic advice disguised as lessons\n- Separate lines for each identity field (merge them)\n- Lists of tool names without context on how/why they're used\n- \"Account Activities\" as a separate section (fold into Preferences or Current Work)`;\n}\n\n/** Build user prompt for SKILL.md generation */\nexport function generateUserPrompt(\n profileName: string,\n vaultContents: string,\n maxLines: number,\n): string {\n return `Generate SKILL.md \"${profileName}\" from vault content. Max ${maxLines} lines.\n\nSections:\n# Personal Context — ${profileName}\n## Who I Am (2-4 lines: identity, languages, timezone — packed dense)\n## Preferences (tool choices WITH context, workflow habits, communication style)\n## Interests (compressed tags from bookmarks/domains/events, NOT raw lists)\n## Current Work (active projects with names, what they do, team members)\n## Paid Services (services with account IDs, amounts — 1-3 lines)\n## Hard-won Lessons (ONLY real lessons from vault, or 1 line \"None yet\")\n\n---VAULT CONTENT---\n${vaultContents}\n---END VAULT CONTENT---\n\nCRITICAL: Compress, don't dump. Every line must be useful to an AI agent.\n- Merge identity fields onto fewer lines (name | email | timezone on one line)\n- Write preferences as \"prefers X for Y\" not bare lists\n- Convert bookmark counts and domain visits into interest TAGS\n- Never write generic advice as lessons\n- Never write raw data (dir paths, bookmark counts, visit numbers)`;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport { loadProfiles } from \"../config/io.js\";\nimport { getVaultDir, getSkillsDir } from \"../config/paths.js\";\nimport { llmCall } from \"../llm/index.js\";\nimport {\n generateSystemPrompt,\n generateUserPrompt,\n} from \"../prompts/generate.js\";\nimport * as console from \"../utils/console.js\";\n\n/** Recursively list all markdown files in a directory */\nasync function walkMd(dir: string): Promise<string[]> {\n const results: string[] = [];\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await walkMd(full)));\n } else if (entry.name.endsWith(\".md\")) {\n results.push(full);\n }\n }\n } catch {\n // Ignore errors\n }\n return results;\n}\n\n/** Collect vault file contents matching scope patterns */\nasync function collectVaultContent(scope: string[]): Promise<string> {\n const vaultDir = getVaultDir();\n const allFiles = await walkMd(vaultDir);\n const matched: string[] = [];\n\n for (const file of allFiles) {\n const relative = \"vault/\" + path.relative(vaultDir, file);\n // Check if this file matches any scope pattern\n for (const pattern of scope) {\n if (minimatch(relative, pattern)) {\n matched.push(file);\n break;\n }\n }\n }\n\n // Read and concatenate all matched files\n const contents: string[] = [];\n for (const file of matched) {\n try {\n const content = await fs.readFile(file, \"utf-8\");\n contents.push(`--- ${path.relative(vaultDir, file)} ---\\n${content}`);\n } catch {\n // Skip unreadable files\n }\n }\n\n return contents.join(\"\\n\\n\");\n}\n\n/** Generate a single SKILL.md profile */\nexport async function generateProfile(profileName: string): Promise<string> {\n const profiles = await loadProfiles();\n const profileDef = profiles.profiles[profileName];\n\n if (!profileDef) {\n throw new Error(`Profile \"${profileName}\" not found in profiles.json5`);\n }\n\n const vaultContent = await collectVaultContent(profileDef.scope);\n\n if (!vaultContent.trim()) {\n throw new Error(\n `No vault content found for profile \"${profileName}\". Run 'pai distill' first.`,\n );\n }\n\n const system = generateSystemPrompt();\n const prompt = generateUserPrompt(\n profileName,\n vaultContent,\n profileDef.maxLines,\n );\n\n const skillMd = await llmCall(prompt, system);\n\n // Write to skills/profiles/\n const skillsDir = getSkillsDir();\n await fs.mkdir(skillsDir, { recursive: true });\n const outPath = path.join(skillsDir, `${profileName}.md`);\n await fs.writeFile(outPath, skillMd.trim() + \"\\n\", \"utf-8\");\n\n return outPath;\n}\n\n/** Generate all profiles defined in profiles.json5 */\nexport async function generateAll(): Promise<string[]> {\n const profiles = await loadProfiles();\n const profileNames = Object.keys(profiles.profiles);\n\n if (profileNames.length === 0) {\n console.warn(\"No profiles defined in profiles.json5\");\n return [];\n }\n\n const results: string[] = [];\n for (const name of profileNames) {\n const spin = console.spinner(`Generating profile: ${name}...`);\n try {\n const outPath = await generateProfile(name);\n spin.succeed(`Generated ${name} → ${outPath}`);\n results.push(outPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n spin.fail(`Failed to generate ${name}: ${msg}`);\n }\n }\n\n return results;\n}\n","import type { Command } from \"commander\";\nimport { generateProfile, generateAll } from \"../generate/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerGenerateCommand(program: Command): void {\n program\n .command(\"generate\")\n .description(\"Generate SKILL.md profiles from vault content\")\n .option(\"--profile <name>\", \"Generate a specific profile only\")\n .action(async (opts: { profile?: string }) => {\n try {\n if (opts.profile) {\n const spin = console.spinner(\n `Generating profile: ${opts.profile}...`,\n );\n try {\n const outPath = await generateProfile(opts.profile);\n spin.succeed(`Generated: ${outPath}`);\n } catch (err) {\n spin.fail(\"Generation failed\");\n throw err;\n }\n } else {\n const results = await generateAll();\n if (results.length > 0) {\n console.log(\"\");\n console.success(\n `Generated ${results.length} SKILL.md profile(s).`,\n );\n console.info(\n \"These files can be used by AI agents (Cursor, Claude, etc.).\",\n );\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Generate failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import { execFile } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { execQmd, isQmdAvailable } from \"../utils/process.js\";\nimport { getVaultDir, getRawDir } from \"../config/paths.js\";\nimport type { SearchResult } from \"../types.js\";\n\nexport type SearchMode = \"query\" | \"search\" | \"vsearch\";\n\n/**\n * Search with automatic fallback:\n * 1. QMD available → use QMD (query/search/vsearch modes)\n * 2. QMD not available → built-in grep search (keyword matching, no install needed)\n *\n * QMD modes:\n * - \"query\" (default): BM25 + vector + expansion + reranking (~5s, best quality)\n * - \"search\": BM25 keyword (~50ms, fast)\n * - \"vsearch\": Vector similarity only (~2s, semantic)\n *\n * For \"search\" (fast) mode, QMD BM25 doesn't support CJK tokenization,\n * so we supplement with grep to cover Chinese/Japanese/Korean.\n */\nexport async function search(\n query: string,\n collection: string = \"vault\",\n n: number = 5,\n mode: SearchMode = \"query\",\n): Promise<SearchResult[]> {\n // Fallback to built-in search when QMD is not available\n if (!(await isQmdAvailable())) {\n return builtinSearch(query, collection, n);\n }\n\n const stdout = await execQmd([\n mode,\n query,\n \"--collection\",\n collection,\n \"--json\",\n \"-n\",\n String(n),\n ]);\n\n let results = parseSearchResults(stdout);\n\n // BM25 can't tokenize CJK — supplement with grep for fast mode\n if (mode === \"search\" && results.length === 0 && hasCjk(query)) {\n results = await grepFallback(query, collection, n);\n }\n\n return results;\n}\n\n/**\n * Built-in keyword search — zero external dependencies.\n * Reads markdown files and does case-insensitive substring matching.\n * Good enough for small-medium collections (< 10k files).\n */\nasync function builtinSearch(\n query: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const baseDir = collection === \"raw\" ? getRawDir() : getVaultDir();\n\n // Try grep first (fast, available on all unix systems)\n const grepResults = await grepFallback(query, collection, n);\n if (grepResults.length > 0) return grepResults;\n\n // Final fallback: pure Node.js file scan (no external tools at all)\n return nodeFsSearch(query, baseDir, collection, n);\n}\n\n/**\n * Pure Node.js file search — absolute zero dependencies.\n * Recursively reads .md files and matches query as case-insensitive substring.\n */\nasync function nodeFsSearch(\n query: string,\n baseDir: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const results: SearchResult[] = [];\n const queryLower = query.toLowerCase();\n\n async function walk(dir: string): Promise<void> {\n let entries;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (results.length >= n) return;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(full);\n } else if (entry.name.endsWith(\".md\")) {\n try {\n const content = await fs.readFile(full, \"utf-8\");\n const idx = content.toLowerCase().indexOf(queryLower);\n if (idx !== -1) {\n const snippetStart = Math.max(0, idx - 40);\n const snippetEnd = Math.min(content.length, idx + query.length + 120);\n const snippet = content.slice(snippetStart, snippetEnd).replace(/\\n/g, \" \").trim();\n const titleMatch = content.match(/^#\\s+(.+)/m);\n const relative = path.relative(baseDir, full);\n results.push({\n file: `${collection}/${relative}`,\n title: titleMatch?.[1]?.trim() ?? path.basename(full, \".md\"),\n snippet,\n score: 1.0,\n });\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n }\n\n await walk(baseDir);\n return results;\n}\n\n/** Parse QMD JSON search output into SearchResult[] */\nfunction parseSearchResults(stdout: string): SearchResult[] {\n try {\n const parsed = JSON.parse(stdout);\n if (!Array.isArray(parsed)) return [];\n return parsed.map((item: Record<string, unknown>) => ({\n file: (item.file as string) ?? \"\",\n title: (item.title as string) ?? undefined,\n snippet:\n (item.snippet as string) ??\n (item.content as string)?.slice(0, 200) ??\n \"\",\n score: item.score as number | undefined,\n }));\n } catch {\n return [];\n }\n}\n\n/** Check if a string contains CJK characters */\nfunction hasCjk(text: string): boolean {\n return /[\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/.test(text);\n}\n\n/**\n * grep fallback for CJK text that BM25 can't tokenize.\n * Runs: grep -rl \"query\" <dir> --include=\"*.md\"\n * Then reads matched files and extracts context.\n */\nasync function grepFallback(\n query: string,\n collection: string,\n n: number,\n): Promise<SearchResult[]> {\n const baseDir = collection === \"raw\" ? getRawDir() : getVaultDir();\n\n // grep -rl: recursive, list filenames only\n const stdout = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-rl\", \"--include=*.md\", query, baseDir],\n { encoding: \"utf-8\", timeout: 5_000 },\n (_err, out) => {\n // grep exits 1 when no matches — that's fine\n resolve(out ?? \"\");\n },\n );\n });\n\n if (!stdout.trim()) return [];\n\n const files = stdout.trim().split(\"\\n\").slice(0, n);\n const results: SearchResult[] = [];\n\n for (const filePath of files) {\n if (!filePath) continue;\n // Get context line with grep -m1 (first match with surrounding text)\n const context = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-m1\", \"-C1\", query, filePath],\n { encoding: \"utf-8\", timeout: 2_000 },\n (_err, out) => resolve(out ?? \"\"),\n );\n });\n\n const snippet = context.replace(/\\n/g, \" \").trim().slice(0, 160);\n const relative = path.relative(baseDir, filePath);\n // Extract title: first H1 line via grep\n const titleLine = await new Promise<string>((resolve) => {\n execFile(\n \"grep\",\n [\"-m1\", \"^# \", filePath],\n { encoding: \"utf-8\", timeout: 1_000 },\n (_err, out) => resolve(out?.replace(/^#\\s+/, \"\").trim() ?? \"\"),\n );\n });\n\n results.push({\n file: `qmd://${collection}/${relative}`,\n title: titleLine || path.basename(filePath, \".md\"),\n snippet,\n score: 1.0,\n });\n }\n\n return results;\n}\n\n/** Update QMD index: runs `qmd update` + `qmd embed` */\nexport async function updateIndex(): Promise<void> {\n if (!(await isQmdAvailable())) {\n throw new Error(\n \"QMD is not installed. Built-in search works without indexing.\\n\" +\n \"For better search quality, install QMD: npm install -g https://github.com/tobi/qmd\",\n );\n }\n await execQmd([\"update\"]);\n await execQmd([\"embed\"]);\n}\n","import type { Command } from \"commander\";\nimport { search, type SearchMode } from \"../search/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerSearchCommand(program: Command): void {\n program\n .command(\"search <query>\")\n .description(\"Search vault (default) or raw via QMD\")\n .option(\"--raw\", \"Search raw collection instead of vault\")\n .option(\"--all\", \"Search all collections\")\n .option(\"--fast\", \"BM25 keyword search only (instant, no AI)\")\n .option(\"--vector\", \"Vector similarity search only\")\n .option(\"--json\", \"Output results as JSON (for agent consumption)\")\n .option(\"-n, --num <number>\", \"Number of results\", \"5\")\n .action(\n async (\n query: string,\n opts: {\n raw?: boolean;\n all?: boolean;\n fast?: boolean;\n vector?: boolean;\n json?: boolean;\n num: string;\n },\n ) => {\n try {\n const n = parseInt(opts.num, 10);\n const collection = opts.raw ? \"raw\" : opts.all ? undefined : \"vault\";\n const mode: SearchMode = opts.fast\n ? \"search\"\n : opts.vector\n ? \"vsearch\"\n : \"query\";\n\n if (opts.all) {\n const [vaultResults, rawResults] = await Promise.all([\n search(query, \"vault\", n, mode),\n search(query, \"raw\", n, mode),\n ]);\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify({ vault: vaultResults, raw: rawResults }, null, 2) + \"\\n\",\n );\n return;\n }\n\n console.log(\"\");\n console.log(console.bold(`Search results for: \"${query}\"`));\n\n if (vaultResults.length > 0) {\n console.log(\"\\n\" + console.bold(\"Vault:\"));\n for (const r of vaultResults) {\n printResult(r);\n }\n }\n\n if (rawResults.length > 0) {\n console.log(\"\\n\" + console.bold(\"Raw:\"));\n for (const r of rawResults) {\n printResult(r);\n }\n }\n\n if (vaultResults.length === 0 && rawResults.length === 0) {\n console.info(\"No results found.\");\n }\n } else {\n const results = await search(query, collection, n, mode);\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(results, null, 2) + \"\\n\");\n return;\n }\n\n console.log(\"\");\n console.log(\n console.bold(\n `Search results for: \"${query}\" (${collection ?? \"all\"})`,\n ),\n );\n\n if (results.length === 0) {\n console.info(\"No results found.\");\n } else {\n for (const r of results) {\n printResult(r);\n }\n }\n }\n console.log(\"\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Search failed: ${msg}`);\n process.exit(1);\n }\n },\n );\n}\n\nfunction printResult(r: { file: string; title?: string; snippet: string; score?: number }): void {\n const score = r.score != null ? console.dim(` (${r.score.toFixed(2)})`) : \"\";\n console.log(` ${console.bold(r.title ?? r.file)}${score}`);\n console.log(` ${console.dim(r.snippet.slice(0, 120))}`);\n console.log(` ${console.dim(r.file)}`);\n console.log(\"\");\n}\n","import type { Command } from \"commander\";\nimport { updateIndex } from \"../search/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerIndexCommand(program: Command): void {\n program\n .command(\"index\")\n .description(\"Update QMD index (qmd update + qmd embed)\")\n .action(async () => {\n const spin = console.spinner(\"Updating QMD index...\");\n try {\n await updateIndex();\n spin.succeed(\"QMD index updated successfully.\");\n } catch (err) {\n spin.fail(\"Index update failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getRawDir } from \"../config/paths.js\";\nimport { generateRawFilename } from \"../utils/slug.js\";\nimport { createRawFile } from \"../utils/frontmatter.js\";\nimport { addConnectorEntry } from \"../raw/add.js\";\nimport type { RawFrontmatter } from \"../types.js\";\nimport * as console from \"../utils/console.js\";\nimport { encryption } from \"../auth/encryption.js\";\nimport { googleOAuth } from \"../auth/google-oauth.js\";\n\nexport function registerImportCommand(program: Command): void {\n program\n .command(\"import\")\n .description(\"Import data from connector or directory (use --source mac for system scan)\")\n .requiredOption(\"--source <source>\", \"Data source (mac, gmail, calendar, twitter, etc.)\")\n .option(\"--path <dir>\", \"Path to data directory or file (not needed for mac, gmail, calendar)\")\n .option(\"--dry-run\", \"Preview what would be imported without writing\")\n .option(\"--days <n>\", \"For gmail/calendar: last N days to fetch (default 30)\", \"30\")\n .option(\"--query <q>\", \"For gmail: Gmail search query (e.g. is:important)\")\n .action(\n async (opts: {\n source: string;\n path?: string;\n dryRun?: boolean;\n days?: string;\n query?: string;\n }) => {\n try {\n // --- Mac connector: auto-discover, no --path needed ---\n if (opts.source === \"mac\") {\n const { scanMac } = await import(\"../connectors/mac/index.js\");\n const result = await scanMac({ dryRun: opts.dryRun });\n\n if (opts.dryRun) return;\n\n console.log(\"\");\n console.success(\n `Mac scan complete: ${result.created} created, ${result.updated} updated, ${result.skipped} unchanged`,\n );\n if (result.failed.length > 0) {\n console.warn(`${result.failed.length} failed: ${result.failed.join(\", \")}`);\n }\n console.info('Run \"pai distill\" to process scanned data.');\n return;\n }\n\n // --- Gmail connector ---\n if (opts.source === \"gmail\") {\n const { syncGmail } = await import(\"../connectors/google/gmail.js\");\n const parsedDays = parseInt(opts.days ?? \"30\", 10);\n const days = Number.isNaN(parsedDays) ? 30 : parsedDays;\n const doSync = () =>\n syncGmail({ days, query: opts.query, maxResults: 100 });\n await encryption.loadKey();\n await googleOAuth.init();\n let entries: Awaited<ReturnType<typeof syncGmail>>;\n const spinGmail =\n opts.dryRun ? null : console.spinner(\"Syncing Gmail...\");\n try {\n entries = await doSync();\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"Not authenticated\")\n ) {\n spinGmail?.stop();\n console.info(\"Not authenticated; starting Google OAuth...\");\n await googleOAuth.authorize();\n const spinRetry =\n opts.dryRun ? null : console.spinner(\"Syncing Gmail...\");\n entries = await doSync();\n spinRetry?.stop();\n } else {\n throw err;\n }\n } finally {\n spinGmail?.stop();\n }\n if (opts.dryRun) {\n console.info(`Would import ${entries.length} Gmail message(s).`);\n return;\n }\n let created = 0;\n let updated = 0;\n let skipped = 0;\n for (const entry of entries) {\n const status = await addConnectorEntry(\"gmail\", entry);\n if (status === \"created\") created++;\n else if (status === \"updated\") updated++;\n else skipped++;\n }\n console.success(\n `Gmail import: ${created} created, ${updated} updated, ${skipped} unchanged`,\n );\n console.info('Run \"pai distill\" to process imported data.');\n return;\n }\n\n // --- Calendar connector ---\n if (opts.source === \"calendar\") {\n const { syncCalendar } = await import(\"../connectors/google/calendar.js\");\n const parsedDays = parseInt(opts.days ?? \"30\", 10);\n const days = Number.isNaN(parsedDays) ? 30 : parsedDays;\n const doSync = () =>\n syncCalendar({ lookbackDays: days, lookforwardDays: 90 });\n await encryption.loadKey();\n await googleOAuth.init();\n let entries: Awaited<ReturnType<typeof syncCalendar>>;\n const spinCal =\n opts.dryRun ? null : console.spinner(\"Syncing Calendar...\");\n try {\n entries = await doSync();\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"Not authenticated\")\n ) {\n spinCal?.stop();\n console.info(\"Not authenticated; starting Google OAuth...\");\n await googleOAuth.authorize();\n const spinRetry =\n opts.dryRun ? null : console.spinner(\"Syncing Calendar...\");\n entries = await doSync();\n spinRetry?.stop();\n } else {\n throw err;\n }\n } finally {\n spinCal?.stop();\n }\n if (opts.dryRun) {\n console.info(`Would import ${entries.length} calendar event(s).`);\n return;\n }\n let created = 0;\n let updated = 0;\n let skipped = 0;\n for (const entry of entries) {\n const status = await addConnectorEntry(\"calendar\", entry);\n if (status === \"created\") created++;\n else if (status === \"updated\") updated++;\n else skipped++;\n }\n console.success(\n `Calendar import: ${created} created, ${updated} updated, ${skipped} unchanged`,\n );\n console.info('Run \"pai distill\" to process imported data.');\n return;\n }\n\n // --- Path-based import (existing behavior) ---\n if (!opts.path) {\n console.error(\n \"--path is required for this source. Usage: pai import --source <name> --path <dir>\",\n );\n process.exit(1);\n }\n\n const sourcePath = opts.path;\n const source = `connector/${opts.source}`;\n const stat = await fs.stat(sourcePath);\n\n const outDir = path.join(getRawDir(), \"connector\", opts.source);\n await fs.mkdir(outDir, { recursive: true });\n\n let imported = 0;\n\n if (stat.isDirectory()) {\n // Import all markdown/text files from directory\n const entries = await fs.readdir(sourcePath, { withFileTypes: true });\n for (const entry of entries) {\n if (\n entry.isFile() &&\n (entry.name.endsWith(\".md\") ||\n entry.name.endsWith(\".txt\") ||\n entry.name.endsWith(\".json\"))\n ) {\n const content = await fs.readFile(\n path.join(sourcePath, entry.name),\n \"utf-8\",\n );\n const title = path.basename(entry.name, path.extname(entry.name));\n const filename = generateRawFilename(title);\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n await fs.writeFile(\n path.join(outDir, filename),\n fileContent,\n \"utf-8\",\n );\n imported++;\n }\n }\n } else {\n // Import single file\n const content = await fs.readFile(sourcePath, \"utf-8\");\n const title = path.basename(sourcePath, path.extname(sourcePath));\n const filename = generateRawFilename(title);\n\n const fm: RawFrontmatter = {\n source,\n timestamp: new Date().toISOString(),\n status: \"pending\",\n };\n\n const fileContent = createRawFile(fm, title, content);\n await fs.writeFile(\n path.join(outDir, filename),\n fileContent,\n \"utf-8\",\n );\n imported = 1;\n }\n\n console.success(`Imported ${imported} file(s) from ${opts.source}`);\n console.info('Run \"pai distill\" to process imported data.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Import failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getVaultDir, getSkillsDir } from \"../config/paths.js\";\nimport { listPending, listAll } from \"../raw/index.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Show pai data status overview\")\n .option(\"--json\", \"Output as JSON (for agent consumption)\")\n .action(async (opts: { json?: boolean }) => {\n try {\n // Count raw files\n const allRaw = await listAll();\n const pendingRaw = await listPending();\n\n // Count vault files\n const vaultFiles = await countFiles(getVaultDir());\n\n // Count skill profiles\n const skillFiles = await countFiles(getSkillsDir());\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n raw: { total: allRaw.length, pending: pendingRaw.length },\n vault: { files: vaultFiles },\n profiles: skillFiles,\n },\n null,\n 2,\n ) + \"\\n\",\n );\n return;\n }\n\n console.log(\"\");\n console.log(console.bold(\"pai status\"));\n console.log(\"─\".repeat(40));\n console.log(` Raw files: ${allRaw.length} total, ${pendingRaw.length} pending`);\n console.log(` Vault files: ${vaultFiles}`);\n console.log(` SKILL profiles: ${skillFiles}`);\n console.log(\"\");\n\n if (pendingRaw.length > 0) {\n console.info(\n `${pendingRaw.length} file(s) waiting to be distilled. Run \"pai distill\" to process.`,\n );\n }\n\n if (vaultFiles === 0) {\n console.info(\n 'No vault content yet. Run \"pai add <text>\" then \"pai distill\".',\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Status check failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\nasync function countFiles(dir: string): Promise<number> {\n let count = 0;\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n count += await countFiles(full);\n } else if (entry.name.endsWith(\".md\")) {\n count++;\n }\n }\n } catch {\n // Directory doesn't exist\n }\n return count;\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport readline from \"node:readline\";\nimport { getPaiHome } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nfunction askConfirm(question: string): Promise<boolean> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(/^y|yes$/i.test(answer.trim()));\n });\n });\n}\n\nexport function registerResetCommand(program: Command): void {\n program\n .command(\"reset\")\n .description(\"Clear all pai data (remove ~/.pai directory)\")\n .option(\"--force\", \"Skip confirmation prompt\")\n .action(async (opts: { force?: boolean }) => {\n const paiHome = getPaiHome();\n\n if (!opts.force) {\n const confirmed = await askConfirm(\n `Remove ALL data at ${paiHome}? [y/N] `,\n );\n if (!confirmed) {\n console.info(\"Reset cancelled.\");\n return;\n }\n }\n\n const spin = console.spinner(`Clearing pai data at ${paiHome}...`);\n try {\n await fs.rm(paiHome, { recursive: true, force: true });\n spin.succeed(`Cleared ${paiHome}`);\n console.info(\"Run \\\"pai init\\\" to set up again.\");\n } catch (err) {\n spin.fail(\"Reset failed\");\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n }\n });\n}\n","import os from \"node:os\";\nimport { getPaiHome } from \"../config/paths.js\";\n\n/**\n * System prompt for the ask agent (personal AI secretary).\n * Built in sections, similar to clawdbot system-prompt.ts.\n */\nexport function buildAskSystemPrompt(): string {\n const sections: string[] = [];\n const paiHome = getPaiHome();\n const homeDir = os.homedir();\n\n sections.push(\n `You are a personal AI secretary who knows the user intimately.\nYour job: answer questions about the user accurately, specifically, and honestly.\nToday's date: ${new Date().toISOString().split(\"T\")[0]}\nUser home: ${homeDir}\nPAI data directory: ${paiHome}`,\n );\n\n sections.push(`## Data Layout\n${paiHome}/\n├── profile.md # Compiled user profile (identity, env, projects)\n├── raw/ # Original immutable data with timestamps\n│ ├── local/ # Manual input\n│ ├── web/ # URL scrapes\n│ └── connector/ # Imported data from connectors (dynamic — use queryConnector to discover)\n├── vault/ # Distilled knowledge (experiences, preferences, lessons)\n│ ├── coding/\n│ ├── context/\n│ ├── life/\n│ └── preferences/\n└── config/ # Configuration files`);\n\n sections.push(`## Tool Strategy\n\n### Knowledge tools (use first — fast, structured)\n- readProfile: Read compiled user profile — fastest way to understand who the user is\n- searchVault: Search distilled knowledge. Ranked by RELEVANCE, not recency.\n- searchRaw: Search original raw data. Has timestamps in frontmatter. Ranked by RELEVANCE, not recency.\n\n### Connector data tool (for time/fact-based questions — calendar, email, etc.)\n- queryConnector: Query imported data from ANY connector source.\n - Call WITHOUT source → discovers available sources (calendar, gmail, mac, or any future connector)\n - Call WITH source + range → returns entries sorted by date, with structured fields\n - Supports range: upcoming/today/this_week/past_week/past_month/all\n - ALWAYS use this for schedule, email, or any time-sensitive imported data questions\n - Examples: \"下一个会议\" → queryConnector(source=\"calendar\", range=\"upcoming\", limit=3)\n - Examples: \"最近邮件\" → queryConnector(source=\"gmail\", range=\"past_week\", sort=\"desc\")\n\n### Filesystem tools (use to go deeper)\n- readFile: Read full content of any file. ALWAYS use this after search finds a relevant file — search snippets are too short to answer well.\n- grep: Search file contents with regex (ripgrep)\n- glob: Find files by pattern\n- ls: List directory contents\n- bash: Execute shell commands\n\n### CRITICAL: Always read files after searching\nSearch tools return short snippets. When you find relevant results:\n1. Use readFile to read the full content of the top results\n2. Extract specific details (names, dates, numbers) from the full text\n3. NEVER answer based on search snippets alone — they are incomplete\n\n### Recommended flow\n1. For calendar/email/time-sensitive data → use queryConnector FIRST (time-aware, structured)\n2. readProfile → understand who the user is\n3. searchVault/searchRaw → find relevant topics\n4. readFile → read full content of promising results\n5. grep/glob/bash → dig deeper if needed\n\n### Time-sensitive queries\nsearchVault and searchRaw rank by relevance, NOT by recency.\nWhen the user asks about schedules, recent events, emails, or activity:\n- Use queryConnector — it parses dates from content and supports time range filtering\n- For system state: use bash (e.g. \\`git -C <project_dir> log --oneline -20 --since=\"1 week ago\"\\`)\n- Do NOT rely solely on search for time-based questions — search has no time ranking`);\n\n sections.push(`## Rules\n- ONLY answer based on data from tools — NEVER fabricate information\n- If you cannot find the answer after thorough searching, say so honestly\n- Be specific: use real names, versions, dates, numbers from the data\n- Be concise but substantive: answer like a knowledgeable personal secretary\n- NEVER give empty or placeholder answers — if data is sparse, say what you found and what's missing\n- When citing sources, prefer qmd:// paths from vault/raw searches`);\n\n return sections.join(\"\\n\\n\");\n}\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { execFile } from \"node:child_process\";\nimport type { Dirent } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst BASH_TIMEOUT_MS = 30_000;\nconst BASH_OUTPUT_MAX_BYTES = 10 * 1024;\n\nfunction resolvePath(raw: string): string {\n const trimmed = raw.trim();\n if (trimmed.startsWith(\"~\")) {\n return path.join(os.homedir(), trimmed.slice(1).replace(/^\\//, \"\"));\n }\n return path.resolve(trimmed);\n}\n\n// ---------- readFile ----------\n\nconst ReadFileParams = Type.Object({\n path: Type.String({ description: \"Absolute or relative file path\" }),\n offset: Type.Optional(Type.Number({ description: \"Start line (1-based)\" })),\n limit: Type.Optional(\n Type.Number({ description: \"Number of lines to read\" }),\n ),\n});\n\nexport const readFileTool: AgentTool<typeof ReadFileParams> = {\n name: \"readFile\",\n label: \"Read File\",\n description:\n \"Read file contents. Supports offset/limit for large files. Path can be absolute or relative; ~ expands to home directory.\",\n parameters: ReadFileParams,\n execute: async (_toolCallId, params) => {\n const resolved = resolvePath(params.path);\n try {\n const raw = await fs.readFile(resolved, \"utf-8\");\n const lines = raw.split(\"\\n\");\n const start = Math.min(\n lines.length,\n params.offset != null ? Math.max(0, params.offset - 1) : 0,\n );\n const end =\n params.limit != null\n ? Math.min(lines.length, start + params.limit)\n : lines.length;\n const slice = lines.slice(start, end).join(\"\\n\");\n return {\n content: [{ type: \"text\", text: slice }],\n details: {\n path: resolved,\n lines: end - start,\n totalLines: lines.length,\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg, path: resolved },\n };\n }\n },\n};\n\n// ---------- grep ----------\n\nconst GrepParams = Type.Object({\n pattern: Type.String({ description: \"Regex pattern to search\" }),\n path: Type.Optional(\n Type.String({ description: \"File or directory to search in\" }),\n ),\n glob: Type.Optional(\n Type.String({ description: \"File glob filter, e.g. '*.ts'\" }),\n ),\n maxResults: Type.Optional(\n Type.Number({\n description: \"Max matching lines to return (default 20)\",\n }),\n ),\n});\n\nexport const grepTool: AgentTool<typeof GrepParams> = {\n name: \"grep\",\n label: \"Grep\",\n description:\n \"Search file contents using ripgrep (rg). Returns matching lines with context.\",\n parameters: GrepParams,\n execute: async (_toolCallId, params) => {\n const maxResults = params.maxResults ?? 20;\n const args = [\"-n\", \"-m\", String(maxResults), params.pattern];\n if (params.path) {\n args.push(resolvePath(params.path));\n }\n if (params.glob) {\n args.push(\"--glob\", params.glob);\n }\n return new Promise((resolve) => {\n execFile(\n \"rg\",\n args,\n { encoding: \"utf-8\", timeout: 10_000, maxBuffer: 512 * 1024 },\n (err, stdout, stderr) => {\n if (err) {\n if ((err as NodeJS.ErrnoException).code === \"1\" || err.code === 1) {\n resolve({\n content: [{ type: \"text\", text: \"No matches found.\" }],\n details: { matches: [], count: 0 },\n });\n return;\n }\n resolve({\n content: [\n {\n type: \"text\",\n text: `Error: ${stderr?.trim() || err.message}`,\n },\n ],\n details: { error: stderr?.trim() || err.message },\n });\n return;\n }\n const lines = stdout.trim().split(\"\\n\").slice(0, maxResults);\n resolve({\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { matches: lines, count: lines.length },\n });\n },\n );\n });\n },\n};\n\n// ---------- glob ----------\n\nasync function globWalk(\n dir: string,\n pattern: string,\n results: string[],\n baseDir: string,\n): Promise<void> {\n const { minimatch } = await import(\"minimatch\");\n const mm = new minimatch.Minimatch(pattern, { dot: true });\n let entries: Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const full = path.join(dir, e.name);\n const relative = path.relative(baseDir, full);\n if (e.isDirectory()) {\n await globWalk(full, pattern, results, baseDir);\n } else if (mm.match(relative) || mm.match(e.name)) {\n results.push(full);\n }\n }\n}\n\nconst GlobParams = Type.Object({\n pattern: Type.String({ description: \"Glob pattern, e.g. '**/*.md'\" }),\n cwd: Type.Optional(\n Type.String({ description: \"Base directory (default: current)\" }),\n ),\n});\n\nexport const globTool: AgentTool<typeof GlobParams> = {\n name: \"glob\",\n label: \"Find Files\",\n description:\n \"Find files matching a glob pattern. Returns file paths. Pattern e.g. '**/*.md' or '*.ts'.\",\n parameters: GlobParams,\n execute: async (_toolCallId, params) => {\n const baseDir = params.cwd ? resolvePath(params.cwd) : process.cwd();\n const results: string[] = [];\n await globWalk(baseDir, params.pattern, results, baseDir);\n const limited = results.slice(0, 200);\n return {\n content: [\n {\n type: \"text\",\n text:\n limited.length > 0\n ? limited.join(\"\\n\")\n : \"No files matched the pattern.\",\n },\n ],\n details: { paths: limited, count: results.length },\n };\n },\n};\n\n// ---------- ls ----------\n\nconst LsParams = Type.Object({\n path: Type.String({ description: \"Directory path\" }),\n});\n\nexport const lsTool: AgentTool<typeof LsParams> = {\n name: \"ls\",\n label: \"List Directory\",\n description:\n \"List directory contents with file types and sizes. Path can be absolute or ~/...\",\n parameters: LsParams,\n execute: async (_toolCallId, params) => {\n const resolved = resolvePath(params.path);\n try {\n const entries = await fs.readdir(resolved, { withFileTypes: true });\n const items: { name: string; type: string; size?: number }[] = [];\n for (const e of entries) {\n const item: { name: string; type: string; size?: number } = {\n name: e.name,\n type: e.isDirectory() ? \"dir\" : \"file\",\n };\n if (e.isFile()) {\n try {\n const stat = await fs.stat(path.join(resolved, e.name));\n item.size = stat.size;\n } catch {\n // ignore\n }\n }\n items.push(item);\n }\n const lines = items.map(\n (i) =>\n `${i.type === \"dir\" ? \"📁\" : \"📄\"} ${i.name}${i.size != null ? ` (${i.size}B)` : \"\"}`,\n );\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n details: { path: resolved, entries: items },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg, path: resolved },\n };\n }\n },\n};\n\n// ---------- bash ----------\n\nconst BashParams = Type.Object({\n command: Type.String({ description: \"Shell command to execute\" }),\n cwd: Type.Optional(Type.String({ description: \"Working directory\" })),\n});\n\nexport const bashTool: AgentTool<typeof BashParams> = {\n name: \"bash\",\n label: \"Shell\",\n description:\n \"Execute a shell command. Use for checking system state, running pai commands, etc. Timeout: 30s. Output truncated to 10KB.\",\n parameters: BashParams,\n execute: async (_toolCallId, params) => {\n const workDir = params.cwd ? resolvePath(params.cwd) : process.cwd();\n return new Promise((resolve) => {\n execFile(\n \"bash\",\n [\"-c\", params.command],\n {\n encoding: \"utf-8\",\n timeout: BASH_TIMEOUT_MS,\n maxBuffer: BASH_OUTPUT_MAX_BYTES,\n cwd: workDir,\n },\n (err, stdout, stderr) => {\n if (err) {\n const out = [\n stdout ? `stdout: ${stdout.slice(0, 2000)}` : \"\",\n stderr ? `stderr: ${stderr.slice(0, 2000)}` : \"\",\n `error: ${err.message}`,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n resolve({\n content: [{ type: \"text\", text: out }],\n details: {\n exitCode: err.code ?? -1,\n error: err.message,\n },\n });\n return;\n }\n const output = (stdout ?? \"\").slice(0, BASH_OUTPUT_MAX_BYTES);\n const stderrTrimmed = (stderr ?? \"\").trim();\n const text = stderrTrimmed\n ? `${output}\\nstderr: ${stderrTrimmed}`\n : output;\n resolve({\n content: [{ type: \"text\", text }],\n details: { exitCode: 0 },\n });\n },\n );\n });\n },\n};\n","/**\n * Generic connector data query tool for the ask agent.\n * Dynamically discovers available data sources under ~/.pai/raw/connector/\n * and provides unified time-sorted access to any connector's data.\n *\n * No hardcoded connector types — works with calendar, gmail, mac, or any future source.\n */\n\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPaiHome } from \"../config/paths.js\";\n\n// ---------- Generic date extraction ----------\n\n/**\n * Try to extract a meaningful content date from markdown body.\n * Checks common field patterns across connector types:\n * **Time**: ... (calendar events)\n * **Date**: ... (emails, generic)\n * **Created**: ...\n * Falls back to frontmatter timestamp (import time).\n */\nfunction extractContentDate(\n content: string,\n frontmatter: Record<string, unknown>,\n): Date | null {\n // Try content fields in priority order\n const patterns = [\n /\\*\\*Time\\*\\*:\\s*(\\S+)/, // calendar: \"**Time**: 2026-02-12T09:00:00+08:00 (tz)\"\n /\\*\\*Date\\*\\*:\\s*(.+)/, // gmail: \"**Date**: Sat, 31 Jan 2026 03:29:45 +0000\"\n /\\*\\*Created\\*\\*:\\s*(.+)/, // generic\n ];\n\n for (const re of patterns) {\n const m = content.match(re);\n if (m) {\n // For **Date** style, the entire value is a date string\n // For **Time** style, only the first token is the date\n const raw = re === patterns[0] ? m[1] : m[1].trim();\n const d = new Date(raw);\n if (!isNaN(d.getTime())) return d;\n }\n }\n\n // Fallback: frontmatter timestamp (import time)\n if (typeof frontmatter.timestamp === \"string\") {\n const d = new Date(frontmatter.timestamp);\n if (!isNaN(d.getTime())) return d;\n }\n\n return null;\n}\n\n/** Extract title from \"# Title\" in markdown content */\nfunction extractTitle(content: string): string {\n const m = content.match(/^#\\s+(.+)/m);\n return m ? m[1].trim() : \"(no title)\";\n}\n\n/** Extract all \"- **Field**: value\" pairs from markdown content */\nfunction extractFields(content: string): Record<string, string> {\n const fields: Record<string, string> = {};\n const re = /\\*\\*(\\w[\\w\\s]*?)\\*\\*:\\s*(.+)/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(content)) !== null) {\n fields[match[1].trim()] = match[2].trim();\n }\n return fields;\n}\n\n// ---------- File reading ----------\n\ninterface ParsedEntry {\n file: string;\n title: string;\n date: Date | null;\n fields: Record<string, string>;\n frontmatter: Record<string, unknown>;\n}\n\n/** Read and parse all markdown files in a connector subdirectory */\nasync function readConnectorDir(subdir: string): Promise<ParsedEntry[]> {\n const dir = path.join(getPaiHome(), \"raw\", \"connector\", subdir);\n let fileNames: string[];\n try {\n fileNames = (await fs.readdir(dir)).filter((f) => f.endsWith(\".md\"));\n } catch {\n return [];\n }\n\n const results: ParsedEntry[] = [];\n const BATCH = 50;\n\n for (let i = 0; i < fileNames.length; i += BATCH) {\n const batch = fileNames.slice(i, i + BATCH);\n const parsed = await Promise.all(\n batch.map(async (f) => {\n const filePath = path.join(dir, f);\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { data, content } = matter(raw);\n const fm = data as Record<string, unknown>;\n return {\n file: filePath,\n title: extractTitle(content),\n date: extractContentDate(content, fm),\n fields: extractFields(content),\n frontmatter: fm,\n };\n } catch {\n return null;\n }\n }),\n );\n for (const p of parsed) {\n if (p) results.push(p);\n }\n }\n\n return results;\n}\n\n// ---------- queryConnector tool ----------\n\nconst QueryConnectorParams = Type.Object({\n source: Type.Optional(\n Type.String({\n description:\n \"Connector source to query (e.g. 'calendar', 'gmail', 'mac'). Omit to list all available sources.\",\n }),\n ),\n range: Type.Optional(\n Type.Union(\n [\n Type.Literal(\"upcoming\"),\n Type.Literal(\"today\"),\n Type.Literal(\"this_week\"),\n Type.Literal(\"past_week\"),\n Type.Literal(\"past_month\"),\n Type.Literal(\"all\"),\n ],\n {\n description:\n \"Time range filter. 'upcoming' = future events only, 'today' = today, 'this_week' = ±7 days, 'past_week' = last 7 days, 'past_month' = last 30 days, 'all' = no filter. Default: 'all'.\",\n },\n ),\n ),\n limit: Type.Optional(\n Type.Number({ description: \"Max entries to return (default 10)\" }),\n ),\n query: Type.Optional(\n Type.String({\n description: \"Optional text filter on title/content (case-insensitive)\",\n }),\n ),\n sort: Type.Optional(\n Type.Union([Type.Literal(\"asc\"), Type.Literal(\"desc\")], {\n description:\n \"Sort by date: 'asc' = oldest first (good for upcoming events), 'desc' = newest first (good for emails). Default: auto-detected based on range.\",\n }),\n ),\n});\n\nexport const queryConnectorTool: AgentTool<typeof QueryConnectorParams> = {\n name: \"queryConnector\",\n label: \"Query Connector Data\",\n description:\n \"Query imported data from any connector source (calendar, gmail, mac, etc.). \" +\n \"Call WITHOUT 'source' to discover available connectors. \" +\n \"Call WITH 'source' to query entries sorted by date. \" +\n \"Supports time range filtering and text search. \" +\n \"Use this for ALL time-sensitive questions (schedules, recent emails, upcoming events).\",\n parameters: QueryConnectorParams,\n execute: async (_toolCallId, params) => {\n const connectorDir = path.join(getPaiHome(), \"raw\", \"connector\");\n\n // --- Discovery mode: list available sources ---\n if (!params.source) {\n let subdirs: string[];\n try {\n const entries = await fs.readdir(connectorDir, { withFileTypes: true });\n subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return {\n content: [\n {\n type: \"text\",\n text: \"No connector data found. Run 'pai import' to import data.\",\n },\n ],\n details: { sources: [] },\n };\n }\n\n if (subdirs.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No connector sources found. Run 'pai import --source <source>' to import data.\",\n },\n ],\n details: { sources: [] },\n };\n }\n\n // Get file counts and date ranges for each source\n const summaries: { name: string; files: number; sample: string }[] = [];\n for (const sub of subdirs) {\n const subPath = path.join(connectorDir, sub);\n try {\n const files = (await fs.readdir(subPath)).filter((f) =>\n f.endsWith(\".md\"),\n );\n // Peek at the latest file for a sample title\n let sample = \"\";\n if (files.length > 0) {\n try {\n const latest = path.join(subPath, files[files.length - 1]);\n const raw = await fs.readFile(latest, \"utf-8\");\n const { content } = matter(raw);\n sample = extractTitle(content);\n } catch {\n // ignore\n }\n }\n summaries.push({ name: sub, files: files.length, sample });\n } catch {\n summaries.push({ name: sub, files: 0, sample: \"\" });\n }\n }\n\n const text = summaries\n .map(\n (s) =>\n `- **${s.name}**: ${s.files} files${s.sample ? ` (latest: \"${s.sample}\")` : \"\"}`,\n )\n .join(\"\\n\");\n\n return {\n content: [\n {\n type: \"text\",\n text: `Available connector sources:\\n${text}\\n\\nCall queryConnector with source=\"<name>\" to query a specific source.`,\n },\n ],\n details: {\n sources: summaries.map((s) => ({\n name: s.name,\n files: s.files,\n })),\n },\n };\n }\n\n // --- Query mode: read and filter entries from a specific source ---\n const entries = await readConnectorDir(params.source);\n if (entries.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No data found for source \"${params.source}\". Check available sources by calling queryConnector without a source parameter.`,\n },\n ],\n details: { results: [], totalImported: 0 },\n };\n }\n\n const now = new Date();\n const range = params.range ?? \"all\";\n const limit = params.limit ?? 10;\n const queryLower = params.query?.toLowerCase();\n\n // Filter\n const filtered = entries.filter((e) => {\n // Text filter\n if (queryLower) {\n const haystack = (\n e.title +\n \" \" +\n Object.values(e.fields).join(\" \")\n ).toLowerCase();\n if (!haystack.includes(queryLower)) return false;\n }\n\n // Time filter (skip entries without dates for time-filtered queries)\n if (range !== \"all\") {\n if (!e.date) return false;\n const t = e.date.getTime();\n const nowMs = now.getTime();\n const DAY = 24 * 60 * 60 * 1000;\n\n switch (range) {\n case \"upcoming\":\n if (t < nowMs) return false;\n break;\n case \"today\": {\n const startOfDay = new Date(now);\n startOfDay.setHours(0, 0, 0, 0);\n const endOfDay = new Date(now);\n endOfDay.setHours(23, 59, 59, 999);\n if (t < startOfDay.getTime() || t > endOfDay.getTime())\n return false;\n break;\n }\n case \"this_week\":\n if (t < nowMs - 7 * DAY || t > nowMs + 7 * DAY) return false;\n break;\n case \"past_week\":\n if (t < nowMs - 7 * DAY || t > nowMs) return false;\n break;\n case \"past_month\":\n if (t < nowMs - 30 * DAY || t > nowMs) return false;\n break;\n }\n }\n\n return true;\n });\n\n // Sort — auto-detect direction based on range\n const sortDir =\n params.sort ??\n (range === \"upcoming\" || range === \"today\" || range === \"this_week\"\n ? \"asc\"\n : \"desc\");\n\n filtered.sort((a, b) => {\n const ta = a.date?.getTime() ?? 0;\n const tb = b.date?.getTime() ?? 0;\n return sortDir === \"asc\" ? ta - tb : tb - ta;\n });\n\n const limited = filtered.slice(0, limit);\n\n if (limited.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No entries matched for source \"${params.source}\", range \"${range}\". Total imported: ${entries.length}`,\n },\n ],\n details: { results: [], totalImported: entries.length },\n };\n }\n\n // Format output — show title, date, and all extracted fields\n const text = limited\n .map((e, i) => {\n const parts = [`${i + 1}. **${e.title}**`];\n // Show all structured fields from content\n for (const [key, val] of Object.entries(e.fields)) {\n parts.push(` ${key}: ${val}`);\n }\n // Add date if not already shown via fields\n if (e.date && !e.fields[\"Time\"] && !e.fields[\"Date\"]) {\n parts.push(` Date: ${e.date.toISOString()}`);\n }\n return parts.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${filtered.length} entries (showing ${limited.length}) from \"${params.source}\":\\n\\n${text}`,\n },\n ],\n details: {\n results: limited.map((e) => ({\n file: e.file,\n title: e.title,\n date: e.date?.toISOString() ?? null,\n fields: e.fields,\n })),\n totalMatched: filtered.length,\n totalImported: entries.length,\n },\n };\n },\n};\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { loadProfile } from \"../profile/index.js\";\nimport { search } from \"../search/index.js\";\nimport { isQmdAvailable } from \"../utils/process.js\";\n\n// ---------- searchVault ----------\n\nconst SearchParams = Type.Object({\n query: Type.String({ description: \"Search query\" }),\n limit: Type.Optional(\n Type.Number({ description: \"Max results (default 5)\" }),\n ),\n});\n\nexport const searchVaultTool: AgentTool<typeof SearchParams> = {\n name: \"searchVault\",\n label: \"Search Vault\",\n description:\n \"Search user's distilled knowledge vault (experiences, preferences, lessons). Hybrid search: BM25 + vector + reranking. Ranked by RELEVANCE, not recency. For time-sensitive queries, supplement with bash/grep on raw files.\",\n parameters: SearchParams,\n execute: async (_toolCallId, params) => {\n if (!(await isQmdAvailable())) {\n return {\n content: [{ type: \"text\", text: \"Error: QMD not installed.\" }],\n details: { error: \"QMD not installed\" },\n };\n }\n try {\n const results = await search(\n params.query,\n \"vault\",\n params.limit ?? 5,\n \"query\",\n );\n const text = results\n .map(\n (r) =>\n `## ${r.title ?? \"Untitled\"}\\nFile: ${r.file}\\n${r.snippet ?? \"\"}`,\n )\n .join(\"\\n\\n\");\n return {\n content: [{ type: \"text\", text: text || \"No results found.\" }],\n details: {\n results: results.map((r) => ({\n file: r.file,\n title: r.title,\n snippet: r.snippet,\n })),\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg },\n };\n }\n },\n};\n\n// ---------- searchRaw ----------\n\nexport const searchRawTool: AgentTool<typeof SearchParams> = {\n name: \"searchRaw\",\n label: \"Search Raw\",\n description:\n \"Search user's original raw data (for tracing back to sources). Raw files have timestamps in frontmatter. Ranked by RELEVANCE, not recency.\",\n parameters: SearchParams,\n execute: async (_toolCallId, params) => {\n if (!(await isQmdAvailable())) {\n return {\n content: [{ type: \"text\", text: \"Error: QMD not installed.\" }],\n details: { error: \"QMD not installed\" },\n };\n }\n try {\n const results = await search(\n params.query,\n \"raw\",\n params.limit ?? 5,\n \"query\",\n );\n const text = results\n .map(\n (r) =>\n `## ${r.title ?? \"Untitled\"}\\nFile: ${r.file}\\n${r.snippet ?? \"\"}`,\n )\n .join(\"\\n\\n\");\n return {\n content: [{ type: \"text\", text: text || \"No results found.\" }],\n details: {\n results: results.map((r) => ({\n file: r.file,\n title: r.title,\n snippet: r.snippet,\n })),\n },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: \"text\", text: `Error: ${msg}` }],\n details: { error: msg },\n };\n }\n },\n};\n\n// ---------- readProfile ----------\n\nconst EmptyParams = Type.Object({});\n\nexport const readProfileTool: AgentTool<typeof EmptyParams> = {\n name: \"readProfile\",\n label: \"Read Profile\",\n description:\n \"Read the user's compiled profile (identity, environment, tools, projects, habits). This is the fastest way to understand who the user is.\",\n parameters: EmptyParams,\n execute: async () => {\n const profile = await loadProfile();\n const text = profile ?? \"No profile found. Run 'pai init' first.\";\n return {\n content: [{ type: \"text\", text }],\n details: { hasProfile: !!profile },\n };\n },\n};\n","import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport {\n bashTool,\n globTool,\n grepTool,\n lsTool,\n readFileTool,\n} from \"./tools-meta.js\";\nimport { queryConnectorTool } from \"./tools-connector.js\";\nimport {\n readProfileTool,\n searchRawTool,\n searchVaultTool,\n} from \"./tools-pai.js\";\n\nexport interface CreateAskToolsOptions {\n /** Enable bash tool (prompt-injection risk — only enable when you trust all data sources) */\n allowBash?: boolean;\n}\n\n/** Create the full ask agent tool set: pai knowledge tools + filesystem meta tools */\nexport function createAskTools(opts?: CreateAskToolsOptions): AgentTool<any>[] {\n const tools: AgentTool<any>[] = [\n // pai knowledge tools (use first — fast, structured)\n searchVaultTool,\n searchRawTool,\n readProfileTool,\n // connector data tool (calendar, gmail, mac, or any future source)\n queryConnectorTool,\n // filesystem meta tools (use when knowledge tools aren't enough)\n readFileTool,\n grepTool,\n globTool,\n lsTool,\n ];\n if (opts?.allowBash) {\n tools.push(bashTool);\n }\n return tools;\n}\n","import { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { AgentEvent } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport fs from \"node:fs/promises\";\nimport { loadConfig } from \"../config/index.js\";\nimport { getPreferencesPath } from \"../config/paths.js\";\nimport type { AskResult, AskUsage } from \"../types.js\";\nimport { buildAskSystemPrompt } from \"./system-prompt.js\";\nimport { createAskTools } from \"./tools.js\";\n\n/**\n * Resolve a model from pai config.\n * - If custom baseUrl is set, construct an openai-completions Model for compatibility.\n * - Otherwise try the pi-ai registry first, then fallback to a manual Model.\n */\nfunction resolveModel(modelId: string, baseUrl?: string): Model<any> {\n const trimmedBase = baseUrl?.trim() || undefined;\n\n // Custom baseUrl → always use openai-completions for third-party compatibility\n if (trimmedBase) {\n return {\n id: modelId,\n name: modelId,\n api: \"openai-completions\" as const,\n provider: \"openai\",\n baseUrl: trimmedBase,\n reasoning: false,\n input: [\"text\" as const],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 16384,\n };\n }\n\n // Try pi-ai registry (typed, but we cast for dynamic model IDs)\n const registered = getModel(\"openai\", modelId as any);\n if (registered) return registered;\n\n // Fallback: unknown model on standard OpenAI\n return {\n id: modelId,\n name: modelId,\n api: \"openai-completions\" as const,\n provider: \"openai\",\n baseUrl: \"https://api.openai.com/v1\",\n reasoning: false,\n input: [\"text\" as const],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 16384,\n };\n}\n\n/** Build full system prompt with user preferences injected */\nasync function getSystemPrompt(): Promise<string> {\n const base = buildAskSystemPrompt();\n try {\n const prefs = await fs.readFile(getPreferencesPath(), \"utf-8\");\n if (prefs.trim()) {\n return `${base}\\n\\n---USER PREFERENCES (always respect these)---\\n${prefs}`;\n }\n } catch {\n // no preferences file\n }\n return base;\n}\n\n/** Knowledge tool names whose results count as citable sources */\nconst KNOWLEDGE_TOOLS = new Set([\n \"searchVault\",\n \"searchRaw\",\n \"readProfile\",\n \"readFile\",\n \"queryConnector\",\n]);\n\n/** Extract source file paths from pai knowledge tool results only (skip glob/ls/bash noise) */\nfunction extractSources(messages: any[]): string[] {\n const sources = new Set<string>();\n for (const msg of messages) {\n if (msg.role !== \"toolResult\") continue;\n if (!KNOWLEDGE_TOOLS.has(msg.toolName)) continue;\n const details = msg.details;\n if (!details) continue;\n // searchVault / searchRaw results\n if (Array.isArray(details.results)) {\n for (const r of details.results) {\n if (typeof r.file === \"string\" && r.file) sources.add(r.file);\n }\n }\n // readFile path\n if (typeof details.path === \"string\" && details.path) {\n sources.add(details.path);\n }\n }\n return [...sources];\n}\n\n/** Aggregate token usage from all assistant messages */\nfunction aggregateUsage(messages: any[]): AskUsage {\n const usage: AskUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n totalTokens: 0,\n cost: 0,\n };\n for (const msg of messages) {\n if (msg.role === \"assistant\" && msg.usage) {\n const u = msg.usage;\n usage.inputTokens += u.input ?? 0;\n usage.outputTokens += u.output ?? 0;\n usage.cacheReadTokens += u.cacheRead ?? 0;\n usage.cacheWriteTokens += u.cacheWrite ?? 0;\n usage.totalTokens += u.totalTokens ?? 0;\n if (u.cost) {\n usage.cost += u.cost.total ?? 0;\n }\n }\n }\n return usage;\n}\n\n/** Extract text answer from the last assistant message */\nfunction extractAnswer(messages: any[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n const texts: string[] = [];\n for (const c of msg.content) {\n if (c.type === \"text\") texts.push(c.text);\n }\n if (texts.length > 0) return texts.join(\"\");\n }\n }\n return \"\";\n}\n\nexport interface AskAgentOptions {\n maxSteps?: number;\n model?: string;\n /** Enable the bash tool (disabled by default due to prompt-injection risk) */\n allowBash?: boolean;\n onEvent?: (event: AgentEvent) => void;\n}\n\nexport async function runAskAgent(\n question: string,\n opts?: AskAgentOptions,\n): Promise<AskResult> {\n const config = await loadConfig();\n const apiKey = process.env[config.llm.apiKeyEnv];\n if (!apiKey) {\n throw new Error(\n `Missing API key: set ${config.llm.apiKeyEnv} environment variable`,\n );\n }\n\n const modelId = opts?.model ?? config.llm.cheapModel;\n const model = resolveModel(modelId, config.llm.baseUrl);\n const system = await getSystemPrompt();\n const tools = createAskTools({ allowBash: opts?.allowBash });\n\n const agent = new Agent({\n initialState: {\n systemPrompt: system,\n model,\n tools,\n },\n getApiKey: async () => apiKey,\n });\n\n // Subscribe to events for verbose / streaming output.\n // Enforce maxSteps by aborting the agent when the turn limit is reached.\n const maxSteps = opts?.maxSteps ?? 10;\n let turnCount = 0;\n let toolCallCount = 0;\n const unsubscribe = agent.subscribe((event) => {\n if (event.type === \"turn_end\") {\n turnCount++;\n if (turnCount >= maxSteps) {\n agent.abort();\n }\n }\n if (event.type === \"tool_execution_start\") {\n toolCallCount++;\n }\n opts?.onEvent?.(event);\n });\n\n const startTime = performance.now();\n try {\n await agent.prompt(question);\n } finally {\n unsubscribe();\n }\n const durationMs = Math.round(performance.now() - startTime);\n\n const messages = agent.state.messages;\n const answer = extractAnswer(messages);\n const sources = extractSources(messages);\n const usage = aggregateUsage(messages);\n\n // Extract model and stopReason from the last assistant message\n let actualModel = modelId;\n let stopReason = \"unknown\";\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i] as any;\n if (m.role === \"assistant\") {\n if (m.model) actualModel = m.model;\n if (m.stopReason) stopReason = m.stopReason;\n break;\n }\n }\n\n return {\n answer,\n sources,\n steps: turnCount,\n durationMs,\n usage,\n model: actualModel,\n toolCalls: toolCallCount,\n stopReason,\n };\n}\n","import type { Command } from \"commander\";\nimport type { AgentEvent } from \"@mariozechner/pi-agent-core\";\nimport { runAskAgent } from \"../ask/index.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface AskOpts {\n json?: boolean;\n steps?: number;\n model?: string;\n verbose?: boolean;\n unsafeBash?: boolean;\n}\n\nexport function registerAskCommand(program: Command): void {\n program\n .command(\"ask <question>\")\n .description(\n \"Ask a question about the user; agent uses tools (search, read, bash) to find the answer\",\n )\n .option(\"--json\", \"Output as JSON (answer, sources, steps)\")\n .option(\"--steps <n>\", \"Max tool-call steps\", \"10\")\n .option(\"--model <name>\", \"Override LLM model\")\n .option(\"--verbose\", \"Show each tool step\")\n .option(\n \"--unsafe-bash\",\n \"Enable bash tool (risk: model can execute arbitrary commands)\",\n )\n .action(async (question: string, opts: AskOpts) => {\n if (!question?.trim()) {\n console.error(\"Usage: pai ask <question>\");\n process.exit(1);\n }\n try {\n const onEvent = opts.verbose\n ? (event: AgentEvent) => {\n if (event.type === \"tool_execution_start\") {\n console.log(\n `[tool] ${event.toolName}(${JSON.stringify(event.args ?? {}).slice(0, 80)}...)`,\n );\n }\n if (event.type === \"tool_execution_end\") {\n const status = event.isError ? \"✗\" : \"✓\";\n console.log(`[tool] ${event.toolName} ${status}`);\n }\n }\n : undefined;\n\n const result = await runAskAgent(question.trim(), {\n maxSteps: parseInt(String(opts.steps), 10) || 10,\n model: opts.model,\n allowBash: opts.unsafeBash,\n onEvent,\n });\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n answer: result.answer,\n sources: result.sources,\n model: result.model,\n steps: result.steps,\n toolCalls: result.toolCalls,\n stopReason: result.stopReason,\n durationMs: result.durationMs,\n usage: result.usage,\n },\n null,\n 2,\n ) + \"\\n\",\n );\n } else {\n process.stdout.write(result.answer + \"\\n\");\n if (result.sources.length > 0) {\n process.stdout.write(\n \"\\n_Sources: \" + result.sources.join(\", \") + \"_\\n\",\n );\n }\n // Display metrics\n const { usage, durationMs, steps, model, toolCalls } = result;\n const secs = (durationMs / 1000).toFixed(1);\n const tps =\n durationMs > 0\n ? ((usage.outputTokens / durationMs) * 1000).toFixed(1)\n : \"–\";\n const costStr =\n usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : \"\";\n const cacheStr =\n usage.cacheReadTokens > 0\n ? ` · cache: ${usage.cacheReadTokens} read`\n : \"\";\n\n const parts = [\n model,\n `${secs}s`,\n `${steps} steps · ${toolCalls} tool calls`,\n `${usage.totalTokens} tokens (in: ${usage.inputTokens}, out: ${usage.outputTokens})`,\n `${tps} tok/s`,\n ];\n if (cacheStr) parts.push(cacheStr.replace(\" · \", \"\"));\n if (costStr) parts.push(costStr);\n\n process.stdout.write(`\\n---\\n${parts.join(\" · \")}\\n`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Ask failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getSkillsDir } from \"../config/paths.js\";\nimport { search } from \"../search/index.js\";\nimport { isQmdAvailable } from \"../utils/process.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface ContextOpts {\n task?: string;\n profile?: string;\n json?: boolean;\n}\n\nexport function registerContextCommand(program: Command): void {\n program\n .command(\"context\")\n .description(\n \"Output personal context for AI agents (identity + task-relevant memories)\",\n )\n .option(\"--task <description>\", \"Current task — searches vault for relevant memories\")\n .option(\"--profile <name>\", \"Profile to use\", \"full-context\")\n .option(\"--json\", \"Output as JSON for machine consumption\")\n .action(async (opts: ContextOpts) => {\n try {\n const profileName = opts.profile ?? \"full-context\";\n const profileContent = await readProfile(profileName);\n\n if (!profileContent) {\n console.error(\n `Profile \"${profileName}\" not found. Run \"pai generate\" first.`,\n );\n process.exit(1);\n }\n\n // Extract compact identity from profile (Who I Am + Preferences sections)\n const identity = extractCompactIdentity(profileContent);\n\n // If --task given, search vault for relevant memories\n // Use \"query\" mode (hybrid: BM25 + vector + reranking) for best recall\n let memories: { file: string; title?: string; snippet: string; score?: number }[] = [];\n if (opts.task) {\n const qmdOk = await isQmdAvailable();\n if (qmdOk) {\n memories = await search(opts.task, \"vault\", 5, \"query\");\n }\n }\n\n if (opts.json) {\n const output = {\n profile: profileName,\n identity,\n memories: memories.map((m) => ({\n file: m.file,\n title: m.title ?? null,\n snippet: m.snippet,\n score: m.score ?? null,\n })),\n };\n // Write raw JSON to stdout (bypass chalk/ora)\n process.stdout.write(JSON.stringify(output, null, 2) + \"\\n\");\n } else {\n // Human-readable markdown output to stdout\n process.stdout.write(identity + \"\\n\");\n\n if (memories.length > 0) {\n process.stdout.write(\"\\n## Relevant Memories\\n\\n\");\n for (const m of memories) {\n const title = m.title ?? path.basename(m.file);\n const snippet = m.snippet.slice(0, 160).replace(/\\n/g, \" \");\n process.stdout.write(`- **${title}**: ${snippet}\\n`);\n process.stdout.write(` _source: ${m.file}_\\n\\n`);\n }\n } else if (opts.task) {\n process.stdout.write(\n \"\\n_No matching memories found for this task._\\n\",\n );\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Context failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\n/** Read a profile file from ~/.pai/skills/profiles/ */\nasync function readProfile(name: string): Promise<string | null> {\n const profilePath = path.join(getSkillsDir(), `${name}.md`);\n try {\n return await fs.readFile(profilePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/**\n * Extract compact identity from a full profile.\n * Keeps: ## Who I Am, ## Preferences, ## Current Work sections\n * Drops: ## Hard-won Lessons (those come from search results instead)\n */\nfunction extractCompactIdentity(profile: string): string {\n const lines = profile.split(\"\\n\");\n const sections: { heading: string; lines: string[] }[] = [];\n let current: { heading: string; lines: string[] } | null = null;\n\n for (const line of lines) {\n if (line.startsWith(\"## \")) {\n if (current) sections.push(current);\n current = { heading: line, lines: [] };\n } else if (line.startsWith(\"# \") && !line.startsWith(\"## \")) {\n // Keep H1 title\n if (current) sections.push(current);\n current = { heading: line, lines: [] };\n } else if (current) {\n current.lines.push(line);\n }\n }\n if (current) sections.push(current);\n\n // Keep identity-relevant sections, drop \"Hard-won Lessons\"\n const keep = sections.filter(\n (s) => !s.heading.toLowerCase().includes(\"hard-won lessons\"),\n );\n\n return keep.map((s) => [s.heading, ...s.lines].join(\"\\n\")).join(\"\\n\");\n}\n","import type { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { getSkillsDir } from \"../config/paths.js\";\nimport { loadProfile } from \"../profile/index.js\";\nimport * as console from \"../utils/console.js\";\n\nconst MANAGED_TAG = \"<!-- managed by pai distribute — do not edit manually -->\";\n\ninterface DistributeOpts {\n target?: string;\n profile?: string;\n}\n\nexport function registerDistributeCommand(program: Command): void {\n program\n .command(\"distribute\")\n .description(\n \"Deploy personal profile + agent instructions to Cursor rules and other agent configs\",\n )\n .option(\n \"--target <name>\",\n \"Target platform (cursor). Default: all\",\n )\n .option(\"--profile <name>\", \"Legacy: use a generated SKILL.md profile instead of profile.md\")\n .action(async (opts: DistributeOpts) => {\n try {\n // Priority: profile.md (fast path) → legacy SKILL.md profiles (fallback)\n let profileContent: string | null = null;\n let source = \"\";\n\n if (opts.profile) {\n // Explicit --profile flag: use legacy SKILL.md path\n profileContent = await readLegacyProfile(opts.profile);\n source = `skills/profiles/${opts.profile}.md`;\n } else {\n // Default: try profile.md first\n profileContent = await loadProfile();\n source = \"profile.md\";\n\n if (!profileContent) {\n // Fallback to legacy full-context profile\n profileContent = await readLegacyProfile(\"full-context\");\n source = \"skills/profiles/full-context.md\";\n }\n }\n\n if (!profileContent) {\n console.error(\n 'No profile found. Run \"pai init\" or \"pai profile --rebuild\" first.',\n );\n process.exit(1);\n }\n\n console.info(`Source: ${source}`);\n\n const ruleContent = buildAgentRule(profileContent);\n const targets = opts.target ? [opts.target] : [\"cursor\"];\n const results: string[] = [];\n\n for (const target of targets) {\n switch (target) {\n case \"cursor\": {\n const wrote = await distributeToCursor(ruleContent);\n if (wrote) results.push(wrote);\n break;\n }\n default:\n console.warn(`Unknown target: ${target}`);\n }\n }\n\n if (results.length > 0) {\n console.success(\n `Distributed to ${results.length} target(s):`,\n );\n for (const r of results) {\n console.log(` → ${r}`);\n }\n } else {\n console.warn(\"No targets were written.\");\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Distribute failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n\n/** Read a legacy SKILL.md profile file from skills/profiles/ */\nasync function readLegacyProfile(name: string): Promise<string | null> {\n const profilePath = path.join(getSkillsDir(), `${name}.md`);\n try {\n return await fs.readFile(profilePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/**\n * Build the full agent rule file content.\n * Combines: user profile (who you are) + skill instructions (how to use pai).\n */\nfunction buildAgentRule(profileContent: string): string {\n return `${MANAGED_TAG}\n# Personal AI Context\n\n${profileContent.trim()}\n\n---\n\n## Agent Skill — pai CLI\n\nYou have access to the user's personal knowledge base via \\`pai\\` CLI commands.\nUse these to retrieve context, search knowledge, and remember new lessons.\n\n### Retrieve task-relevant memories (RECOMMENDED before starting work)\n\n\\`\\`\\`bash\npai context --task \"<brief description of current task>\"\n\\`\\`\\`\n\nThis returns the user's identity + vault memories relevant to the task.\n\n### Search specific knowledge\n\n\\`\\`\\`bash\npai search \"<query>\" # hybrid search (best quality, ~5s)\npai search \"<query>\" --fast # keyword search (instant)\npai search \"<query>\" --json # machine-readable output\n\\`\\`\\`\n\n### Remember new lessons\n\nWhen you discover something the user should remember (a lesson, preference, or tip):\n\n\\`\\`\\`bash\npai add \"<lesson or experience>\"\n\\`\\`\\`\n\n### Rebuild profile after changes\n\n\\`\\`\\`bash\npai profile --rebuild # re-scan local machine and recompile\npai distribute # redeploy updated profile\n\\`\\`\\`\n`;\n}\n\n/** Deploy to ~/.cursor/rules/pai-context.mdc */\nasync function distributeToCursor(content: string): Promise<string | null> {\n const cursorRulesDir = path.join(os.homedir(), \".cursor\", \"rules\");\n const targetPath = path.join(cursorRulesDir, \"pai-context.mdc\");\n\n try {\n await fs.mkdir(cursorRulesDir, { recursive: true });\n await fs.writeFile(targetPath, content, \"utf-8\");\n return targetPath;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Failed to write Cursor rule: ${msg}`);\n return null;\n }\n}\n","import type { Command } from \"commander\";\nimport { loadProfile, rebuildProfile } from \"../profile/index.js\";\nimport { getProfilePath } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\ninterface ProfileOpts {\n rebuild?: boolean;\n export?: boolean;\n json?: boolean;\n}\n\nexport function registerProfileCommand(program: Command): void {\n program\n .command(\"profile\")\n .description(\"View, rebuild, or export your personal profile\")\n .option(\"--rebuild\", \"Re-scan local machine and rebuild profile\")\n .option(\"--export\", \"Output profile in copy-paste friendly format\")\n .option(\"--json\", \"Output profile metadata as JSON\")\n .action(async (opts: ProfileOpts) => {\n try {\n if (opts.rebuild) {\n const { profilePath, results } = await rebuildProfile({ verbose: true });\n console.success(`Profile rebuilt → ${profilePath}`);\n console.info(`Compiled from ${results.length} data sources.`);\n return;\n }\n\n const profile = await loadProfile();\n\n if (!profile) {\n console.error(\n 'No profile found. Run \"pai init\" or \"pai profile --rebuild\" first.',\n );\n process.exit(1);\n }\n\n if (opts.json) {\n const lines = profile.split(\"\\n\");\n const sections: { heading: string; lineCount: number }[] = [];\n for (const line of lines) {\n if (line.startsWith(\"## \")) {\n sections.push({ heading: line.replace(\"## \", \"\"), lineCount: 0 });\n } else if (sections.length > 0) {\n sections[sections.length - 1].lineCount++;\n }\n }\n\n const meta = {\n path: getProfilePath(),\n totalLines: lines.length,\n sections,\n };\n\n process.stdout.write(JSON.stringify(meta, null, 2) + \"\\n\");\n return;\n }\n\n if (opts.export) {\n // Clean output for copy-paste (no extra decorations)\n process.stdout.write(profile);\n return;\n }\n\n // Default: display profile with some formatting context\n console.log(\"\");\n console.info(`Profile: ${getProfilePath()}`);\n console.log(\"\");\n process.stdout.write(profile);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Profile command failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { appendJournal, readJournal, getJournalStatus } from \"../memory/index.js\";\nimport { getTodayDate } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerLogCommand(program: Command): void {\n program\n .command(\"log [text...]\")\n .description(\"Quick-log to today's journal (memory/YYYY-MM-DD.md)\")\n .option(\"--clip\", \"Read from clipboard (macOS pbpaste)\")\n .option(\"--show\", \"Show today's journal\")\n .option(\"--date <date>\", \"Target date (YYYY-MM-DD), default today\")\n .action(async (textParts: string[], opts: { clip?: boolean; show?: boolean; date?: string }) => {\n try {\n const date = opts.date ?? getTodayDate();\n\n // Show mode: display journal\n if (opts.show) {\n const content = await readJournal(date);\n if (!content) {\n console.info(`No journal for ${date}.`);\n return;\n }\n console.log(content);\n const status = await getJournalStatus(date);\n if (status) {\n console.log(console.dim(`(${status.entries} entries${status.hasDigest ? \", has digest\" : \"\"})`));\n }\n return;\n }\n\n // Get text from args or clipboard\n let text = textParts.join(\" \").trim();\n\n if (opts.clip || !text) {\n if (opts.clip) {\n // Read from clipboard\n const { execFile } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execFileP = promisify(execFile);\n try {\n const { stdout } = await execFileP(\"pbpaste\");\n text = stdout.trim();\n } catch {\n console.error(\"Failed to read clipboard. Is pbpaste available?\");\n process.exit(1);\n }\n }\n\n if (!text) {\n console.error('Usage: pai log \"your note\" or pai log --clip');\n process.exit(1);\n }\n }\n\n const { path: journalPath, entryCount } = await appendJournal(text, date);\n console.success(`Logged to ${date} (entry #${entryCount})`);\n console.log(console.dim(` ${journalPath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Log failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { readJournal, appendDigest } from \"../memory/index.js\";\nimport { listVaultEntries } from \"../vault/index.js\";\nimport { llmCall } from \"../llm/index.js\";\nimport { digestSystemPrompt, digestUserPrompt } from \"../prompts/extract.js\";\nimport { getTodayDate } from \"../config/paths.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerDigestCommand(program: Command): void {\n program\n .command(\"digest\")\n .description(\"Generate daily AI digest for a journal day\")\n .option(\"--date <date>\", \"Target date (YYYY-MM-DD), default today\")\n .option(\"--dry-run\", \"Preview digest without writing\")\n .action(async (opts: { date?: string; dryRun?: boolean }) => {\n try {\n const date = opts.date ?? getTodayDate();\n\n // Read journal\n const journal = await readJournal(date);\n if (!journal) {\n console.info(`No journal for ${date}. Use \"pai log\" to add entries first.`);\n return;\n }\n\n const spin = console.spinner(`Generating digest for ${date}...`);\n\n // Collect today's PINData entries from vault\n const allEntries = await listVaultEntries();\n const todayEntries = allEntries\n .filter((e) => e.parsed?.date === date)\n .map((e) => e.line);\n\n const pinDataText = todayEntries.length > 0\n ? todayEntries.join(\"\\n\")\n : \"\";\n\n // Generate digest via LLM\n const system = digestSystemPrompt();\n const prompt = digestUserPrompt(journal, pinDataText, date);\n const digest = await llmCall(prompt, system);\n\n if (opts.dryRun) {\n spin.succeed(`[DRY RUN] Digest for ${date}:`);\n console.log(\"\");\n console.log(digest);\n return;\n }\n\n // Append digest to journal\n const journalPath = await appendDigest(digest, date);\n spin.succeed(`Digest written for ${date}`);\n console.log(\"\");\n console.log(digest);\n console.log(\"\");\n console.log(console.dim(` ${journalPath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Digest failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { findGaps } from \"../memory/journal.js\";\nimport * as console from \"../utils/console.js\";\n\nexport function registerGapsCommand(program: Command): void {\n program\n .command(\"gaps\")\n .description(\"Check for missing journal entries in the last N days\")\n .option(\"--days <n>\", \"Number of days to check\", \"7\")\n .action(async (opts: { days: string }) => {\n try {\n const days = parseInt(opts.days, 10);\n if (isNaN(days) || days < 1) {\n console.error(\"--days must be a positive number.\");\n process.exit(1);\n }\n\n const gaps = await findGaps(days);\n\n if (gaps.length === 0) {\n console.success(`No gaps in the last ${days} day(s). All journals present.`);\n return;\n }\n\n console.warn(`Found ${gaps.length} missing journal(s) in the last ${days} days:`);\n for (const date of gaps) {\n console.log(` ${date}`);\n }\n console.log(\"\");\n console.info('Use \"pai log --date YYYY-MM-DD \\'note\\'\" to backfill.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Gaps check failed: ${msg}`);\n process.exit(1);\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { registerAuthCommand } from \"./register.auth.js\";\nimport { registerInitCommand } from \"./register.init.js\";\nimport { registerAddCommand } from \"./register.add.js\";\nimport { registerDistillCommand } from \"./register.distill.js\";\nimport { registerGenerateCommand } from \"./register.generate.js\";\nimport { registerSearchCommand } from \"./register.search.js\";\nimport { registerIndexCommand } from \"./register.index.js\";\nimport { registerImportCommand } from \"./register.import.js\";\nimport { registerStatusCommand } from \"./register.status.js\";\nimport { registerResetCommand } from \"./register.reset.js\";\nimport { registerAskCommand } from \"./register.ask.js\";\nimport { registerContextCommand } from \"./register.context.js\";\nimport { registerDistributeCommand } from \"./register.distribute.js\";\nimport { registerProfileCommand } from \"./register.profile.js\";\nimport { registerLogCommand } from \"./register.log.js\";\nimport { registerDigestCommand } from \"./register.digest.js\";\nimport { registerGapsCommand } from \"./register.gaps.js\";\n\nexport type CommandRegistration = {\n id: string;\n register: (program: Command) => void;\n};\n\nexport const commandRegistry: CommandRegistration[] = [\n { id: \"auth\", register: (p) => registerAuthCommand(p) },\n { id: \"init\", register: (p) => registerInitCommand(p) },\n { id: \"reset\", register: (p) => registerResetCommand(p) },\n { id: \"add\", register: (p) => registerAddCommand(p) },\n { id: \"log\", register: (p) => registerLogCommand(p) },\n { id: \"profile\", register: (p) => registerProfileCommand(p) },\n { id: \"distill\", register: (p) => registerDistillCommand(p) },\n { id: \"digest\", register: (p) => registerDigestCommand(p) },\n { id: \"gaps\", register: (p) => registerGapsCommand(p) },\n { id: \"generate\", register: (p) => registerGenerateCommand(p) },\n { id: \"search\", register: (p) => registerSearchCommand(p) },\n { id: \"index\", register: (p) => registerIndexCommand(p) },\n { id: \"import\", register: (p) => registerImportCommand(p) },\n { id: \"status\", register: (p) => registerStatusCommand(p) },\n { id: \"context\", register: (p) => registerContextCommand(p) },\n { id: \"ask\", register: (p) => registerAskCommand(p) },\n { id: \"distribute\", register: (p) => registerDistributeCommand(p) },\n];\n\nexport function registerAllCommands(program: Command): void {\n for (const entry of commandRegistry) {\n entry.register(program);\n }\n}\n","import { Command } from \"commander\";\nimport { registerAllCommands } from \"./command-registry.js\";\n\nexport function buildProgram(): Command {\n const program = new Command()\n .name(\"pai\")\n .description(\"Personal AI Identity Provider — local-first AI agent identity & memory system\")\n .version(PKG_VERSION);\n\n registerAllCommands(program);\n return program;\n}\n","import { buildProgram } from \"./cli/build-program.js\";\n\nconst program = buildProgram();\n\nprocess.on(\"uncaughtException\", (error) => {\n console.error(\"[pai] Uncaught exception:\", error.message);\n process.exit(1);\n});\n\nprogram.parseAsync(process.argv).catch((err: Error) => {\n console.error(\"[pai] Error:\", err.message);\n process.exit(1);\n});\n"],"mappings":"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"}