@grackle-ai/server 0.0.2

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 (110) hide show
  1. package/README.md +5 -0
  2. package/dist/adapter-manager.d.ts +18 -0
  3. package/dist/adapter-manager.d.ts.map +1 -0
  4. package/dist/adapter-manager.js +67 -0
  5. package/dist/adapter-manager.js.map +1 -0
  6. package/dist/adapters/adapter.d.ts +40 -0
  7. package/dist/adapters/adapter.d.ts.map +1 -0
  8. package/dist/adapters/adapter.js +2 -0
  9. package/dist/adapters/adapter.js.map +1 -0
  10. package/dist/adapters/docker.d.ts +26 -0
  11. package/dist/adapters/docker.d.ts.map +1 -0
  12. package/dist/adapters/docker.js +274 -0
  13. package/dist/adapters/docker.js.map +1 -0
  14. package/dist/adapters/local.d.ts +15 -0
  15. package/dist/adapters/local.d.ts.map +1 -0
  16. package/dist/adapters/local.js +57 -0
  17. package/dist/adapters/local.js.map +1 -0
  18. package/dist/adapters/powerline-transport.d.ts +7 -0
  19. package/dist/adapters/powerline-transport.d.ts.map +1 -0
  20. package/dist/adapters/powerline-transport.js +22 -0
  21. package/dist/adapters/powerline-transport.js.map +1 -0
  22. package/dist/api-key.d.ts +8 -0
  23. package/dist/api-key.d.ts.map +1 -0
  24. package/dist/api-key.js +58 -0
  25. package/dist/api-key.js.map +1 -0
  26. package/dist/crypto.d.ts +5 -0
  27. package/dist/crypto.d.ts.map +1 -0
  28. package/dist/crypto.js +74 -0
  29. package/dist/crypto.js.map +1 -0
  30. package/dist/db.d.ts +11 -0
  31. package/dist/db.d.ts.map +1 -0
  32. package/dist/db.js +140 -0
  33. package/dist/db.js.map +1 -0
  34. package/dist/env-registry.d.ts +20 -0
  35. package/dist/env-registry.d.ts.map +1 -0
  36. package/dist/env-registry.js +55 -0
  37. package/dist/env-registry.js.map +1 -0
  38. package/dist/finding-store.d.ts +9 -0
  39. package/dist/finding-store.d.ts.map +1 -0
  40. package/dist/finding-store.js +68 -0
  41. package/dist/finding-store.js.map +1 -0
  42. package/dist/grpc-service.d.ts +4 -0
  43. package/dist/grpc-service.d.ts.map +1 -0
  44. package/dist/grpc-service.js +594 -0
  45. package/dist/grpc-service.js.map +1 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +108 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/json-helpers.d.ts +7 -0
  51. package/dist/json-helpers.d.ts.map +1 -0
  52. package/dist/json-helpers.js +22 -0
  53. package/dist/json-helpers.js.map +1 -0
  54. package/dist/log-writer.d.ts +18 -0
  55. package/dist/log-writer.d.ts.map +1 -0
  56. package/dist/log-writer.js +44 -0
  57. package/dist/log-writer.js.map +1 -0
  58. package/dist/logger.d.ts +4 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +10 -0
  61. package/dist/logger.js.map +1 -0
  62. package/dist/paths.d.ts +7 -0
  63. package/dist/paths.d.ts.map +1 -0
  64. package/dist/paths.js +12 -0
  65. package/dist/paths.js.map +1 -0
  66. package/dist/project-store.d.ts +11 -0
  67. package/dist/project-store.d.ts.map +1 -0
  68. package/dist/project-store.js +32 -0
  69. package/dist/project-store.js.map +1 -0
  70. package/dist/schema.d.ts +1199 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/schema.js +82 -0
  73. package/dist/schema.js.map +1 -0
  74. package/dist/session-store.d.ts +22 -0
  75. package/dist/session-store.d.ts.map +1 -0
  76. package/dist/session-store.js +73 -0
  77. package/dist/session-store.js.map +1 -0
  78. package/dist/stream-hub.d.ts +14 -0
  79. package/dist/stream-hub.d.ts.map +1 -0
  80. package/dist/stream-hub.js +95 -0
  81. package/dist/stream-hub.js.map +1 -0
  82. package/dist/task-store.d.ts +28 -0
  83. package/dist/task-store.d.ts.map +1 -0
  84. package/dist/task-store.js +121 -0
  85. package/dist/task-store.js.map +1 -0
  86. package/dist/token-broker.d.ts +27 -0
  87. package/dist/token-broker.d.ts.map +1 -0
  88. package/dist/token-broker.js +76 -0
  89. package/dist/token-broker.js.map +1 -0
  90. package/dist/transcript.d.ts +5 -0
  91. package/dist/transcript.d.ts.map +1 -0
  92. package/dist/transcript.js +51 -0
  93. package/dist/transcript.js.map +1 -0
  94. package/dist/utils/exec.d.ts +17 -0
  95. package/dist/utils/exec.d.ts.map +1 -0
  96. package/dist/utils/exec.js +21 -0
  97. package/dist/utils/exec.js.map +1 -0
  98. package/dist/utils/ports.d.ts +3 -0
  99. package/dist/utils/ports.d.ts.map +1 -0
  100. package/dist/utils/ports.js +19 -0
  101. package/dist/utils/ports.js.map +1 -0
  102. package/dist/utils/sleep.d.ts +3 -0
  103. package/dist/utils/sleep.d.ts.map +1 -0
  104. package/dist/utils/sleep.js +5 -0
  105. package/dist/utils/sleep.js.map +1 -0
  106. package/dist/ws-bridge.d.ts +10 -0
  107. package/dist/ws-bridge.d.ts.map +1 -0
  108. package/dist/ws-bridge.js +846 -0
  109. package/dist/ws-bridge.js.map +1 -0
  110. package/package.json +50 -0
@@ -0,0 +1,58 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { API_KEY_FILENAME } from "@grackle-ai/common";
5
+ import { logger } from "./logger.js";
6
+ import { grackleHome } from "./paths.js";
7
+ const API_KEY_BYTE_LENGTH = 32;
8
+ const keyPath = join(grackleHome, API_KEY_FILENAME);
9
+ let cachedKey = undefined;
10
+ /** Attempt to read an existing API key from disk. Returns undefined if none exists. */
11
+ function tryLoadApiKey() {
12
+ if (existsSync(keyPath)) {
13
+ const content = readFileSync(keyPath, "utf8").trim();
14
+ if (content.length > 0) {
15
+ return content;
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+ /** Generate a new random API key, write it to disk with 0600 permissions, and return it. */
21
+ function createApiKey() {
22
+ const key = randomBytes(API_KEY_BYTE_LENGTH).toString("hex");
23
+ mkdirSync(grackleHome, { recursive: true });
24
+ writeFileSync(keyPath, key + "\n", { mode: 0o600 });
25
+ // Ensure permissions on Windows (best-effort)
26
+ try {
27
+ chmodSync(keyPath, 0o600);
28
+ }
29
+ catch { /* Windows may not support this */ }
30
+ logger.info({ keyPath }, "Generated new API key");
31
+ return key;
32
+ }
33
+ /**
34
+ * Load or create the API key. On first run, a random 256-bit key is
35
+ * generated and written to ~/.grackle/api-key with 0600 permissions.
36
+ */
37
+ export function loadOrCreateApiKey() {
38
+ if (cachedKey) {
39
+ return cachedKey;
40
+ }
41
+ cachedKey = tryLoadApiKey() ?? createApiKey();
42
+ return cachedKey;
43
+ }
44
+ /** Verify a bearer token matches the API key. */
45
+ export function verifyApiKey(token) {
46
+ const key = loadOrCreateApiKey();
47
+ // Constant-time comparison to prevent timing attacks
48
+ if (token.length !== key.length) {
49
+ return false;
50
+ }
51
+ let result = 0;
52
+ for (let i = 0; i < key.length; i++) {
53
+ // eslint-disable-next-line no-bitwise
54
+ result |= token.charCodeAt(i) ^ key.charCodeAt(i);
55
+ }
56
+ return result === 0;
57
+ }
58
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,mBAAmB,GAAW,EAAE,CAAC;AAEvC,MAAM,OAAO,GAAW,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;AAE5D,IAAI,SAAS,GAAuB,SAAS,CAAC;AAE9C,uFAAuF;AACvF,SAAS,aAAa;IACpB,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4FAA4F;AAC5F,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE7D,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpD,8CAA8C;IAC9C,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;IAE9C,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,GAAG,aAAa,EAAE,IAAI,YAAY,EAAE,CAAC;IAC9C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACjC,qDAAqD;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,sCAAsC;QACtC,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,KAAK,CAAC,CAAC;AACtB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Encrypt a plaintext string using AES-256-GCM with a PBKDF2-derived key. */
2
+ export declare function encrypt(plaintext: string): string;
3
+ /** Decrypt an AES-256-GCM ciphertext string produced by {@link encrypt}. */
4
+ export declare function decrypt(ciphertext: string): string;
5
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AA4DA,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWlD"}
package/dist/crypto.js ADDED
@@ -0,0 +1,74 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { grackleHome } from "./paths.js";
5
+ import { logger } from "./logger.js";
6
+ const ALGORITHM = "aes-256-gcm";
7
+ const IV_LENGTH = 12;
8
+ const TAG_LENGTH = 16;
9
+ const SALT_LENGTH = 16;
10
+ const KEY_LENGTH = 32;
11
+ const ITERATIONS = 100_000;
12
+ const MASTER_KEY_BYTE_LENGTH = 32;
13
+ const MASTER_KEY_FILENAME = "master-key";
14
+ /**
15
+ * Load or generate the master key for token encryption. Priority:
16
+ * 1. `GRACKLE_MASTER_KEY` env var
17
+ * 2. Persisted random key at `$GRACKLE_HOME/.grackle/master-key`
18
+ * 3. Generate and persist a new random key
19
+ */
20
+ function loadMasterKey() {
21
+ if (process.env.GRACKLE_MASTER_KEY) {
22
+ return process.env.GRACKLE_MASTER_KEY;
23
+ }
24
+ const keyPath = join(grackleHome, MASTER_KEY_FILENAME);
25
+ if (existsSync(keyPath)) {
26
+ const key = readFileSync(keyPath, "utf8").trim();
27
+ if (key.length > 0) {
28
+ return key;
29
+ }
30
+ }
31
+ // Generate and persist a random key
32
+ const key = randomBytes(MASTER_KEY_BYTE_LENGTH).toString("hex");
33
+ mkdirSync(grackleHome, { recursive: true });
34
+ writeFileSync(keyPath, key + "\n", { mode: 0o600 });
35
+ try {
36
+ chmodSync(keyPath, 0o600);
37
+ }
38
+ catch { /* Windows may not support this */ }
39
+ logger.warn("Generated new master key for token encryption. Set GRACKLE_MASTER_KEY env var for explicit control.");
40
+ return key;
41
+ }
42
+ let cachedMasterKey = undefined;
43
+ function getMasterKey() {
44
+ if (!cachedMasterKey) {
45
+ cachedMasterKey = loadMasterKey();
46
+ }
47
+ return cachedMasterKey;
48
+ }
49
+ function deriveKey(salt) {
50
+ return pbkdf2Sync(getMasterKey(), salt, ITERATIONS, KEY_LENGTH, "sha256");
51
+ }
52
+ /** Encrypt a plaintext string using AES-256-GCM with a PBKDF2-derived key. */
53
+ export function encrypt(plaintext) {
54
+ const salt = randomBytes(SALT_LENGTH);
55
+ const key = deriveKey(salt);
56
+ const iv = randomBytes(IV_LENGTH);
57
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
58
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
59
+ const tag = cipher.getAuthTag();
60
+ // Format: salt:iv:tag:ciphertext (all base64)
61
+ return [salt, iv, tag, encrypted].map((b) => b.toString("base64")).join(":");
62
+ }
63
+ /** Decrypt an AES-256-GCM ciphertext string produced by {@link encrypt}. */
64
+ export function decrypt(ciphertext) {
65
+ const parts = ciphertext.split(":");
66
+ if (parts.length !== 4)
67
+ throw new Error("Invalid encrypted format");
68
+ const [salt, iv, tag, encrypted] = parts.map((p) => Buffer.from(p, "base64"));
69
+ const key = deriveKey(salt);
70
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
71
+ decipher.setAuthTag(tag);
72
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
73
+ }
74
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Database from "better-sqlite3";
2
+ import * as schema from "./schema.js";
3
+ /** Initialize all database tables and run migrations. Call once at startup. */
4
+ export declare function initDatabase(): void;
5
+ /** Drizzle ORM instance wrapping the SQLite database. */
6
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
7
+ declare const db: BetterSQLite3Database<typeof schema> & {
8
+ $client: InstanceType<typeof Database>;
9
+ };
10
+ export default db;
11
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAMtC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAatC,+EAA+E;AAC/E,wBAAgB,YAAY,IAAI,IAAI,CAwHnC;AAKD,yDAAyD;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,QAAA,MAAM,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,GAAG;IAAE,OAAO,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAA;CAAgC,CAAC;AAE1H,eAAe,EAAE,CAAC"}
package/dist/db.js ADDED
@@ -0,0 +1,140 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle } from "drizzle-orm/better-sqlite3";
3
+ import { join } from "node:path";
4
+ import { mkdirSync } from "node:fs";
5
+ import { DB_FILENAME } from "@grackle-ai/common";
6
+ import { grackleHome } from "./paths.js";
7
+ import * as schema from "./schema.js";
8
+ mkdirSync(grackleHome, { recursive: true });
9
+ const dbPath = join(grackleHome, DB_FILENAME);
10
+ const sqlite = new Database(dbPath);
11
+ // Enable WAL mode for better concurrent read performance
12
+ sqlite.pragma("journal_mode = WAL");
13
+ // Enable foreign key enforcement (off by default in SQLite)
14
+ sqlite.pragma("foreign_keys = ON");
15
+ /** Initialize all database tables and run migrations. Call once at startup. */
16
+ export function initDatabase() {
17
+ // Create tables — idempotent, safe to run every startup
18
+ sqlite.exec(`
19
+ CREATE TABLE IF NOT EXISTS environments (
20
+ id TEXT PRIMARY KEY,
21
+ display_name TEXT NOT NULL,
22
+ adapter_type TEXT NOT NULL,
23
+ adapter_config TEXT NOT NULL,
24
+ default_runtime TEXT NOT NULL DEFAULT 'claude-code',
25
+ bootstrapped INTEGER NOT NULL DEFAULT 0,
26
+ status TEXT NOT NULL DEFAULT 'disconnected',
27
+ last_seen TEXT,
28
+ env_info TEXT,
29
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
30
+ powerline_token TEXT NOT NULL DEFAULT ''
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS sessions (
34
+ id TEXT PRIMARY KEY,
35
+ env_id TEXT NOT NULL REFERENCES environments(id),
36
+ runtime TEXT NOT NULL,
37
+ runtime_session_id TEXT,
38
+ prompt TEXT NOT NULL,
39
+ model TEXT NOT NULL,
40
+ status TEXT NOT NULL DEFAULT 'pending',
41
+ log_path TEXT,
42
+ turns INTEGER NOT NULL DEFAULT 0,
43
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
44
+ suspended_at TEXT,
45
+ ended_at TEXT,
46
+ error TEXT
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS tokens (
50
+ id TEXT PRIMARY KEY,
51
+ config TEXT NOT NULL,
52
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS projects (
56
+ id TEXT PRIMARY KEY,
57
+ name TEXT NOT NULL,
58
+ description TEXT NOT NULL DEFAULT '',
59
+ repo_url TEXT NOT NULL DEFAULT '',
60
+ default_env_id TEXT NOT NULL DEFAULT '',
61
+ status TEXT NOT NULL DEFAULT 'active',
62
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
63
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS tasks (
67
+ id TEXT PRIMARY KEY,
68
+ project_id TEXT NOT NULL REFERENCES projects(id),
69
+ title TEXT NOT NULL,
70
+ description TEXT NOT NULL DEFAULT '',
71
+ status TEXT NOT NULL DEFAULT 'pending',
72
+ branch TEXT NOT NULL DEFAULT '',
73
+ env_id TEXT NOT NULL DEFAULT '',
74
+ session_id TEXT NOT NULL DEFAULT '',
75
+ depends_on TEXT NOT NULL DEFAULT '[]',
76
+ assigned_at TEXT,
77
+ started_at TEXT,
78
+ completed_at TEXT,
79
+ review_notes TEXT NOT NULL DEFAULT '',
80
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
81
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
82
+ sort_order INTEGER NOT NULL DEFAULT 0
83
+ );
84
+
85
+ CREATE TABLE IF NOT EXISTS findings (
86
+ id TEXT PRIMARY KEY,
87
+ project_id TEXT NOT NULL REFERENCES projects(id),
88
+ task_id TEXT NOT NULL DEFAULT '',
89
+ session_id TEXT NOT NULL DEFAULT '',
90
+ category TEXT NOT NULL DEFAULT 'general',
91
+ title TEXT NOT NULL,
92
+ content TEXT NOT NULL,
93
+ tags TEXT NOT NULL DEFAULT '[]',
94
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
95
+ );
96
+
97
+ CREATE INDEX IF NOT EXISTS idx_findings_project ON findings(project_id);
98
+ `);
99
+ // Migration: add powerline_token column if missing (older databases)
100
+ try {
101
+ sqlite.exec("ALTER TABLE environments ADD COLUMN powerline_token TEXT NOT NULL DEFAULT ''");
102
+ }
103
+ catch { /* column already exists */ }
104
+ // Migration: rename sidecar_token → powerline_token (from older databases)
105
+ try {
106
+ sqlite.exec("ALTER TABLE environments RENAME COLUMN sidecar_token TO powerline_token");
107
+ }
108
+ catch { /* column already renamed or doesn't exist */ }
109
+ // Migration: backfill NULLs in stage-2 tables from older schemas that lacked NOT NULL
110
+ sqlite.exec(`
111
+ UPDATE projects SET description = '' WHERE description IS NULL;
112
+ UPDATE projects SET repo_url = '' WHERE repo_url IS NULL;
113
+ UPDATE projects SET default_env_id = '' WHERE default_env_id IS NULL;
114
+ UPDATE projects SET status = 'active' WHERE status IS NULL;
115
+ UPDATE projects SET created_at = datetime('now') WHERE created_at IS NULL;
116
+ UPDATE projects SET updated_at = datetime('now') WHERE updated_at IS NULL;
117
+
118
+ UPDATE tasks SET description = '' WHERE description IS NULL;
119
+ UPDATE tasks SET status = 'pending' WHERE status IS NULL;
120
+ UPDATE tasks SET branch = '' WHERE branch IS NULL;
121
+ UPDATE tasks SET env_id = '' WHERE env_id IS NULL;
122
+ UPDATE tasks SET session_id = '' WHERE session_id IS NULL;
123
+ UPDATE tasks SET depends_on = '[]' WHERE depends_on IS NULL;
124
+ UPDATE tasks SET review_notes = '' WHERE review_notes IS NULL;
125
+ UPDATE tasks SET created_at = datetime('now') WHERE created_at IS NULL;
126
+ UPDATE tasks SET updated_at = datetime('now') WHERE updated_at IS NULL;
127
+ UPDATE tasks SET sort_order = 0 WHERE sort_order IS NULL;
128
+
129
+ UPDATE findings SET task_id = '' WHERE task_id IS NULL;
130
+ UPDATE findings SET session_id = '' WHERE session_id IS NULL;
131
+ UPDATE findings SET category = 'general' WHERE category IS NULL;
132
+ UPDATE findings SET tags = '[]' WHERE tags IS NULL;
133
+ UPDATE findings SET created_at = datetime('now') WHERE created_at IS NULL;
134
+ `);
135
+ }
136
+ // Run init immediately for backwards compatibility — stores import db at module load
137
+ initDatabase();
138
+ const db = drizzle(sqlite, { schema });
139
+ export default db;
140
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAE5C,MAAM,MAAM,GAAW,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACtD,MAAM,MAAM,GAAkC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnE,yDAAyD;AACzD,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEpC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAEnC,+EAA+E;AAC/E,MAAM,UAAU,YAAY;IAC1B,wDAAwD;IACxD,MAAM,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFX,CAAC,CAAC;IAEH,qEAAqE;IACrE,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IAEvC,2EAA2E;IAC3E,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC,CAAC,6CAA6C,CAAC,CAAC;IAEzD,sFAAsF;IACtF,MAAM,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;GAwBX,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACrF,YAAY,EAAE,CAAC;AAIf,MAAM,EAAE,GAAsF,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAE1H,eAAe,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type EnvironmentRow } from "./schema.js";
2
+ import type { EnvironmentStatus } from "@grackle-ai/common";
3
+ export type { EnvironmentRow };
4
+ /** Return all registered environments. */
5
+ export declare function listEnvironments(): EnvironmentRow[];
6
+ /** Retrieve a single environment by ID. */
7
+ export declare function getEnvironment(id: string): EnvironmentRow | undefined;
8
+ /** Insert a new environment record with a randomly-generated PowerLine token. */
9
+ export declare function addEnvironment(id: string, displayName: string, adapterType: string, adapterConfig: string, defaultRuntime: string): void;
10
+ /** Delete an environment record from the database. */
11
+ export declare function removeEnvironment(id: string): void;
12
+ /** Update an environment's connection status and touch `last_seen`. */
13
+ export declare function updateEnvironmentStatus(id: string, status: EnvironmentStatus): void;
14
+ /** Mark an environment as having completed first-time bootstrap. */
15
+ export declare function markBootstrapped(id: string): void;
16
+ /** Store serialized environment info (e.g. OS, node version) from the PowerLine. */
17
+ export declare function setEnvInfo(id: string, info: string): void;
18
+ /** Reset all environment statuses to disconnected on server startup. */
19
+ export declare function resetAllStatuses(): void;
20
+ //# sourceMappingURL=env-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-registry.d.ts","sourceRoot":"","sources":["../src/env-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B,0CAA0C;AAC1C,wBAAgB,gBAAgB,IAAI,cAAc,EAAE,CAEnD;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAErE;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,IAAI,CAUN;AAED,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAKnF;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAKjD;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAKzD;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
@@ -0,0 +1,55 @@
1
+ import db from "./db.js";
2
+ import { environments } from "./schema.js";
3
+ import { eq, sql } from "drizzle-orm";
4
+ import { randomBytes } from "node:crypto";
5
+ const POWERLINE_TOKEN_BYTE_LENGTH = 32;
6
+ /** Return all registered environments. */
7
+ export function listEnvironments() {
8
+ return db.select().from(environments).all();
9
+ }
10
+ /** Retrieve a single environment by ID. */
11
+ export function getEnvironment(id) {
12
+ return db.select().from(environments).where(eq(environments.id, id)).get();
13
+ }
14
+ /** Insert a new environment record with a randomly-generated PowerLine token. */
15
+ export function addEnvironment(id, displayName, adapterType, adapterConfig, defaultRuntime) {
16
+ const powerlineToken = randomBytes(POWERLINE_TOKEN_BYTE_LENGTH).toString("hex");
17
+ db.insert(environments).values({
18
+ id,
19
+ displayName,
20
+ adapterType,
21
+ adapterConfig,
22
+ defaultRuntime,
23
+ powerlineToken,
24
+ }).run();
25
+ }
26
+ /** Delete an environment record from the database. */
27
+ export function removeEnvironment(id) {
28
+ db.delete(environments).where(eq(environments.id, id)).run();
29
+ }
30
+ /** Update an environment's connection status and touch `last_seen`. */
31
+ export function updateEnvironmentStatus(id, status) {
32
+ db.update(environments)
33
+ .set({ status, lastSeen: sql `datetime('now')` })
34
+ .where(eq(environments.id, id))
35
+ .run();
36
+ }
37
+ /** Mark an environment as having completed first-time bootstrap. */
38
+ export function markBootstrapped(id) {
39
+ db.update(environments)
40
+ .set({ bootstrapped: true })
41
+ .where(eq(environments.id, id))
42
+ .run();
43
+ }
44
+ /** Store serialized environment info (e.g. OS, node version) from the PowerLine. */
45
+ export function setEnvInfo(id, info) {
46
+ db.update(environments)
47
+ .set({ envInfo: info })
48
+ .where(eq(environments.id, id))
49
+ .run();
50
+ }
51
+ /** Reset all environment statuses to disconnected on server startup. */
52
+ export function resetAllStatuses() {
53
+ db.update(environments).set({ status: "disconnected" }).run();
54
+ }
55
+ //# sourceMappingURL=env-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-registry.js","sourceRoot":"","sources":["../src/env-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,2BAA2B,GAAW,EAAE,CAAC;AAI/C,0CAA0C;AAC1C,MAAM,UAAU,gBAAgB;IAC9B,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,CAAC;AAC9C,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC7E,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAC5B,EAAU,EACV,WAAmB,EACnB,WAAmB,EACnB,aAAqB,EACrB,cAAsB;IAEtB,MAAM,cAAc,GAAG,WAAW,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChF,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC;QAC7B,EAAE;QACF,WAAW;QACX,WAAW;QACX,aAAa;QACb,cAAc;QACd,cAAc;KACf,CAAC,CAAC,GAAG,EAAE,CAAC;AACX,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC/D,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,uBAAuB,CAAC,EAAU,EAAE,MAAyB;IAC3E,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;SACpB,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAA,iBAAiB,EAAE,CAAC;SAC/C,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SAC9B,GAAG,EAAE,CAAC;AACX,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;SACpB,GAAG,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;SAC3B,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SAC9B,GAAG,EAAE,CAAC;AACX,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,IAAY;IACjD,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;SACpB,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;SACtB,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SAC9B,GAAG,EAAE,CAAC;AACX,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,gBAAgB;IAC9B,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type FindingRow } from "./schema.js";
2
+ export type { FindingRow };
3
+ /** Insert a new finding record. */
4
+ export declare function postFinding(id: string, projectId: string, taskId: string, sessionId: string, category: string, title: string, content: string, tags: string[]): void;
5
+ /** Query findings for a project, optionally filtering by categories and tags. */
6
+ export declare function queryFindings(projectId: string, categories?: string[], tags?: string[], limit?: number): FindingRow[];
7
+ /** Build a summarized text context of recent findings for a project. */
8
+ export declare function buildFindingsContext(projectId: string): string;
9
+ //# sourceMappingURL=finding-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-store.d.ts","sourceRoot":"","sources":["../src/finding-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAIxD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,mCAAmC;AACnC,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,GACb,IAAI,CAWN;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,IAAI,CAAC,EAAE,MAAM,EAAE,EACf,KAAK,CAAC,EAAE,MAAM,GACb,UAAU,EAAE,CA8Bd;AAED,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAwB9D"}
@@ -0,0 +1,68 @@
1
+ import db from "./db.js";
2
+ import { findings } from "./schema.js";
3
+ import { eq, desc, sql, and } from "drizzle-orm";
4
+ import { safeParseJsonArray } from "./json-helpers.js";
5
+ /** Insert a new finding record. */
6
+ export function postFinding(id, projectId, taskId, sessionId, category, title, content, tags) {
7
+ db.insert(findings).values({
8
+ id,
9
+ projectId,
10
+ taskId,
11
+ sessionId,
12
+ category,
13
+ title,
14
+ content,
15
+ tags: JSON.stringify(tags),
16
+ }).run();
17
+ }
18
+ /** Query findings for a project, optionally filtering by categories and tags. */
19
+ export function queryFindings(projectId, categories, tags, limit) {
20
+ const maxResults = Math.min(limit || 50, 100);
21
+ let results;
22
+ if (categories && categories.length > 0) {
23
+ results = db.select().from(findings)
24
+ .where(and(eq(findings.projectId, projectId), sql `${findings.category} IN (SELECT value FROM json_each(${JSON.stringify(categories)}))`))
25
+ .orderBy(desc(findings.createdAt))
26
+ .limit(maxResults)
27
+ .all();
28
+ }
29
+ else {
30
+ results = db.select().from(findings)
31
+ .where(eq(findings.projectId, projectId))
32
+ .orderBy(desc(findings.createdAt))
33
+ .limit(maxResults)
34
+ .all();
35
+ }
36
+ // Client-side tag filtering (simple approach)
37
+ if (tags && tags.length > 0) {
38
+ results = results.filter((r) => {
39
+ const rowTags = safeParseJsonArray(r.tags);
40
+ return tags.some((t) => rowTags.includes(t));
41
+ });
42
+ }
43
+ return results;
44
+ }
45
+ /** Build a summarized text context of recent findings for a project. */
46
+ export function buildFindingsContext(projectId) {
47
+ const allFindings = queryFindings(projectId, undefined, undefined, 20);
48
+ if (allFindings.length === 0) {
49
+ return "";
50
+ }
51
+ const lines = ["## Project Findings (shared knowledge from other agents)\n"];
52
+ let totalChars = lines[0].length;
53
+ const MAX_CHARS = 8000;
54
+ const MAX_PER_FINDING = 500;
55
+ for (const f of allFindings) {
56
+ const content = f.content.length > MAX_PER_FINDING
57
+ ? f.content.slice(0, MAX_PER_FINDING) + "..."
58
+ : f.content;
59
+ const entry = `### [${f.category}] ${f.title}\n${content}\n`;
60
+ if (totalChars + entry.length > MAX_CHARS) {
61
+ break;
62
+ }
63
+ lines.push(entry);
64
+ totalChars += entry.length;
65
+ }
66
+ return lines.join("\n");
67
+ }
68
+ //# sourceMappingURL=finding-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-store.js","sourceRoot":"","sources":["../src/finding-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { ConnectRouter } from "@connectrpc/connect";
2
+ /** Register all Grackle gRPC service handlers on the given ConnectRPC router. */
3
+ export declare function registerGrackleRoutes(router: ConnectRouter): void;
4
+ //# sourceMappingURL=grpc-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc-service.d.ts","sourceRoot":"","sources":["../src/grpc-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAyKzD,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAghBjE"}