cognova 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/.env.example +58 -0
  2. package/Claude/CLAUDE.md +92 -0
  3. package/Claude/hooks/lib/__init__.py +1 -0
  4. package/Claude/hooks/lib/hook_client.py +207 -0
  5. package/Claude/hooks/log-event.py +97 -0
  6. package/Claude/hooks/pre-compact.py +46 -0
  7. package/Claude/hooks/session-end.py +26 -0
  8. package/Claude/hooks/session-start.py +35 -0
  9. package/Claude/hooks/stop-extract.py +40 -0
  10. package/Claude/rules/frontmatter.md +54 -0
  11. package/Claude/rules/markdown.md +43 -0
  12. package/Claude/rules/note-organization.md +33 -0
  13. package/Claude/settings.json +54 -0
  14. package/Claude/skills/README.md +136 -0
  15. package/Claude/skills/_lib/__init__.py +1 -0
  16. package/Claude/skills/_lib/api.py +164 -0
  17. package/Claude/skills/_lib/output.py +95 -0
  18. package/Claude/skills/environment/SKILL.md +73 -0
  19. package/Claude/skills/environment/environment.py +239 -0
  20. package/Claude/skills/memory/SKILL.md +153 -0
  21. package/Claude/skills/memory/memory.py +270 -0
  22. package/Claude/skills/project/SKILL.md +105 -0
  23. package/Claude/skills/project/project.py +203 -0
  24. package/Claude/skills/skill-creator/SKILL.md +261 -0
  25. package/Claude/skills/task/SKILL.md +135 -0
  26. package/Claude/skills/task/task.py +310 -0
  27. package/LICENSE +21 -0
  28. package/README.md +176 -0
  29. package/app/app.config.ts +8 -0
  30. package/app/app.vue +39 -0
  31. package/app/assets/css/main.css +10 -0
  32. package/app/components/AppLogo.vue +40 -0
  33. package/app/components/AssistantPanel.client.vue +518 -0
  34. package/app/components/ConfirmModal.vue +84 -0
  35. package/app/components/TemplateMenu.vue +49 -0
  36. package/app/components/agents/AgentActivityChart.client.vue +105 -0
  37. package/app/components/agents/AgentActivityChart.server.vue +25 -0
  38. package/app/components/agents/AgentForm.vue +304 -0
  39. package/app/components/agents/AgentRunModal.vue +154 -0
  40. package/app/components/agents/AgentStatsCards.vue +98 -0
  41. package/app/components/chat/ChatInput.vue +85 -0
  42. package/app/components/chat/ConversationList.vue +78 -0
  43. package/app/components/chat/MessageBubble.vue +81 -0
  44. package/app/components/chat/StreamingMessage.vue +36 -0
  45. package/app/components/chat/ToolCallBlock.vue +77 -0
  46. package/app/components/editor/CodeEditor.client.vue +212 -0
  47. package/app/components/editor/CodeEditorFallback.vue +12 -0
  48. package/app/components/editor/DocumentEditor.vue +326 -0
  49. package/app/components/editor/DocumentMetadata.vue +140 -0
  50. package/app/components/editor/MarkdownEditor.vue +146 -0
  51. package/app/components/files/FileTree.vue +436 -0
  52. package/app/components/hooks/HookActivityChart.client.vue +117 -0
  53. package/app/components/hooks/HookActivityChart.server.vue +25 -0
  54. package/app/components/hooks/HookStatsCards.vue +63 -0
  55. package/app/components/hooks/RecentEventsTable.vue +123 -0
  56. package/app/components/hooks/ToolBreakdownTable.vue +72 -0
  57. package/app/components/search/DashboardSearch.vue +122 -0
  58. package/app/components/tasks/ProjectSelect.vue +35 -0
  59. package/app/components/tasks/TaskCard.vue +182 -0
  60. package/app/components/tasks/TaskDetail.vue +160 -0
  61. package/app/components/tasks/TaskForm.vue +280 -0
  62. package/app/components/tasks/TaskList.vue +69 -0
  63. package/app/components/view/ViewToc.vue +85 -0
  64. package/app/composables/useAgents.ts +153 -0
  65. package/app/composables/useAuth.ts +73 -0
  66. package/app/composables/useChat.ts +298 -0
  67. package/app/composables/useDocument.ts +141 -0
  68. package/app/composables/useEditor.ts +100 -0
  69. package/app/composables/useFileTree.ts +220 -0
  70. package/app/composables/useHookEvents.ts +68 -0
  71. package/app/composables/useMemories.ts +83 -0
  72. package/app/composables/useNotificationBus.ts +154 -0
  73. package/app/composables/usePreferences.ts +131 -0
  74. package/app/composables/useProjects.ts +97 -0
  75. package/app/composables/useSearch.ts +52 -0
  76. package/app/composables/useTasks.ts +201 -0
  77. package/app/composables/useTerminal.ts +135 -0
  78. package/app/layouts/auth.vue +20 -0
  79. package/app/layouts/dashboard.vue +186 -0
  80. package/app/layouts/view.vue +60 -0
  81. package/app/middleware/auth.ts +9 -0
  82. package/app/pages/agents/[id].vue +602 -0
  83. package/app/pages/agents/index.vue +412 -0
  84. package/app/pages/chat.vue +146 -0
  85. package/app/pages/dashboard.vue +80 -0
  86. package/app/pages/docs.vue +131 -0
  87. package/app/pages/hooks.vue +163 -0
  88. package/app/pages/index.vue +249 -0
  89. package/app/pages/login.vue +60 -0
  90. package/app/pages/memories.vue +282 -0
  91. package/app/pages/settings.vue +625 -0
  92. package/app/pages/tasks.vue +312 -0
  93. package/app/pages/view/[uuid].vue +376 -0
  94. package/dist/cli/index.js +2711 -0
  95. package/drizzle.config.ts +10 -0
  96. package/nuxt.config.ts +98 -0
  97. package/package.json +107 -0
  98. package/server/api/agents/[id]/cancel.post.ts +27 -0
  99. package/server/api/agents/[id]/run.post.ts +34 -0
  100. package/server/api/agents/[id]/runs.get.ts +45 -0
  101. package/server/api/agents/[id]/stats.get.ts +94 -0
  102. package/server/api/agents/[id].delete.ts +29 -0
  103. package/server/api/agents/[id].get.ts +25 -0
  104. package/server/api/agents/[id].patch.ts +55 -0
  105. package/server/api/agents/index.get.ts +15 -0
  106. package/server/api/agents/index.post.ts +48 -0
  107. package/server/api/agents/stats.get.ts +86 -0
  108. package/server/api/auth/[...all].ts +5 -0
  109. package/server/api/conversations/[id].delete.ts +16 -0
  110. package/server/api/conversations/[id].get.ts +34 -0
  111. package/server/api/conversations/index.get.ts +17 -0
  112. package/server/api/documents/[id]/index.delete.ts +47 -0
  113. package/server/api/documents/[id]/index.put.ts +102 -0
  114. package/server/api/documents/[id]/public.get.ts +60 -0
  115. package/server/api/documents/[id]/restore.post.ts +65 -0
  116. package/server/api/documents/by-path.post.ts +168 -0
  117. package/server/api/documents/index.get.ts +48 -0
  118. package/server/api/fs/delete.post.ts +41 -0
  119. package/server/api/fs/list.get.ts +99 -0
  120. package/server/api/fs/mkdir.post.ts +44 -0
  121. package/server/api/fs/move.post.ts +68 -0
  122. package/server/api/fs/read.post.ts +48 -0
  123. package/server/api/fs/rename.post.ts +55 -0
  124. package/server/api/fs/write.post.ts +51 -0
  125. package/server/api/health.get.ts +40 -0
  126. package/server/api/home.get.ts +26 -0
  127. package/server/api/hooks/events/index.get.ts +56 -0
  128. package/server/api/hooks/events/index.post.ts +36 -0
  129. package/server/api/hooks/stats.get.ts +99 -0
  130. package/server/api/memory/[id].delete.ts +26 -0
  131. package/server/api/memory/context.get.ts +83 -0
  132. package/server/api/memory/extract.post.ts +42 -0
  133. package/server/api/memory/search.get.ts +70 -0
  134. package/server/api/memory/store.post.ts +31 -0
  135. package/server/api/projects/[id]/index.delete.ts +40 -0
  136. package/server/api/projects/[id]/index.get.ts +25 -0
  137. package/server/api/projects/[id]/index.put.ts +50 -0
  138. package/server/api/projects/index.get.ts +20 -0
  139. package/server/api/projects/index.post.ts +34 -0
  140. package/server/api/secrets/[key].delete.ts +31 -0
  141. package/server/api/secrets/[key].get.ts +30 -0
  142. package/server/api/secrets/[key].put.ts +52 -0
  143. package/server/api/secrets/index.get.ts +20 -0
  144. package/server/api/secrets/index.post.ts +58 -0
  145. package/server/api/tasks/[id]/index.delete.ts +46 -0
  146. package/server/api/tasks/[id]/index.get.ts +24 -0
  147. package/server/api/tasks/[id]/index.put.ts +70 -0
  148. package/server/api/tasks/[id]/restore.post.ts +49 -0
  149. package/server/api/tasks/index.get.ts +53 -0
  150. package/server/api/tasks/index.post.ts +47 -0
  151. package/server/api/tasks/tags.get.ts +21 -0
  152. package/server/api/user/email.patch.ts +56 -0
  153. package/server/db/index.ts +76 -0
  154. package/server/db/migrate.ts +41 -0
  155. package/server/db/schema.ts +345 -0
  156. package/server/db/seed.ts +46 -0
  157. package/server/db/types.ts +28 -0
  158. package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
  159. package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
  160. package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
  161. package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
  162. package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
  163. package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
  164. package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
  165. package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
  166. package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
  167. package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
  168. package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
  169. package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
  170. package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
  171. package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
  172. package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
  173. package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
  174. package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
  175. package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
  176. package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
  177. package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
  178. package/server/drizzle/migrations/meta/_journal.json +76 -0
  179. package/server/middleware/auth.ts +79 -0
  180. package/server/plugins/00.env-validate.ts +38 -0
  181. package/server/plugins/01.api-token.ts +31 -0
  182. package/server/plugins/02.database.ts +54 -0
  183. package/server/plugins/03.file-watcher.ts +65 -0
  184. package/server/plugins/04.cron-agents.ts +26 -0
  185. package/server/routes/_ws/chat.ts +252 -0
  186. package/server/routes/notifications.ts +47 -0
  187. package/server/routes/terminal.ts +98 -0
  188. package/server/services/agent-executor.ts +218 -0
  189. package/server/services/cron-scheduler.ts +78 -0
  190. package/server/services/memory-extractor.ts +120 -0
  191. package/server/utils/agent-cleanup.ts +91 -0
  192. package/server/utils/agent-registry.ts +95 -0
  193. package/server/utils/auth.ts +33 -0
  194. package/server/utils/chat-session-manager.ts +59 -0
  195. package/server/utils/crypto.ts +40 -0
  196. package/server/utils/db-guard.ts +12 -0
  197. package/server/utils/db-state.ts +63 -0
  198. package/server/utils/document-sync.ts +207 -0
  199. package/server/utils/frontmatter.ts +84 -0
  200. package/server/utils/notification-bus.ts +60 -0
  201. package/server/utils/path-validator.ts +55 -0
  202. package/server/utils/pty-manager.ts +130 -0
  203. package/shared/types/index.ts +604 -0
  204. package/shared/utils/language-detection.ts +87 -0
  205. package/tsconfig.json +10 -0
@@ -0,0 +1,43 @@
1
+ # Markdown Formatting
2
+
3
+ ## Headers
4
+
5
+ Use headers hierarchically:
6
+ - `#` for document title (one per file)
7
+ - `##` for major sections
8
+ - `###` for subsections
9
+
10
+ ## Lists
11
+
12
+ - Use `-` for unordered lists
13
+ - Use `1.` for ordered/sequential steps
14
+ - Use `- [ ]` for action items within notes
15
+
16
+ ## Links
17
+
18
+ Prefer wiki-style links for internal references:
19
+ ```markdown
20
+ See [[project-name]] for details
21
+ Related: [[topic/subtopic]]
22
+ ```
23
+
24
+ Use standard markdown for external links:
25
+ ```markdown
26
+ [Display Text](https://example.com)
27
+ ```
28
+
29
+ ## Code
30
+
31
+ Use fenced code blocks with language identifiers:
32
+ ```markdown
33
+ ```python
34
+ def example():
35
+ pass
36
+ ```
37
+ ```
38
+
39
+ ## Emphasis
40
+
41
+ - `**bold**` for important terms
42
+ - `*italic*` for emphasis or book/article titles
43
+ - `==highlight==` for key takeaways (if supported)
@@ -0,0 +1,33 @@
1
+ # Note Organization
2
+
3
+ ## File Naming
4
+
5
+ Use lowercase with hyphens:
6
+ - `project-ideas.md` not `Project Ideas.md`
7
+ - `meeting-notes-2024-01.md` for dated content
8
+
9
+ ## Folder Structure
10
+
11
+ Organize by topic or area of responsibility:
12
+ ```
13
+ vault/
14
+ ├── projects/ # Active projects
15
+ ├── areas/ # Ongoing responsibilities
16
+ ├── resources/ # Reference material
17
+ ├── archive/ # Completed/inactive
18
+ └── inbox/ # Uncategorized captures
19
+ ```
20
+
21
+ ## When Creating Notes
22
+
23
+ 1. Check if a related note already exists
24
+ 2. Place in appropriate folder
25
+ 3. Add frontmatter with required fields
26
+ 4. Link to related notes
27
+
28
+ ## When to Split Notes
29
+
30
+ Split a note when:
31
+ - It exceeds ~500 lines
32
+ - It covers multiple distinct topics
33
+ - Sections could be referenced independently
@@ -0,0 +1,54 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {"type": "command", "command": "python3 ~/.claude/hooks/session-start.py"}
7
+ ]
8
+ }
9
+ ],
10
+ "PreCompact": [
11
+ {
12
+ "hooks": [
13
+ {"type": "command", "command": "python3 ~/.claude/hooks/pre-compact.py"}
14
+ ]
15
+ }
16
+ ],
17
+ "Stop": [
18
+ {
19
+ "hooks": [
20
+ {"type": "command", "command": "python3 ~/.claude/hooks/stop-extract.py", "timeout": 60000}
21
+ ]
22
+ }
23
+ ],
24
+ "SessionEnd": [
25
+ {
26
+ "hooks": [
27
+ {"type": "command", "command": "python3 ~/.claude/hooks/session-end.py"}
28
+ ]
29
+ }
30
+ ],
31
+ "PreToolUse": [
32
+ {
33
+ "matcher": "Edit|Write|NotebookEdit",
34
+ "hooks": [
35
+ {"type": "command", "command": "python3 ~/.claude/hooks/log-event.py PreToolUse"}
36
+ ]
37
+ },
38
+ {
39
+ "matcher": "Bash",
40
+ "hooks": [
41
+ {"type": "command", "command": "python3 ~/.claude/hooks/log-event.py PreToolUse"}
42
+ ]
43
+ }
44
+ ],
45
+ "PostToolUse": [
46
+ {
47
+ "matcher": "",
48
+ "hooks": [
49
+ {"type": "command", "command": "python3 ~/.claude/hooks/log-event.py PostToolUse"}
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+ }
@@ -0,0 +1,136 @@
1
+ # Cognova Skills
2
+
3
+ Built-in Claude Code skills for Cognova task and project management.
4
+
5
+ ## Available Skills
6
+
7
+ | Skill | Command | Description |
8
+ |-------|---------|-------------|
9
+ | Task | `/task` | Create, list, update, complete tasks |
10
+ | Project | `/project` | Manage projects with duplicate prevention |
11
+ | Skill Creator | `/skill-creator` | Assists in creating new Claude Code skills |
12
+
13
+ ## Usage
14
+
15
+ ### In Container (Production)
16
+
17
+ Skills are automatically available when running Cognova in Docker:
18
+
19
+ ```bash
20
+ docker compose up -d
21
+ docker exec -it cognova claude
22
+
23
+ # Inside Claude Code:
24
+ > /task list
25
+ > /project search homelab
26
+ ```
27
+
28
+ ### Local Development
29
+
30
+ Run skills directly with Python:
31
+
32
+ ```bash
33
+ # Ensure dev server is running
34
+ pnpm dev
35
+
36
+ # Test task skill (from project root)
37
+ python3 Claude/skills/task/task.py list
38
+ python3 Claude/skills/task/task.py create "Test task" --priority 2
39
+
40
+ # Test project skill
41
+ python3 Claude/skills/project/project.py list
42
+ python3 Claude/skills/project/project.py search "home"
43
+ ```
44
+
45
+ ## Environment Variables
46
+
47
+ | Variable | Default | Description |
48
+ |----------|---------|-------------|
49
+ | `COGNOVA_API_URL` | `http://localhost:3000` | API base URL |
50
+
51
+ ## Directory Structure
52
+
53
+ ```
54
+ Claude/
55
+ ├── CLAUDE.md # Default project instructions
56
+ ├── rules/ # Documentation standards
57
+ │ ├── markdown.md # Formatting conventions
58
+ │ ├── note-organization.md # File/folder structure
59
+ │ └── frontmatter.md # Required metadata
60
+ └── skills/
61
+ ├── _lib/ # Shared Python utilities
62
+ │ ├── api.py # HTTP client
63
+ │ └── output.py # Output formatting
64
+ ├── task/ # Task management skill
65
+ ├── project/ # Project management skill
66
+ └── skill-creator/ # Skill authoring assistant
67
+ ```
68
+
69
+ ## Creating New Skills
70
+
71
+ Use the Skill Creator skill or follow these patterns:
72
+
73
+ ### Pure Instruction Skill
74
+
75
+ For simple workflows where Claude uses existing tools:
76
+
77
+ ```
78
+ skills/my-skill/
79
+ └── SKILL.md
80
+ ```
81
+
82
+ ### Python Script Skill
83
+
84
+ For complex operations requiring API calls:
85
+
86
+ ```
87
+ skills/my-skill/
88
+ ├── SKILL.md
89
+ └── my_skill.py
90
+ ```
91
+
92
+ ### SKILL.md Template
93
+
94
+ ```yaml
95
+ ---
96
+ name: my-skill
97
+ description: When Claude should use this skill
98
+ allowed-tools: Bash, Read
99
+ ---
100
+
101
+ # My Skill
102
+
103
+ Instructions for Claude to follow...
104
+
105
+ ## Commands
106
+
107
+ \`\`\`bash
108
+ python3 .claude/skills/my-skill/my_skill.py <command> [options]
109
+ \`\`\`
110
+ ```
111
+
112
+ ## Shared Library
113
+
114
+ Skills can import from `_lib/`:
115
+
116
+ ```python
117
+ import sys
118
+ from pathlib import Path
119
+ sys.path.insert(0, str(Path(__file__).parent.parent / '_lib'))
120
+
121
+ from api import get, post, put, delete
122
+ from output import success, error, format_task
123
+ ```
124
+
125
+ ## Docker Integration
126
+
127
+ The `Claude/` folder is copied to `/home/node/.claude/` during Docker build:
128
+
129
+ ```dockerfile
130
+ # From Dockerfile
131
+ RUN mkdir -p /home/node/.claude && \
132
+ cp -r /app/Claude/* /home/node/.claude/ && \
133
+ chown -R node:node /home/node/.claude
134
+ ```
135
+
136
+ This makes skills available as `/task`, `/project`, etc. when running Claude Code inside the container.
@@ -0,0 +1 @@
1
+ # Cognova Skills Library
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cognova API Client
4
+
5
+ Provides consistent API access for all built-in skills.
6
+ Uses curl subprocess to avoid pip dependencies.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ API_BASE = os.environ.get('COGNOVA_API_URL', 'http://localhost:3000')
17
+
18
+
19
+ def _get_api_token() -> str:
20
+ """Get API token from environment or .api-token file."""
21
+ # Check environment variable first
22
+ token = os.environ.get('COGNOVA_API_TOKEN', '')
23
+ if token:
24
+ return token
25
+
26
+ # Try to read from .api-token file - check multiple locations
27
+ # to handle bare-metal, Docker, and local dev environments
28
+ possible_paths = [
29
+ # Bare-metal: project dir from environment (set by PM2/settings.json)
30
+ Path(os.environ.get('COGNOVA_PROJECT_DIR', '')) / '.api-token',
31
+ # Docker: app is at /home/node/app, skills at /home/node/.claude/skills
32
+ Path('/home/node/app/.api-token'),
33
+ # Local dev: navigate from _lib -> skills -> Claude -> project root
34
+ Path(__file__).parent.parent.parent.parent / '.api-token',
35
+ # Current working directory
36
+ Path.cwd() / '.api-token',
37
+ ]
38
+
39
+ for token_file in possible_paths:
40
+ if token_file.exists():
41
+ try:
42
+ return token_file.read_text().strip()
43
+ except Exception:
44
+ pass
45
+
46
+ return ''
47
+
48
+
49
+ # Get token at module load time
50
+ API_TOKEN = _get_api_token()
51
+
52
+ # Debug: warn if no token found
53
+ if not API_TOKEN and os.environ.get('DEBUG'):
54
+ print(f"[api.py] Warning: No API token found", file=sys.stderr)
55
+ print(f"[api.py] Env COGNOVA_API_TOKEN: {bool(os.environ.get('COGNOVA_API_TOKEN'))}", file=sys.stderr)
56
+ print(f"[api.py] Checked paths: {[str(p) for p in [Path('/home/node/app/.api-token'), Path(__file__).parent.parent.parent.parent / '.api-token', Path.cwd() / '.api-token']]}", file=sys.stderr)
57
+
58
+
59
+ def api_request(
60
+ method: str,
61
+ endpoint: str,
62
+ data: dict | None = None,
63
+ params: dict | None = None
64
+ ) -> tuple[bool, Any]:
65
+ """
66
+ Make an API request to Cognova.
67
+
68
+ Args:
69
+ method: HTTP method (GET, POST, PUT, DELETE)
70
+ endpoint: API endpoint (e.g., '/tasks')
71
+ data: Request body for POST/PUT
72
+ params: Query parameters
73
+
74
+ Returns:
75
+ Tuple of (success: bool, data | error_message)
76
+ """
77
+ url = f"{API_BASE}/api{endpoint}"
78
+
79
+ cmd = ["curl", "-sL", "-X", method]
80
+ cmd.extend(["-H", "Content-Type: application/json"])
81
+
82
+ # Add API token for authentication if available
83
+ if API_TOKEN:
84
+ cmd.extend(["-H", f"X-API-Token: {API_TOKEN}"])
85
+
86
+ if params:
87
+ query_parts = []
88
+ for k, v in params.items():
89
+ if v is not None:
90
+ if isinstance(v, list):
91
+ for item in v:
92
+ query_parts.append(f"{k}={item}")
93
+ else:
94
+ query_parts.append(f"{k}={v}")
95
+ if query_parts:
96
+ url = f"{url}?{'&'.join(query_parts)}"
97
+
98
+ if data and method in ("POST", "PUT"):
99
+ cmd.extend(["-d", json.dumps(data)])
100
+
101
+ cmd.append(url)
102
+
103
+ try:
104
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
105
+ if result.returncode != 0:
106
+ return False, f"Request failed: {result.stderr}"
107
+
108
+ if not result.stdout.strip():
109
+ return False, "Empty response from server"
110
+
111
+ response = json.loads(result.stdout)
112
+ if response.get("error"):
113
+ # API returns {error: true, message: "..."} on error
114
+ return False, response.get("message", response.get("statusMessage", "Unknown error"))
115
+
116
+ return True, response.get("data", response)
117
+ except json.JSONDecodeError:
118
+ return False, f"Invalid JSON response: {result.stdout[:100]}"
119
+ except subprocess.TimeoutExpired:
120
+ return False, "Request timed out"
121
+ except Exception as e:
122
+ return False, str(e)
123
+
124
+
125
+ def get(endpoint: str, params: dict | None = None) -> tuple[bool, Any]:
126
+ """GET request helper."""
127
+ return api_request("GET", endpoint, params=params)
128
+
129
+
130
+ def post(endpoint: str, data: dict) -> tuple[bool, Any]:
131
+ """POST request helper."""
132
+ return api_request("POST", endpoint, data=data)
133
+
134
+
135
+ def put(endpoint: str, data: dict) -> tuple[bool, Any]:
136
+ """PUT request helper."""
137
+ return api_request("PUT", endpoint, data=data)
138
+
139
+
140
+ def delete(endpoint: str) -> tuple[bool, Any]:
141
+ """DELETE request helper."""
142
+ return api_request("DELETE", endpoint)
143
+
144
+
145
+ def get_secret(key: str) -> tuple[bool, str]:
146
+ """
147
+ Fetch a decrypted secret by key.
148
+
149
+ Args:
150
+ key: The secret key (e.g., "GOOGLE_API_KEY")
151
+
152
+ Returns:
153
+ Tuple of (success: bool, value | error_message)
154
+
155
+ Example:
156
+ success, api_key = get_secret("GOOGLE_API_KEY")
157
+ if not success:
158
+ print(f"Error: {api_key}")
159
+ sys.exit(1)
160
+ """
161
+ success, data = get(f"/secrets/{key}")
162
+ if success and isinstance(data, dict):
163
+ return True, data.get("value", "")
164
+ return False, data if isinstance(data, str) else "Secret not found"
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Output formatting for Claude Code skills.
4
+
5
+ Provides consistent, Claude-friendly output formatting.
6
+ """
7
+
8
+ import sys
9
+ from typing import Any
10
+
11
+
12
+ def success(message: str, data: Any = None) -> None:
13
+ """Print success message with optional data."""
14
+ print(f"SUCCESS: {message}")
15
+ if data:
16
+ print(f"\n{format_data(data)}")
17
+
18
+
19
+ def error(message: str) -> None:
20
+ """Print error message to stderr."""
21
+ print(f"ERROR: {message}", file=sys.stderr)
22
+
23
+
24
+ def info(message: str) -> None:
25
+ """Print info message to stderr (not captured in output)."""
26
+ print(f"INFO: {message}", file=sys.stderr)
27
+
28
+
29
+ def warn(message: str) -> None:
30
+ """Print warning message to stderr."""
31
+ print(f"WARNING: {message}", file=sys.stderr)
32
+
33
+
34
+ def format_task(task: dict) -> str:
35
+ """Format a task for display."""
36
+ status_icons = {
37
+ 'todo': '[ ]',
38
+ 'in_progress': '[~]',
39
+ 'done': '[x]',
40
+ 'blocked': '[!]'
41
+ }
42
+ priority_markers = {1: '', 2: '!', 3: '!!'}
43
+
44
+ status = status_icons.get(task.get('status', 'todo'), '[ ]')
45
+ priority = priority_markers.get(task.get('priority', 2), '!')
46
+
47
+ parts = [status]
48
+ if priority:
49
+ parts.append(priority)
50
+ parts.append(task['title'])
51
+
52
+ if task.get('project'):
53
+ parts.append(f"[{task['project']['name']}]")
54
+
55
+ if task.get('dueDate'):
56
+ due_str = task['dueDate']
57
+ if isinstance(due_str, str):
58
+ due_str = due_str[:10]
59
+ parts.append(f"(due: {due_str})")
60
+
61
+ line1 = ' '.join(parts)
62
+ line2 = f" ID: {task['id'][:8]}"
63
+
64
+ if task.get('tags'):
65
+ line2 += f" | Tags: {', '.join(task['tags'])}"
66
+
67
+ return f"{line1}\n{line2}"
68
+
69
+
70
+ def format_project(project: dict) -> str:
71
+ """Format a project for display."""
72
+ desc = ""
73
+ if project.get('description'):
74
+ desc = f"\n {project['description'][:60]}..."
75
+
76
+ return f"- {project['name']} ({project['color']})\n ID: {project['id'][:8]}{desc}"
77
+
78
+
79
+ def format_data(data: Any) -> str:
80
+ """Format arbitrary data for display."""
81
+ if isinstance(data, list):
82
+ if not data:
83
+ return "(empty)"
84
+ if 'title' in data[0]:
85
+ return "\n\n".join(format_task(t) for t in data)
86
+ elif 'name' in data[0]:
87
+ return "\n\n".join(format_project(p) for p in data)
88
+ return str(data)
89
+ elif isinstance(data, dict):
90
+ if 'title' in data:
91
+ return format_task(data)
92
+ elif 'name' in data:
93
+ return format_project(data)
94
+ return str(data)
95
+ return str(data)
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: environment
3
+ description: Check system status, view configuration, troubleshoot issues with the Cognova installation. Use when diagnosing problems, checking health, or understanding the current setup.
4
+ allowed-tools: Bash, Read
5
+ ---
6
+
7
+ # Environment Skill
8
+
9
+ Check and manage the Cognova installation environment.
10
+
11
+ ## Commands
12
+
13
+ ### System info
14
+
15
+ ```bash
16
+ python3 ~/.claude/skills/environment/environment.py info
17
+ ```
18
+
19
+ Shows install directory, vault path, API URL, OS info, disk usage, vault file counts.
20
+
21
+ ### Service status
22
+
23
+ ```bash
24
+ python3 ~/.claude/skills/environment/environment.py status
25
+ ```
26
+
27
+ Shows PM2 process status (memory, CPU, restarts), API health, and database connectivity.
28
+
29
+ ### Recent logs
30
+
31
+ ```bash
32
+ python3 ~/.claude/skills/environment/environment.py logs [--lines N]
33
+ ```
34
+
35
+ Shows recent PM2 logs (default: 30 lines).
36
+
37
+ ### API health check
38
+
39
+ ```bash
40
+ python3 ~/.claude/skills/environment/environment.py health
41
+ ```
42
+
43
+ Checks multiple API endpoints and reports response times.
44
+
45
+ ## Natural Language Patterns
46
+
47
+ - "Is everything running?" → Use `status`
48
+ - "Check the system" → Use `status`
49
+ - "Show me the logs" → Use `logs`
50
+ - "What's the setup?" → Use `info`
51
+ - "Is the API working?" → Use `health`
52
+ - "How much disk space?" → Use `info`
53
+
54
+ ## Key Paths
55
+
56
+ | Resource | How to Find |
57
+ |----------|-------------|
58
+ | Install dir | `$COGNOVA_PROJECT_DIR` or check `~/.cognova` metadata file |
59
+ | Vault | `$VAULT_PATH` |
60
+ | PM2 config | `$COGNOVA_PROJECT_DIR/ecosystem.config.cjs` |
61
+ | App logs | `$COGNOVA_PROJECT_DIR/logs/` |
62
+ | Environment | `$COGNOVA_PROJECT_DIR/.env` |
63
+ | Database migrations | `$COGNOVA_PROJECT_DIR/server/drizzle/migrations/` |
64
+
65
+ ## Common Troubleshooting
66
+
67
+ | Symptom | Check |
68
+ |---------|-------|
69
+ | 503 errors | `status` — DB might not have initialized; check logs |
70
+ | API unreachable | `pm2 status` — process may have crashed; `pm2 restart cognova` |
71
+ | Skills not working | Verify `~/.claude/skills/` has skill directories; re-run `cognova update` |
72
+ | Out of memory | Check `info` for disk/memory; PM2 config has `--max-old-space-size=4096` |
73
+ | Build failures | `NODE_OPTIONS='--max-old-space-size=4096' pnpm build` |