UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

1,721 lines (1,357 loc) 46.7 kB
--- title: 2 Cli dimension: things category: features tags: agent, ai, frontend, installation, ontology related_dimensions: connections, events, groups, knowledge, people scope: global created: 2025-11-03 updated: 2025-11-03 version: 1.0.0 ai_context: | This document is part of the things dimension in the features category. Location: one/things/features/2-cli.md Purpose: Documents feature 2: one cli - bootstrap & ontology sync Related dimensions: connections, events, groups, knowledge, people For AI agents: Read this to understand 2 cli. --- # Feature 2: ONE CLI - Bootstrap & Ontology Sync **Version:** 2.0.0 **Package:** `npx oneie` **Status:** In Development **Priority:** Critical --- ## Overview The ONE CLI is a bootstrap and synchronization tool that: 1. **Copies ontology files** from the ONE framework to user projects 2. **Syncs agent definitions** to Claude Code integration 3. **Creates user & organization profiles** with ontology mappings 4. **Clones frontend repository** for website building 5. **Clones third-party documentation** for AI context **Core Command:** `npx oneie` --- ## User Flow ### Phase 1: Installation Initialization ```bash npx oneie init ``` **What happens:** ``` ╔══════════════════════════════════════════════════════════════════╗ ██████╗ ███╗ ██╗███████╗ Turn ideas into reality ██╔═══██╗████╗ ██║██╔════╝ ██║ ██║██╔██╗ ██║█████╗ https://one.ie ██║ ██║██║╚██╗██║██╔══╝ ╚██████╔╝██║ ╚████║███████╗ npx oneie ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚══════════════════════════════════════════════════════════════════╝ Welcome to ONE! ONE is organized by groups which have their own people, things, connections, events, and knowledge. Groups can have subgroups. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📥 Downloading ONE Framework... Copied 100+ ontology files from /one/ Synced 12 agent definitions to .claude/agents/ Copied Claude Code hooks and commands ONE Setup Complete! 🎉 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 👤 Account Setup What's your name? Anthony O'Connell What's your email? anthony@one.ie Created your profile in one/people/anthony-o-connell.md Account Setup Complete! 🎉 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🏢 Organization Setup What's your organization name? Acme Corp Installation identifier: acme Domain: acme.com Creating your installation folder... /acme/groups/ /acme/people/ /acme/things/ /acme/connections/ /acme/events/ /acme/knowledge/ Created installation README Updated .env.local with INSTALLATION_NAME=acme Organization Setup Complete! 🎉 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🌐 Next: Create Your First Group Visit your web app to create groups in the database: cd web && bun run dev Then run this to generate group documentation: npx oneie create-group-docs You're all set! 🚀 ``` --- ### Phase 2: Website Setup (Optional) ``` 🌐 Step 4: Website Setup Would you like to build a website? (yes/no): yes Cloning frontend repository... git clone https://github.com/one-ie/web.git /web Installed dependencies (bun install) Created web/.env.local with your installation details Your website is ready at: http://localhost:4321 Run: cd web && bun dev ``` ### Phase 3: Third-Party Docs (Optional) ``` 📖 Step 5: Third-Party Documentation Would you like to clone third-party docs for AI context? Don't do this unless you plan to start development. You can add later if you like. (yes/no): yes ⚠️ This will download complete documentation repositories (~500MB) Only needed if you plan to do development with offline AI context. Cloning documentation repositories... Astro docs /docs/astro Convex docs /docs/convex Effect.ts docs /docs/effect React docs /docs/react Tailwind CSS docs /docs/tailwind Documentation cloned! AI agents can now reference these offline. ``` ### Phase 4: Complete ``` Setup Complete! Your ONE environment is ready: 📁 Project Structure: /one/ Global ontology (100+ files) /acme/ Installation folder (your private docs) ├── groups/ Hierarchical group docs ├── people/ People profiles ├── things/ Entity definitions ├── connections/ Relationship definitions ├── events/ Event specifications └── knowledge/ RAG, AI, embeddings /web/ Astro + React website /docs/ Third-party documentation /.claude/ AI agent integration 🧑 Your Profile: Name: Anthony O'Connell Email: anthony@one.ie Username: anthony Role: org_owner File: one/people/anthony-o-connell.md 🏢 Your Organization: Name: ONE Platform Slug: one Domain: one.ie File: /acme/groups/one.md Installation: /acme/ 🚀 Next Steps: 1. Start building: cd web && bun run dev 2. Use AI agents: claude 3. Read the docs: cat one/knowledge/ontology.md 4. Create your first feature: /build Happy building! 🎉 ``` --- ## Technical Implementation ### 1. Installation Folder Initialization **File:** `cli/src/commands/init.ts` **Purpose:** Create installation folder with 6-dimension structure **Configuration:** ```yaml # ONE CLI - Installation Configuration # cli/config.yaml # Claude Code Integration (global) claude_folders: - .claude/agents - .claude/commands - .claude/hooks # Global Ontology (copy from /one/* to user's project) one_folders: - one/**/* # Installation Folder Structure (created per installation) installation_structure: - groups/ # Hierarchical group docs (supports nesting) - people/ # People profiles - things/ # Entity definitions - connections/ # Relationship definitions - events/ # Event specifications - knowledge/ # RAG, AI, embeddings # File extensions to copy allowed_extensions: - .md - .yaml - .yml - .json - .sh # Files/folders to exclude exclude_patterns: - "*/node_modules/*" - "*/.git/*" - "*/dist/*" - "*/build/*" - "*/.DS_Store" - "*/package-lock.json" - "*/bun.lockb" # Environment variables env_vars: - INSTALLATION_NAME # Set during init - INSTALLATION_ENV # Optional: dev, staging, prod ``` **Implementation:** ```typescript // cli/src/commands/init.ts import fs from "fs/promises"; import path from "path"; import prompts from "prompts"; import { syncOntologyFiles } from "../utils/sync-ontology"; import { createInstallationFolder } from "../utils/installation-setup"; import { updateEnvFile } from "../utils/env-updater"; import { updateGitignore } from "../utils/gitignore-updater"; export async function initCommand() { // 1. Prompt for installation details const { orgName, installationId } = await prompts([ { type: "text", name: "orgName", message: "What is your organization name?", validate: (value) => value.length > 0 ? true : "Organization name cannot be empty", }, { type: "text", name: "installationId", message: "Installation identifier (lowercase, hyphens only):", validate: (value) => /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value) ? true : "Must be lowercase letters, numbers, and hyphens only", }, ]); // 2. Create installation folder console.log(`\nCreating installation folder: /${installationId}`); await createInstallationFolder(installationId, orgName); // 3. Sync global ontology console.log("\nCopying ontology files from /one/*..."); await syncOntologyFiles(); // 4. Update environment console.log( "\n✓ Updated .env.local with INSTALLATION_NAME=" + installationId, ); await updateEnvFile({ INSTALLATION_NAME: installationId }); // 5. Update .gitignore console.log("✓ Updated .gitignore"); await updateGitignore(installationId); console.log("\n🎉 Installation initialized!"); console.log(`\nYour private docs go in /${installationId}/`); console.log("\nNext steps:"); console.log(" 1. Create your first group in the database (via web UI)"); console.log( ` 2. Add group-specific docs: /${installationId}/groups/<group-slug>/`, ); console.log(" 3. Run: npx oneie dev"); } // cli/src/utils/installation-setup.ts import fs from "fs/promises"; import path from "path"; const INSTALLATION_DIMENSIONS = [ "groups", "people", "things", "connections", "events", "knowledge", ]; export async function createInstallationFolder( installationId: string, orgName: string, ) { const installationPath = path.join(process.cwd(), installationId); // Create root folder await fs.mkdir(installationPath, { recursive: true }); // Create 6-dimension folders for (const dimension of INSTALLATION_DIMENSIONS) { const dimensionPath = path.join(installationPath, dimension); await fs.mkdir(dimensionPath, { recursive: true }); console.log(`✓ /${installationId}/${dimension}/`); } // Create README const readmeContent = `# ${orgName} - ONE Installation **Installation ID:** \`${installationId}\` **Created:** ${new Date().toISOString().split("T")[0]} --- ## Structure This installation folder follows the 6-dimension ontology: - **groups/** - Hierarchical group documentation (supports nesting) - **people/** - People profiles and roles - **things/** - Entity definitions and specs - **connections/** - Relationship definitions - **events/** - Event specifications - **knowledge/** - RAG, AI, embeddings ## File Resolution Files in this folder override global templates in \`/one/\`: 1. \`/${installationId}/groups/<group-path>/<file>\` (most specific) 2. \`/${installationId}/<dimension>/<file>\` (installation-wide) 3. \`/one/<dimension>/<file>\` (global fallback) ## Hierarchical Groups Create nested group documentation matching your database structure: \`\`\`bash mkdir -p /${installationId}/groups/engineering/frontend echo "# Frontend Practices" > /${installationId}/groups/engineering/frontend/practices.md \`\`\` ## Security - **DO NOT** store secrets or credentials here - Use \`.env.local\` for environment variables - This folder is for documentation and configuration only - Optionally exclude from git (see .gitignore) --- **Built with ONE Platform:** https://one.ie `; await fs.writeFile( path.join(installationPath, "README.md"), readmeContent, "utf-8", ); console.log(`✓ Created /${installationId}/README.md`); return installationPath; } ``` --- ### 2. Sync Agent Definitions **Sync:** `one/things/agents/* .claude/agents/*` **Implementation:** ```typescript // cli/src/sync-agents.ts import fs from "fs/promises"; import path from "path"; import { glob } from "glob"; export async function syncAgentDefinitions() { const sourceDir = path.join(__dirname, "../../one/things/agents"); const targetDir = path.join(process.cwd(), ".claude/agents"); // Create target directory await fs.mkdir(targetDir, { recursive: true }); // Find all agent files const agentFiles = await glob("*.md", { cwd: sourceDir }); // Copy each agent file for (const file of agentFiles) { const sourcePath = path.join(sourceDir, file); const targetPath = path.join(targetDir, file); await fs.copyFile(sourcePath, targetPath); } console.log(`✓ Synced ${agentFiles.length} agent definitions`); return { agentsSynced: agentFiles.length, agents: agentFiles.map((f) => path.basename(f, ".md")), }; } ``` **Also copy .claude/\* to user:** ```typescript // cli/src/copy-claude-config.ts import fs from "fs/promises"; import path from "path"; export async function copyClaudeConfig() { const sourceDir = path.join(__dirname, "../../.claude"); const targetDir = path.join(process.cwd(), ".claude"); // Create target directory await fs.mkdir(targetDir, { recursive: true }); // Copy hooks/ await fs.cp(path.join(sourceDir, "hooks"), path.join(targetDir, "hooks"), { recursive: true, }); // Copy commands/ await fs.cp( path.join(sourceDir, "commands"), path.join(targetDir, "commands"), { recursive: true }, ); // Copy agents/ (already done by syncAgentDefinitions) // Copy settings if exists const settingsPath = path.join(sourceDir, "settings.json"); if (await fs.stat(settingsPath).catch(() => null)) { await fs.copyFile(settingsPath, path.join(targetDir, "settings.json")); } console.log("✓ Copied Claude Code configuration"); } ``` --- ### 3. Create User & Organization Profiles **User Profile:** `one/people/{name}.md` **Template:** ```typescript // cli/src/create-user-profile.ts import fs from "fs/promises"; import path from "path"; interface UserProfile { name: string; email: string; username: string; website?: string; } export async function createUserProfile(profile: UserProfile) { // Generate filename from name (lowercase, hyphenated) const filename = profile.name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); const filePath = path.join(process.cwd(), `one/people/${filename}.md`); // Create directory await fs.mkdir(path.dirname(filePath), { recursive: true }); // Generate content const content = `# ${profile.name} **Role:** Organization Owner (\`org_owner\`) **Email:** ${profile.email} **Username:** ${profile.username} ${profile.website ? `**Website:** ${profile.website}\n` : ""} --- ## Identity - **Name:** ${profile.name} - **Email:** ${profile.email} - **Username:** ${profile.username} - **Role:** \`org_owner\` ${profile.website ? `- **Website:** ${profile.website}\n` : ""} --- ## The Person Entity \`\`\`typescript { type: "creator", name: "${profile.name}", properties: { role: "org_owner", email: "${profile.email}", username: "${profile.username}", displayName: "${profile.name}", bio: "Organization owner", ${profile.website ? `website: "${profile.website}",` : ""} // Permissions permissions: ["*"], // All permissions as org owner // Organization context organizationId: null, // Set when linked to organization }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), } \`\`\` --- ## Ownership Connections ### Owns Organization \`${profile.username}\` \`org\` via \`owns\` \`\`\`typescript { fromThingId: ${profile.username}Id, toThingId: orgId, relationshipType: "owns", metadata: { ownershipPercentage: 100, since: "${new Date().toISOString().split("T")[0]}", }, createdAt: Date.now(), } \`\`\` ### Member of Organization \`${profile.username}\` → \`org\` via \`member_of\` \`\`\`typescript { fromThingId: ${profile.username}Id, toThingId: orgId, relationshipType: "member_of", metadata: { role: "org_owner", permissions: ["*"], // All permissions joinedAt: Date.now(), }, createdAt: Date.now(), } \`\`\` --- ## Key Principles - **Organization Owner** - Has full control over the organization - **All Permissions** - \`permissions: ["*"]\` grants access to everything - **Ontology Mapping** - Represented as a \`creator\` thing with role metadata - **Connection-Based Access** - Access granted via \`member_of\` connection --- ## See Also - [Group Profile](../groups/${filename}.md) - [People Roles](./people.md) - [Groups](../groups/groups.md) `; // Write file await fs.writeFile(filePath, content, "utf-8"); console.log(`✓ Created ${filePath}`); return filePath; } ``` **Group Profile:** `/{installation-name}/groups/{slug}.md` ```typescript // cli/src/create-group-profile.ts import fs from "fs/promises"; import path from "path"; interface GroupProfile { name: string; slug: string; domain: string; ownerName: string; ownerUsername: string; installationName: string; parentGroupSlug?: string; // For hierarchical groups groupType?: string; // One of 6 group types } export async function createGroupProfile(profile: GroupProfile) { // Determine file path based on hierarchy let groupPath = profile.slug; if (profile.parentGroupSlug) { groupPath = `${profile.parentGroupSlug}/${profile.slug}`; } const filePath = path.join( process.cwd(), `${profile.installationName}/groups/${groupPath}.md`, ); // Create directory (supports nested hierarchies) await fs.mkdir(path.dirname(filePath), { recursive: true }); // Generate content const content = `# ${profile.name} **Slug:** \`${profile.slug}\` **Domain:** ${profile.domain} **Owner:** ${profile.ownerName} (100%) **Status:** Active **Plan:** Enterprise ${profile.parentGroupSlug ? `**Parent Group:** ${profile.parentGroupSlug}` : "**Type:** Top-level organization"} --- ## Identity - **Name:** ${profile.name} - **Slug:** \`${profile.slug}\` - **Domain:** ${profile.domain} - **Owner:** ${profile.ownerName} - **Status:** Active - **Plan:** Enterprise - **Group Type:** ${profile.groupType || "organization"} ${profile.parentGroupSlug ? `- **Parent Group:** ${profile.parentGroupSlug}` : ""} --- ## The Group Entity \`\`\`typescript { _id: Id<"groups">, name: "${profile.name}", type: "${profile.groupType || "organization"}", // One of 6 group types parentGroupId: ${profile.parentGroupSlug ? `parentGroupId` : "undefined"}, // ${profile.parentGroupSlug ? "Nested group" : "Top-level group"} properties: { // Identity slug: "${profile.slug}", domain: "${profile.domain}", description: "Group created via ONE CLI", installationName: "${profile.installationName}", // Status & Plan plan: "enterprise", // Limits & Usage limits: { users: 1000, storage: 100000, apiCalls: -1, // Unlimited cycles: -1, // Unlimited }, usage: { users: 0, storage: 0, apiCalls: 0, cycles: 0, }, // Settings settings: { allowSignups: true, requireEmailVerification: true, enableTwoFactor: true, cycleEnabled: true, }, // Public info website: "https://${profile.domain}", }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), } \`\`\` --- ## File Resolution for This Group **Hierarchical file lookup:** 1. \`/${profile.installationName}/groups/${groupPath}/<file>\` (most specific) ${profile.parentGroupSlug ? `2. \`/${profile.installationName}/groups/${profile.parentGroupSlug}/<file>\` (parent group)` : ""} ${profile.parentGroupSlug ? `3. \`/${profile.installationName}/<file>\` (installation-wide)` : `2. \`/${profile.installationName}/<file>\` (installation-wide)`} ${profile.parentGroupSlug ? `4. \`/one/<file>\` (global fallback)` : `3. \`/one/<file>\` (global fallback)`} **Example:** \`\`\`bash # Create group-specific documentation mkdir -p /${profile.installationName}/groups/${groupPath} echo "# ${profile.name} Practices" > /${profile.installationName}/groups/${groupPath}/practices.md \`\`\` --- ## Ownership Connections ### ${profile.ownerName} Owns ${profile.name} \`${profile.ownerUsername}\` \`${profile.slug}\` via \`owns\` \`\`\`typescript { fromThingId: ${profile.ownerUsername}Id, toThingId: ${profile.slug}Id, relationshipType: "owns", metadata: { ownershipPercentage: 100, since: "${new Date().toISOString().split("T")[0]}", }, createdAt: Date.now(), } \`\`\` ### ${profile.ownerName} is Member of ${profile.name} \`${profile.ownerUsername}\` → \`${profile.slug}\` via \`member_of\` \`\`\`typescript { fromThingId: ${profile.ownerUsername}Id, toThingId: ${profile.slug}Id, relationshipType: "member_of", metadata: { role: "org_owner", permissions: ["*"], // All permissions joinedAt: Date.now(), }, createdAt: Date.now(), } \`\`\` --- ## Key Principles - **Installation-Based Multi-Tenancy** - Group docs stored in installation folder - **Hierarchical Groups** - ${profile.parentGroupSlug ? "This is a nested subgroup" : "Top-level organization, can have subgroups"} - **Owner Control** - ${profile.ownerName} has full control (100% ownership) - **Enterprise Plan** - Unlimited resources for growth - **Ontology Mapping** - Dimension 1 (Groups) in the 6-dimension model - **6 Group Types** - friend_circle, business, community, dao, government, organization --- ## Creating Subgroups ${profile.parentGroupSlug ? `This group can have its own subgroups:` : `Create subgroups for this organization:`} \`\`\`bash # Example: Create engineering/frontend subgroup npx oneie create-group \\ --name "Frontend Team" \\ --slug "frontend" \\ --parent "${profile.slug}" \\ --type "business" # This creates: /${profile.installationName}/groups/${profile.slug}/frontend/frontend.md \`\`\` --- ## See Also - [Owner Profile](../../people/${profile.ownerUsername}.md) - [Installation README](../../README.md) - [Multi-Tenancy Plan](../../../one/things/plans/group-folder-multi-tenancy.md) `; // Write file await fs.writeFile(filePath, content, "utf-8"); console.log(`✓ Created ${filePath}`); return filePath; } ``` --- ### 4. Create Group Documentation (After Database Groups Exist) **Command:** `npx oneie create-group-docs` **Purpose:** Create markdown documentation for groups that exist in the database ```typescript // cli/src/commands/create-group-docs.ts import fs from "fs/promises"; import path from "path"; import prompts from "prompts"; import { ConvexHttpClient } from "convex/browser"; import { api } from "../../convex/_generated/api"; export async function createGroupDocsCommand() { const installationName = process.env.INSTALLATION_NAME; if (!installationName) { console.error("❌ INSTALLATION_NAME not set in .env.local"); console.log("Run: npx oneie init"); process.exit(1); } // 1. Connect to Convex backend const convexUrl = process.env.PUBLIC_CONVEX_URL; if (!convexUrl) { console.error("❌ PUBLIC_CONVEX_URL not set in .env.local"); process.exit(1); } const convex = new ConvexHttpClient(convexUrl); // 2. Fetch all groups from database console.log("Fetching groups from database..."); const groups = await convex.query(api.queries.groups.list, {}); if (groups.length === 0) { console.log("No groups found in database."); console.log("Create your first group in the web UI: /groups/new"); return; } // 3. Display groups and let user select const { selectedGroupIds } = await prompts({ type: "multiselect", name: "selectedGroupIds", message: "Select groups to create documentation for:", choices: groups.map((g) => ({ title: `${g.name} (${g.properties.slug})${g.parentGroupId ? " → subgroup" : ""}`, value: g._id, })), }); if (!selectedGroupIds || selectedGroupIds.length === 0) { console.log("No groups selected. Exiting."); return; } // 4. Create documentation for each selected group for (const groupId of selectedGroupIds) { const group = groups.find((g) => g._id === groupId); if (!group) continue; // Build hierarchical path const groupPath = await buildGroupPath(group, groups); const docsPath = path.join( process.cwd(), installationName, "groups", groupPath, "README.md", ); await fs.mkdir(path.dirname(docsPath), { recursive: true }); const content = `# ${group.name} **Slug:** \`${group.properties.slug}\` **Type:** ${group.type} ${group.parentGroupId ? `**Parent:** ${getParentName(group.parentGroupId, groups)}` : "**Level:** Top-level"} **Created:** ${new Date(group.createdAt).toISOString().split("T")[0]} --- ## About This folder contains documentation specific to the **${group.name}** group. ## File Resolution When AI agents or the web app load documentation for this group, files are resolved in this order: 1. \`/${installationName}/groups/${groupPath}/<file>\` (most specific) ${group.parentGroupId ? `2. \`/${installationName}/groups/${getParentPath(group.parentGroupId, groups)}/<file>\` (parent group)\n` : ""}${group.parentGroupId ? `3. \`/${installationName}/<file>\` (installation-wide)\n` : `2. \`/${installationName}/<file>\` (installation-wide)\n`}${group.parentGroupId ? `4. \`/one/<file>\` (global fallback)` : `3. \`/one/<file>\` (global fallback)`} ## Add Custom Documentation Create markdown files in this folder for group-specific content: \`\`\`bash # Practices and guidelines echo "# ${group.name} Practices" > ${docsPath.replace("README.md", "practices.md")} # Meeting notes echo "# Sprint Planning" > ${docsPath.replace("README.md", "sprint-planning.md")} # Technical specs echo "# Architecture" > ${docsPath.replace("README.md", "architecture.md")} \`\`\` ## Database Entity \`\`\`typescript { _id: "${group._id}", name: "${group.name}", type: "${group.type}", parentGroupId: ${group.parentGroupId ? `"${group.parentGroupId}"` : "undefined"}, properties: { slug: "${group.properties.slug}", installationName: "${installationName}", // ... } } \`\`\` --- **Updated:** ${new Date().toISOString().split("T")[0]} `; await fs.writeFile(docsPath, content, "utf-8"); console.log(`✓ Created ${docsPath}`); } console.log("\n✅ Group documentation created!"); console.log( "Add custom markdown files to these folders to override global templates.", ); } // Helper: Build hierarchical path for group async function buildGroupPath(group: any, allGroups: any[]): Promise<string> { const segments = [group.properties.slug]; let currentParentId = group.parentGroupId; while (currentParentId) { const parent = allGroups.find((g) => g._id === currentParentId); if (parent) { segments.unshift(parent.properties.slug); currentParentId = parent.parentGroupId; } else { break; } } return segments.join("/"); } // Helper: Get parent group name function getParentName(parentId: string, allGroups: any[]): string { const parent = allGroups.find((g) => g._id === parentId); return parent ? parent.name : "Unknown"; } // Helper: Get parent group path function getParentPath(parentId: string, allGroups: any[]): string { const parent = allGroups.find((g) => g._id === parentId); if (!parent) return ""; // Recursively build parent path return buildGroupPath(parent, allGroups); } ``` --- ### 5. File Resolution with Hierarchical Groups **File:** `cli/src/utils/file-resolver.ts` **Purpose:** Resolve markdown files with hierarchical group support ```typescript // cli/src/utils/file-resolver.ts import fs from "fs/promises"; import path from "path"; import { Id } from "../../convex/_generated/dataModel"; interface ResolveOptions { installationName: string; groupId?: Id<"groups">; convexClient: any; // ConvexHttpClient } export async function resolveFile( relativePath: string, options: ResolveOptions, ): Promise<string | null> { const { installationName, groupId, convexClient } = options; // If groupId provided, resolve hierarchically if (groupId) { const groupPath = await getGroupPath(groupId, convexClient); // 1. Check most-specific group first const groupFile = path.join( process.cwd(), installationName, "groups", groupPath, relativePath, ); if (await fileExists(groupFile)) { return await fs.readFile(groupFile, "utf-8"); } // 2. Walk up parent groups let currentGroupId = groupId; while (currentGroupId) { const group = await convexClient.query(api.queries.groups.get, { id: currentGroupId, }); if (group?.parentGroupId) { const parentPath = await getGroupPath( group.parentGroupId, convexClient, ); const parentFile = path.join( process.cwd(), installationName, "groups", parentPath, relativePath, ); if (await fileExists(parentFile)) { return await fs.readFile(parentFile, "utf-8"); } currentGroupId = group.parentGroupId; } else { break; } } } // 3. Check installation root (non-group-specific) const installFile = path.join(process.cwd(), installationName, relativePath); if (await fileExists(installFile)) { return await fs.readFile(installFile, "utf-8"); } // 4. Fallback to global template const globalFile = path.join(process.cwd(), "one", relativePath); if (await fileExists(globalFile)) { return await fs.readFile(globalFile, "utf-8"); } // 5. Not found return null; } // Helper: Get group's full path (e.g., "engineering/frontend") async function getGroupPath( groupId: Id<"groups">, convexClient: any, ): Promise<string> { const group = await convexClient.query(api.queries.groups.get, { id: groupId, }); if (!group) throw new Error("Group not found"); const segments: string[] = [group.properties.slug]; // Walk up to root let currentParentId = group.parentGroupId; while (currentParentId) { const parent = await convexClient.query(api.queries.groups.get, { id: currentParentId, }); if (parent) { segments.unshift(parent.properties.slug); currentParentId = parent.parentGroupId; } else { break; } } return segments.join("/"); } // Helper: Check if file exists async function fileExists(filePath: string): Promise<boolean> { try { await fs.access(filePath); return true; } catch { return false; } } // Example usage: // const content = await resolveFile("practices.md", { // installationName: "acme", // groupId: "frontend-group-id" as Id<"groups">, // convexClient: convex // }); // // Checks in order: // 1. /acme/groups/engineering/frontend/practices.md // 2. /acme/groups/engineering/practices.md (parent) // 3. /acme/practices.md (installation root) // 4. /one/practices.md (global fallback) ``` --- ### 6. Clone Frontend Repository ```typescript // cli/src/clone-frontend.ts import { exec } from "child_process"; import { promisify } from "util"; import fs from "fs/promises"; import path from "path"; const execAsync = promisify(exec); export async function cloneFrontend(orgProfile: { name: string; slug: string; domain: string; }) { const targetDir = path.join(process.cwd(), "web"); // Check if frontend already exists if (await fs.stat(targetDir).catch(() => null)) { console.log("⚠️ Frontend directory already exists, skipping clone"); return { alreadyExists: true }; } // Clone repository console.log("Cloning frontend repository..."); await execAsync("git clone https://github.com/one-ie/web.git web", { cwd: process.cwd(), }); console.log("✓ Cloned frontend repository"); // Install dependencies console.log("Installing dependencies..."); await execAsync("bun install", { cwd: targetDir }); console.log("✓ Installed dependencies"); // Create .env.local with organization details const envContent = `# ONE Configuration # Generated by CLI on ${new Date().toISOString()} # Organization PUBLIC_ORG_NAME="${orgProfile.name}" PUBLIC_ORG_SLUG="${orgProfile.slug}" PUBLIC_ORG_DOMAIN="${orgProfile.domain}" # Convex Backend (update with your deployment) PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud CONVEX_DEPLOYMENT=dev:your-deployment # Better Auth BETTER_AUTH_SECRET= BETTER_AUTH_URL=http://localhost:4321 # OAuth (optional) GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= `; await fs.writeFile(path.join(targetDir, ".env.local"), envContent, "utf-8"); console.log("✓ Created .env.local"); return { cloned: true }; } ``` --- ### 5. Clone Third-Party Documentation ```typescript // cli/src/clone-docs.ts import { exec } from "child_process"; import { promisify } from "util"; import fs from "fs/promises"; import path from "path"; const execAsync = promisify(exec); interface DocRepo { name: string; repo: string; branch?: string; } const DOC_REPOS: DocRepo[] = [ { name: "astro", repo: "https://github.com/withastro/docs.git", branch: "main", }, { name: "convex", repo: "https://github.com/get-convex/convex-docs.git", branch: "main", }, { name: "effect", repo: "https://github.com/Effect-TS/website.git", branch: "main", }, { name: "react", repo: "https://github.com/reactjs/react.dev.git", branch: "main", }, { name: "tailwind", repo: "https://github.com/tailwindlabs/tailwindcss.com.git", branch: "main", }, ]; export async function cloneThirdPartyDocs() { const docsDir = path.join(process.cwd(), "docs"); // Create docs directory await fs.mkdir(docsDir, { recursive: true }); const results = []; for (const doc of DOC_REPOS) { const targetDir = path.join(docsDir, doc.name); // Check if already exists if (await fs.stat(targetDir).catch(() => null)) { console.log(`⚠️ ${doc.name} docs already exist, skipping`); results.push({ name: doc.name, skipped: true }); continue; } // Clone repository const cloneCmd = doc.branch ? `git clone --depth 1 --branch ${doc.branch} ${doc.repo} ${doc.name}` : `git clone --depth 1 ${doc.repo} ${doc.name}`; try { await execAsync(cloneCmd, { cwd: docsDir }); console.log(`✓ ${doc.name} docs /docs/${doc.name}`); results.push({ name: doc.name, cloned: true }); } catch (error) { console.error(`✗ Failed to clone ${doc.name}: ${error.message}`); results.push({ name: doc.name, error: error.message }); } } return results; } ``` --- ## CLI Entry Point **File:** `cli/src/index.ts` ```typescript #!/usr/bin/env node import prompts from "prompts"; import chalk from "chalk"; import ora from "ora"; import { syncOntologyFiles } from "./sync-ontology"; import { syncAgentDefinitions } from "./sync-agents"; import { copyClaudeConfig } from "./copy-claude-config"; import { createUserProfile } from "./create-user-profile"; import { createOrgProfile } from "./create-org-profile"; import { cloneFrontend } from "./clone-frontend"; import { cloneThirdPartyDocs } from "./clone-docs"; import { displayBanner } from "./banner"; async function main() { // Display welcome banner displayBanner(); console.log(chalk.cyan("\n✨ Welcome to ONE Platform!\n")); console.log("Let's set up your environment with the 6-dimension ontology.\n"); // Step 1: User profile console.log(chalk.bold("🧑 Step 1: Tell us about yourself\n")); const userAnswers = await prompts([ { type: "text", name: "name", message: "What's your name?", validate: (value) => (value.length > 0 ? true : "Name cannot be empty"), }, { type: "text", name: "email", message: "What's your email address?", validate: (value) => value.includes("@") ? true : "Please enter a valid email", }, { type: "text", name: "username", message: "What username would you like?", validate: (value) => /^[a-z0-9_-]+$/.test(value) ? true : "Username must be lowercase letters, numbers, hyphens, or underscores", }, { type: "text", name: "website", message: "What's your website URL? (optional)", initial: "", }, ]); // Step 2: Organization profile console.log(chalk.bold("\n🏢 Step 2: Organization Setup\n")); const orgAnswers = await prompts([ { type: "text", name: "name", message: "What's your organization name?", validate: (value) => value.length > 0 ? true : "Organization name cannot be empty", }, { type: "text", name: "slug", message: "Organization slug (URL-friendly)?", validate: (value) => /^[a-z0-9-]+$/.test(value) ? true : "Slug must be lowercase letters, numbers, and hyphens", }, { type: "text", name: "domain", message: "Organization domain?", validate: (value) => value.includes(".") ? true : "Please enter a valid domain", }, ]); // Step 3: Sync ontology console.log(chalk.bold("\n📚 Step 3: Syncing ONE Ontology\n")); let spinner = ora("Copying ontology files from /one/*...").start(); const ontologyResult = await syncOntologyFiles(); spinner.succeed(`Copied ${ontologyResult.filesCopied} ontology files`); spinner = ora("Syncing agent definitions...").start(); const agentsResult = await syncAgentDefinitions(); spinner.succeed( `Synced ${agentsResult.agentsSynced} agent definitions to .claude/agents/`, ); spinner = ora("Copying Claude Code configuration...").start(); await copyClaudeConfig(); spinner.succeed("Synced .claude/hooks/ and .claude/commands/"); // Step 4: Create profiles console.log(chalk.bold("\nCreating your profile...\n")); spinner = ora("Creating user profile...").start(); const userProfilePath = await createUserProfile({ name: userAnswers.name, email: userAnswers.email, username: userAnswers.username, website: userAnswers.website || undefined, }); spinner.succeed(`Created ${userProfilePath}`); spinner = ora("Creating group profile...").start(); const groupProfilePath = await createGroupProfile({ name: orgAnswers.name, slug: orgAnswers.slug, domain: orgAnswers.domain, ownerName: userAnswers.name, ownerUsername: userAnswers.username, }); spinner.succeed(`Created ${groupProfilePath}`); spinner = ora("Linking user to group...").start(); spinner.succeed(`Linked ${userAnswers.username} owns ${orgAnswers.name}`); spinner.succeed( `Linked ${userAnswers.username} member_of ${orgAnswers.name} (role: group_owner)`, ); // Step 5: Website setup (optional) console.log(chalk.bold("\n🌐 Step 4: Website Setup\n")); const { buildWebsite } = await prompts({ type: "confirm", name: "buildWebsite", message: "Would you like to build a website?", initial: true, }); if (buildWebsite) { spinner = ora("Cloning frontend repository...").start(); const frontendResult = await cloneFrontend({ name: orgAnswers.name, slug: orgAnswers.slug, domain: orgAnswers.domain, }); if (frontendResult.alreadyExists) { spinner.warn("Frontend directory already exists"); } else { spinner.succeed("Cloned and configured frontend"); console.log( chalk.gray("\nYour website is ready at: http://localhost:4321"), ); console.log(chalk.gray("Run: cd frontend && bun run dev\n")); } } // Step 6: Third-party docs (optional) console.log(chalk.bold("\n📖 Step 5: Third-Party Documentation\n")); const { cloneDocs } = await prompts({ type: "confirm", name: "cloneDocs", message: "Would you like to clone third-party docs for AI context?", initial: true, }); if (cloneDocs) { spinner = ora("Cloning documentation repositories...").start(); const docsResults = await cloneThirdPartyDocs(); const clonedCount = docsResults.filter((r) => r.cloned).length; spinner.succeed(`Cloned ${clonedCount} documentation repositories`); } // Step 7: Complete console.log(chalk.bold.green("\n✅ Setup Complete!\n")); console.log(chalk.bold("Your ONE environment is ready:\n")); console.log(chalk.cyan("📁 Project Structure:")); console.log(" /one/ 6-dimension ontology (100+ files)"); if (buildWebsite) { console.log(" /frontend/ Astro + React website"); } if (cloneDocs) { console.log(" /docs/ Third-party documentation"); } console.log(" /.claude/ AI agent integration"); console.log(" /cli/ CLI configuration\n"); console.log(chalk.cyan("🧑 Your Profile:")); console.log(` Name: ${userAnswers.name}`); console.log(` Email: ${userAnswers.email}`); console.log(` Username: ${userAnswers.username}`); console.log(` Role: org_owner`); console.log(` File: ${userProfilePath}\n`); console.log(chalk.cyan("🏢 Your Group:")); console.log(` Name: ${orgAnswers.name}`); console.log(` Slug: ${orgAnswers.slug}`); console.log(` Domain: ${orgAnswers.domain}`); console.log(` File: ${groupProfilePath}\n`); console.log(chalk.bold("🚀 Next Steps:\n")); if (buildWebsite) { console.log("1. Start building:"); console.log(chalk.gray(" cd frontend && bun run dev\n")); } console.log("2. Use AI agents:"); console.log(chalk.gray(" claude\n")); console.log("3. Read the docs:"); console.log(chalk.gray(" cat one/knowledge/ontology.md\n")); console.log("4. Create your first feature:"); console.log(chalk.gray(" /one\n")); console.log(chalk.bold.green("Happy building! 🎉\n")); } main().catch((error) => { console.error(chalk.red("\n✗ Error:"), error.message); process.exit(1); }); ``` --- ## Package Configuration **File:** `cli/package.json` ```json { "name": "oneie", "version": "2.0.0", "description": "ONE CLI - Bootstrap & Ontology Sync", "bin": { "oneie": "./dist/index.js" }, "scripts": { "build": "tsc", "dev": "tsc --watch", "prepublishOnly": "npm run build" }, "keywords": ["one", "ontology", "cli", "bootstrap", "6-dimension"], "author": "Anthony O'Connell <anthony@one.ie>", "license": "MIT", "dependencies": { "prompts": "^2.4.2", "chalk": "^5.3.0", "ora": "^8.0.1", "yaml": "^2.3.4", "glob": "^10.3.10" }, "devDependencies": { "@types/node": "^20.11.0", "@types/prompts": "^2.4.9", "typescript": "^5.3.3" }, "files": ["dist", "README.md"] } ``` --- ## Testing ### Manual Test Plan 1. **Test ontology sync:** ```bash npx oneie # Verify all /one/* files copied # Verify folders.yaml updated ``` 2. **Test agent sync:** ```bash ls .claude/agents/ # Should see: agent-director.md, agent-backend.md, etc. ``` 3. **Test user profile creation:** ```bash cat one/people/anthony-o-connell.md # Verify correct formatting and connections ``` 4. **Test group profile creation:** ```bash cat one/groups/one.md # Verify correct formatting and connections ``` 5. **Test frontend clone:** ```bash cd frontend && bun run dev # Should start on localhost:4321 ``` 6. **Test docs clone:** ```bash ls docs/ # Should see: astro/, convex/, effect/, react/, tailwind/ ``` --- ## Success Metrics - All 100+ ontology files copied from `/one/*` - Installation folder created with 6-dimension structure - 12 agent definitions synced to `.claude/agents/` - User profile created with correct ontology mapping - `INSTALLATION_NAME` set in `.env.local` - `.gitignore` updated to exclude installation folder (optional) - Group documentation command available for database groups - Hierarchical file resolution working (group parent installation global) - Frontend cloned and configured with installation details - Third-party docs cloned for offline AI context (optional) - Complete setup in < 5 minutes --- ## Future Enhancements ### Phase 2: Incremental Sync ```bash npx oneie sync # Sync only changed files from upstream /one/ to local # Preserves installation folder customizations ``` ### Phase 3: Multi-Installation Support ```bash npx oneie switch acme-staging # Switch between multiple installations in same project # Useful for dev/staging/prod environments ``` ### Phase 4: Group Documentation Templates ```bash npx oneie create-group-docs --template=engineering npx oneie create-group-docs --template=marketing # Pre-populated documentation templates for common group types ``` ### Phase 5: Cloud Sync (KV/R2) ```bash npx oneie sync --upload # Push installation docs to Cloudflare KV npx oneie sync --download # Pull from Cloudflare KV # Enable runtime doc updates without rebuilds ``` --- ## Related Documentation - [Installation Folder Multi-Tenancy Plan](../plans/group-folder-multi-tenancy.md) - Complete architecture - [CLI Overview](../cli.md) - Complete CLI architecture - [Ontology](../../knowledge/ontology.md) - 6-dimension ontology - [People](../../people/people.md) - People & roles - [Groups](../../groups/groups.md) - Group structure (6 types, hierarchical) - [Agents](../agents/agent-director.md) - AI agent definitions - [File Resolution](../../knowledge/file-resolution.md) - Hierarchical file loading logic --- ## Key Differences from Legacy **OLD (Pre-v2.0.0):** - Created `one/groups/{name}.md` immediately - No installation folder concept - No hierarchical group documentation - Groups were filesystem folders only **NEW (v2.0.0+):** - Creates installation folder (e.g., `/acme/`) with 6-dimension structure - Groups are database entities with `groupId` and `parentGroupId` - Group documentation created AFTER groups exist in database - Hierarchical file resolution: group parent installation global - Installation folder = organization-wide customization - Database groups = runtime data isolation --- **This is the ONE CLI. Simple, powerful, ontology-driven.**