ctxpkg 0.0.1

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 (61) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +282 -0
  3. package/bin/cli.js +8 -0
  4. package/bin/daemon.js +7 -0
  5. package/package.json +70 -0
  6. package/src/agent/AGENTS.md +249 -0
  7. package/src/agent/agent.prompts.ts +66 -0
  8. package/src/agent/agent.test-runner.schemas.ts +158 -0
  9. package/src/agent/agent.test-runner.ts +436 -0
  10. package/src/agent/agent.ts +371 -0
  11. package/src/agent/agent.types.ts +94 -0
  12. package/src/backend/AGENTS.md +112 -0
  13. package/src/backend/backend.protocol.ts +95 -0
  14. package/src/backend/backend.schemas.ts +123 -0
  15. package/src/backend/backend.services.ts +151 -0
  16. package/src/backend/backend.ts +111 -0
  17. package/src/backend/backend.types.ts +34 -0
  18. package/src/cli/AGENTS.md +213 -0
  19. package/src/cli/cli.agent.ts +197 -0
  20. package/src/cli/cli.chat.ts +369 -0
  21. package/src/cli/cli.client.ts +55 -0
  22. package/src/cli/cli.collections.ts +491 -0
  23. package/src/cli/cli.config.ts +252 -0
  24. package/src/cli/cli.daemon.ts +160 -0
  25. package/src/cli/cli.documents.ts +413 -0
  26. package/src/cli/cli.mcp.ts +177 -0
  27. package/src/cli/cli.ts +28 -0
  28. package/src/cli/cli.utils.ts +122 -0
  29. package/src/client/AGENTS.md +135 -0
  30. package/src/client/client.adapters.ts +279 -0
  31. package/src/client/client.ts +86 -0
  32. package/src/client/client.types.ts +17 -0
  33. package/src/collections/AGENTS.md +185 -0
  34. package/src/collections/collections.schemas.ts +195 -0
  35. package/src/collections/collections.ts +1160 -0
  36. package/src/config/config.ts +118 -0
  37. package/src/daemon/AGENTS.md +168 -0
  38. package/src/daemon/daemon.config.ts +23 -0
  39. package/src/daemon/daemon.manager.ts +215 -0
  40. package/src/daemon/daemon.schemas.ts +22 -0
  41. package/src/daemon/daemon.ts +205 -0
  42. package/src/database/AGENTS.md +211 -0
  43. package/src/database/database.ts +64 -0
  44. package/src/database/migrations/migrations.001-init.ts +56 -0
  45. package/src/database/migrations/migrations.002-fts5.ts +32 -0
  46. package/src/database/migrations/migrations.ts +20 -0
  47. package/src/database/migrations/migrations.types.ts +9 -0
  48. package/src/documents/AGENTS.md +301 -0
  49. package/src/documents/documents.schemas.ts +190 -0
  50. package/src/documents/documents.ts +734 -0
  51. package/src/embedder/embedder.ts +53 -0
  52. package/src/exports.ts +0 -0
  53. package/src/mcp/AGENTS.md +264 -0
  54. package/src/mcp/mcp.ts +105 -0
  55. package/src/tools/AGENTS.md +228 -0
  56. package/src/tools/agent/agent.ts +45 -0
  57. package/src/tools/documents/documents.ts +401 -0
  58. package/src/tools/tools.langchain.ts +37 -0
  59. package/src/tools/tools.mcp.ts +46 -0
  60. package/src/tools/tools.types.ts +35 -0
  61. package/src/utils/utils.services.ts +46 -0
@@ -0,0 +1,185 @@
1
+ # Collections — Agent Guidelines
2
+
3
+ This document describes the collections module architecture for AI agents working on this codebase.
4
+
5
+ ## Overview
6
+
7
+ The collections module manages context packages — local files, remote URLs, and git repositories. It handles project configuration (`context.json`), collection syncing, and manifest resolution. Think of it as the "package manager" part of ctxpkg.
8
+
9
+ ## File Structure
10
+
11
+ | File | Purpose |
12
+ |------|---------|
13
+ | `collections.ts` | `CollectionsService` — sync logic, project config, manifest handling |
14
+ | `collections.schemas.ts` | Zod schemas for specs, manifests, and database records |
15
+
16
+ ## Core Concepts
17
+
18
+ ### Collection Spec
19
+
20
+ All collections are manifest-based packages identified by URL:
21
+
22
+ ```typescript
23
+ type CollectionSpec = { url: string };
24
+ ```
25
+
26
+ Collection IDs are computed as `pkg:{normalized_url}`.
27
+
28
+ ### Project Config (`context.json`)
29
+
30
+ Maps user-friendly names to collection specs (local to a project):
31
+
32
+ ```json
33
+ {
34
+ "collections": {
35
+ "my-docs": { "url": "file://./docs/manifest.json" },
36
+ "langchain": { "url": "https://example.com/langchain/manifest.json" },
37
+ "react": { "url": "git+https://github.com/facebook/react#v18.2.0?manifest=docs/manifest.json" }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### URL Formats
43
+
44
+ | Protocol | Format | Example |
45
+ |----------|--------|---------|
46
+ | Local file | `file://path/to/manifest.json` | `file://./docs/manifest.json` |
47
+ | HTTPS | `https://host/path/manifest.json` | `https://example.com/pkg/manifest.json` |
48
+ | Git HTTPS | `git+https://host/repo#ref?manifest=path` | `git+https://github.com/owner/repo#v1.0?manifest=docs/manifest.json` |
49
+ | Git SSH | `git+ssh://git@host/repo#ref?manifest=path` | `git+ssh://git@github.com/org/repo#main?manifest=manifest.json` |
50
+ | Git local | `git+file:///path/repo?manifest=path` | `git+file:///tmp/repo?manifest=manifest.json` |
51
+ | Bundle | `*.tar.gz` or `*.tgz` | `https://example.com/pkg.tar.gz` |
52
+
53
+ ### Global Config (`~/.config/ctxpkg/global-context.json`)
54
+
55
+ Same structure as project config, but user-level (available across all projects):
56
+
57
+ ```json
58
+ {
59
+ "collections": {
60
+ "typescript-docs": { "url": "https://example.com/ts-docs/manifest.json" }
61
+ }
62
+ }
63
+ ```
64
+
65
+ When resolving collection aliases, local takes precedence over global.
66
+
67
+ ### Package Manifests
68
+
69
+ Remote packages have a `manifest.json`:
70
+
71
+ ```json
72
+ {
73
+ "name": "my-package",
74
+ "version": "1.0.0",
75
+ "baseUrl": "https://example.com/files/",
76
+ "sources": {
77
+ "files": [
78
+ "intro.md",
79
+ { "path": "guide.md", "hash": "abc123..." }
80
+ ]
81
+ }
82
+ }
83
+ ```
84
+
85
+ Sources can be `{ glob: [...] }` (local only) or `{ files: [...] }`.
86
+
87
+ ## Architecture
88
+
89
+ ```
90
+ ┌─────────────────────────────────────────────────────────────┐
91
+ │ CollectionsService │
92
+ ├─────────────────────────────────────────────────────────────┤
93
+ │ Project Config │ Global Config │
94
+ │ ───────────────── │ ───────────────── │
95
+ │ readProjectConfig() │ readGlobalConfig() │
96
+ │ writeProjectConfig() │ writeGlobalConfig() │
97
+ │ projectConfigExists() │ globalConfigExists() │
98
+ ├────────────────────────┼────────────────────────────────────┤
99
+ │ Unified Config Ops │ Sync Operations │
100
+ │ ───────────────── │ ──────────────── │
101
+ │ addToConfig() │ syncCollection() │
102
+ │ removeFromConfig() │ syncPkgCollection() │
103
+ │ getFromConfig() │ syncBundleCollection() │
104
+ │ getAllCollections() │ syncGitCollection() │
105
+ ├────────────────────────┼────────────────────────────────────┤
106
+ │ Collection IDs │ Manifest Handling │
107
+ │ ───────────────── │ ──────────────────── │
108
+ │ computeCollectionId() │ loadLocalManifest() │
109
+ │ normalizePath() │ loadRemoteManifest() │
110
+ │ │ resolveManifestSources() │
111
+ │ │ downloadAndExtractBundle() │
112
+ └─────────────────────────────────────────────────────────────┘
113
+
114
+
115
+ ┌─────────────────┐
116
+ │ DocumentsService │ (stores documents)
117
+ └──────────────────┘
118
+ ```
119
+
120
+ **Unified Config Operations:**
121
+ - `addToConfig(name, spec, { global })` - Add to project or global config
122
+ - `removeFromConfig(name, { global })` - Remove from project or global config
123
+ - `getFromConfig(name, { global })` - Get spec (if global undefined, searches local then global)
124
+ - `getAllCollections()` - Get all collections from both configs with source indicators
125
+
126
+ ## Sync Flow
127
+
128
+ ### Package Collections (file://, https://)
129
+
130
+ 1. Parse manifest URL (file:// or https://)
131
+ 2. Load and parse `manifest.json`
132
+ 3. Check manifest hash — skip if unchanged
133
+ 4. Resolve sources to file entries (expand globs or resolve paths)
134
+ 5. Fetch and hash each file
135
+ 6. Sync to database, update collection record
136
+
137
+ ### Bundle Collections (.tar.gz)
138
+
139
+ 1. Download and extract to temp directory
140
+ 2. Find `manifest.json` in extracted content
141
+ 3. Process as local package collection
142
+ 4. Clean up temp directory
143
+
144
+ ### Git Collections
145
+
146
+ 1. Parse git URL to extract clone URL, ref, and manifest path
147
+ 2. Clone to cwd-relative temp directory (`.ctxpkg/tmp/git-*`)
148
+ - Uses shallow clone (`--depth 1`) when possible
149
+ - Disables git hooks for security
150
+ - Preserves user's git config (includeIf directives, SSH keys, etc.)
151
+ 3. Checkout specific ref (branch/tag/commit)
152
+ 4. Load manifest from specified path in repo
153
+ 5. Process as local package collection
154
+ 6. Clean up temp directory
155
+
156
+ **Git URL Components:**
157
+ - `git+https://` or `git+ssh://` — protocol prefix
158
+ - `#ref` — optional branch, tag, or commit SHA (defaults to default branch)
159
+ - `?manifest=path` — required path to manifest.json in repo
160
+
161
+ ## Collection ID Computation
162
+
163
+ IDs are deterministic and computed from the URL:
164
+
165
+ ```typescript
166
+ // Normalized URL (trailing slashes removed)
167
+ `pkg:${url.replace(/\/+$/, '')}`
168
+ ```
169
+
170
+ This ensures the same spec always maps to the same collection ID.
171
+
172
+ ## Key Patterns
173
+
174
+ ### Manifest Source Resolution
175
+
176
+ The service handles multiple source formats:
177
+
178
+ - **Glob sources**: `{ glob: ['**/*.md'] }` — expanded relative to manifest directory
179
+ - **File sources**: `{ files: ['path.md', { url: '...' }] }` — resolved via `baseUrl` or manifest location
180
+
181
+ ### Change Detection
182
+
183
+ - **Manifest hash**: Skip sync if manifest unchanged
184
+ - **Content hash**: Per-file content hash comparison for updates
185
+ - **Force sync**: `force: true` option bypasses hash checks
@@ -0,0 +1,195 @@
1
+ import { z } from 'zod';
2
+
3
+ // === Project Config (context.json) ===
4
+
5
+ const collectionSpecSchema = z.object({
6
+ url: z.string(),
7
+ });
8
+
9
+ const projectConfigSchema = z.object({
10
+ collections: z.record(z.string(), collectionSpecSchema).default({}),
11
+ });
12
+
13
+ type CollectionSpec = z.infer<typeof collectionSpecSchema>;
14
+ type ProjectConfig = z.infer<typeof projectConfigSchema>;
15
+
16
+ // === Package Manifest (manifest.json) ===
17
+
18
+ const globSourcesSchema = z.object({
19
+ glob: z.array(z.string()),
20
+ });
21
+
22
+ const fileEntryObjectSchema = z
23
+ .object({
24
+ path: z.string().optional(),
25
+ url: z.string().optional(),
26
+ hash: z.string().optional(),
27
+ })
28
+ .refine((data) => (data.path && !data.url) || (!data.path && data.url), {
29
+ message: 'File entry must have either path or url, not both or neither',
30
+ });
31
+
32
+ const fileEntrySchema = z.union([z.string(), fileEntryObjectSchema]);
33
+
34
+ const fileSourcesSchema = z.object({
35
+ files: z.array(fileEntrySchema),
36
+ });
37
+
38
+ const manifestSourcesSchema = z.union([globSourcesSchema, fileSourcesSchema]);
39
+
40
+ const manifestSchema = z.object({
41
+ name: z.string(),
42
+ version: z.string(),
43
+ description: z.string().optional(),
44
+ baseUrl: z.string().optional(),
45
+ sources: manifestSourcesSchema,
46
+ metadata: z.record(z.string(), z.unknown()).optional(),
47
+ });
48
+
49
+ type GlobSources = z.infer<typeof globSourcesSchema>;
50
+ type FileEntryObject = z.infer<typeof fileEntryObjectSchema>;
51
+ type FileEntry = z.infer<typeof fileEntrySchema>;
52
+ type FileSources = z.infer<typeof fileSourcesSchema>;
53
+ type ManifestSources = z.infer<typeof manifestSourcesSchema>;
54
+ type Manifest = z.infer<typeof manifestSchema>;
55
+
56
+ // === Database Record ===
57
+
58
+ const collectionRecordSchema = z.object({
59
+ id: z.string(),
60
+ url: z.string(),
61
+ name: z.string().nullable(),
62
+ version: z.string().nullable(),
63
+ description: z.string().nullable(),
64
+ manifest_hash: z.string().nullable(),
65
+ last_sync_at: z.string().nullable(),
66
+ created_at: z.string(),
67
+ updated_at: z.string(),
68
+ });
69
+
70
+ type CollectionRecord = z.infer<typeof collectionRecordSchema>;
71
+
72
+ // === Utility Types ===
73
+
74
+ type ResolvedFileEntry = {
75
+ id: string; // Document ID (path or URL)
76
+ url: string; // Resolved URL to fetch from
77
+ hash?: string; // Optional hash for change detection
78
+ };
79
+
80
+ // === Helpers ===
81
+
82
+ const isGlobSources = (sources: ManifestSources): sources is GlobSources => {
83
+ return 'glob' in sources;
84
+ };
85
+
86
+ const isFileSources = (sources: ManifestSources): sources is FileSources => {
87
+ return 'files' in sources;
88
+ };
89
+
90
+ // === Git URL Parsing ===
91
+
92
+ type ParsedGitUrl = {
93
+ protocol: 'git';
94
+ cloneUrl: string; // URL to clone (without git+ prefix)
95
+ ref: string | null; // Branch, tag, or commit SHA
96
+ manifestPath: string; // Path to manifest within repo
97
+ };
98
+
99
+ /**
100
+ * Check if a URL is a git URL (starts with git+https://, git+ssh://, or git+file://).
101
+ */
102
+ const isGitUrl = (url: string): boolean => {
103
+ return url.startsWith('git+https://') || url.startsWith('git+ssh://') || url.startsWith('git+file://');
104
+ };
105
+
106
+ /**
107
+ * Parse a git URL into its components.
108
+ *
109
+ * Format: git+<protocol>://<host>/<path>[#<ref>]?manifest=<path>
110
+ *
111
+ * Examples:
112
+ * git+https://github.com/owner/repo#v1.0.0?manifest=docs/manifest.json
113
+ * git+ssh://git@github.com/org/repo#main?manifest=manifest.json
114
+ */
115
+ const parseGitUrl = (url: string): ParsedGitUrl => {
116
+ if (!isGitUrl(url)) {
117
+ throw new Error(`Not a git URL: ${url}`);
118
+ }
119
+
120
+ // Remove git+ prefix
121
+ const urlWithoutPrefix = url.slice(4);
122
+
123
+ // Split off the fragment (#ref) first
124
+ const hashIndex = urlWithoutPrefix.indexOf('#');
125
+ const queryIndex = urlWithoutPrefix.indexOf('?');
126
+
127
+ let baseUrl: string;
128
+ let ref: string | null = null;
129
+ let queryString: string;
130
+
131
+ if (hashIndex !== -1 && (queryIndex === -1 || hashIndex < queryIndex)) {
132
+ // Has fragment: extract ref
133
+ baseUrl = urlWithoutPrefix.slice(0, hashIndex);
134
+ const afterHash = urlWithoutPrefix.slice(hashIndex + 1);
135
+ const refQueryIndex = afterHash.indexOf('?');
136
+ if (refQueryIndex !== -1) {
137
+ ref = afterHash.slice(0, refQueryIndex);
138
+ queryString = afterHash.slice(refQueryIndex + 1);
139
+ } else {
140
+ ref = afterHash;
141
+ queryString = '';
142
+ }
143
+ } else if (queryIndex !== -1) {
144
+ // No fragment, but has query
145
+ baseUrl = urlWithoutPrefix.slice(0, queryIndex);
146
+ queryString = urlWithoutPrefix.slice(queryIndex + 1);
147
+ } else {
148
+ throw new Error(`Git URL must specify manifest path: ?manifest=<path>`);
149
+ }
150
+
151
+ // Parse query string for manifest path
152
+ const params = new URLSearchParams(queryString);
153
+ const manifestPath = params.get('manifest');
154
+
155
+ if (!manifestPath) {
156
+ throw new Error(`Git URL must specify manifest path: ?manifest=<path>`);
157
+ }
158
+
159
+ return {
160
+ protocol: 'git',
161
+ cloneUrl: baseUrl,
162
+ ref: ref || null,
163
+ manifestPath,
164
+ };
165
+ };
166
+
167
+ export type {
168
+ CollectionSpec,
169
+ ProjectConfig,
170
+ GlobSources,
171
+ FileEntryObject,
172
+ FileEntry,
173
+ FileSources,
174
+ ManifestSources,
175
+ Manifest,
176
+ CollectionRecord,
177
+ ResolvedFileEntry,
178
+ ParsedGitUrl,
179
+ };
180
+
181
+ export {
182
+ collectionSpecSchema,
183
+ projectConfigSchema,
184
+ globSourcesSchema,
185
+ fileEntryObjectSchema,
186
+ fileEntrySchema,
187
+ fileSourcesSchema,
188
+ manifestSourcesSchema,
189
+ manifestSchema,
190
+ collectionRecordSchema,
191
+ isGlobSources,
192
+ isFileSources,
193
+ isGitUrl,
194
+ parseGitUrl,
195
+ };