@proletariat/cli 0.3.51 → 0.3.52

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 (87) hide show
  1. package/dist/commands/agent/status.js +1 -0
  2. package/dist/commands/asana/connect.d.ts +15 -0
  3. package/dist/commands/asana/connect.js +267 -0
  4. package/dist/commands/asana/sync.d.ts +15 -0
  5. package/dist/commands/asana/sync.js +189 -0
  6. package/dist/commands/config/index.js +7 -1
  7. package/dist/commands/execution/list.js +3 -0
  8. package/dist/commands/execution/view.js +10 -0
  9. package/dist/commands/monday/connect.d.ts +16 -0
  10. package/dist/commands/monday/connect.js +212 -0
  11. package/dist/commands/monday/sync.d.ts +14 -0
  12. package/dist/commands/monday/sync.js +178 -0
  13. package/dist/commands/orchestrator/start.d.ts +6 -0
  14. package/dist/commands/orchestrator/start.js +149 -11
  15. package/dist/commands/session/list.js +6 -5
  16. package/dist/commands/work/index.js +7 -0
  17. package/dist/commands/work/jira.d.ts +28 -0
  18. package/dist/commands/work/jira.js +225 -0
  19. package/dist/commands/work/source/set.d.ts +12 -0
  20. package/dist/commands/work/source/set.js +52 -0
  21. package/dist/commands/work/source.d.ts +11 -0
  22. package/dist/commands/work/source.js +53 -0
  23. package/dist/commands/work/spawn.d.ts +1 -0
  24. package/dist/commands/work/spawn.js +73 -8
  25. package/dist/commands/work/start.d.ts +8 -0
  26. package/dist/commands/work/start.js +241 -3
  27. package/dist/lib/asana/client.d.ts +15 -0
  28. package/dist/lib/asana/client.js +120 -0
  29. package/dist/lib/asana/config.d.ts +9 -0
  30. package/dist/lib/asana/config.js +61 -0
  31. package/dist/lib/asana/index.d.ts +5 -0
  32. package/dist/lib/asana/index.js +4 -0
  33. package/dist/lib/asana/mapper.d.ts +13 -0
  34. package/dist/lib/asana/mapper.js +70 -0
  35. package/dist/lib/asana/sync.d.ts +13 -0
  36. package/dist/lib/asana/sync.js +36 -0
  37. package/dist/lib/asana/types.d.ts +40 -0
  38. package/dist/lib/asana/types.js +1 -0
  39. package/dist/lib/database/drizzle-schema.d.ts +393 -0
  40. package/dist/lib/database/drizzle-schema.js +45 -0
  41. package/dist/lib/execution/config.d.ts +10 -0
  42. package/dist/lib/execution/config.js +19 -0
  43. package/dist/lib/execution/runners.d.ts +10 -0
  44. package/dist/lib/execution/runners.js +110 -1
  45. package/dist/lib/execution/spawner.js +26 -0
  46. package/dist/lib/execution/storage.d.ts +4 -0
  47. package/dist/lib/execution/storage.js +8 -3
  48. package/dist/lib/execution/types.d.ts +4 -0
  49. package/dist/lib/external-issues/adapters.d.ts +18 -1
  50. package/dist/lib/external-issues/adapters.js +49 -1
  51. package/dist/lib/external-issues/index.d.ts +4 -1
  52. package/dist/lib/external-issues/index.js +5 -0
  53. package/dist/lib/external-issues/jira.d.ts +23 -0
  54. package/dist/lib/external-issues/jira.js +223 -0
  55. package/dist/lib/external-issues/linear.js +4 -3
  56. package/dist/lib/external-issues/mapper.d.ts +3 -2
  57. package/dist/lib/external-issues/mapper.js +5 -2
  58. package/dist/lib/external-issues/mapping-store.d.ts +12 -0
  59. package/dist/lib/external-issues/mapping-store.js +164 -0
  60. package/dist/lib/external-issues/types.d.ts +34 -0
  61. package/dist/lib/external-issues/validation.js +11 -0
  62. package/dist/lib/external-issues/work-start.d.ts +10 -0
  63. package/dist/lib/external-issues/work-start.js +12 -0
  64. package/dist/lib/linear/mapper.d.ts +2 -0
  65. package/dist/lib/linear/mapper.js +66 -2
  66. package/dist/lib/monday/client.d.ts +14 -0
  67. package/dist/lib/monday/client.js +113 -0
  68. package/dist/lib/monday/config.d.ts +10 -0
  69. package/dist/lib/monday/config.js +64 -0
  70. package/dist/lib/monday/index.d.ts +5 -0
  71. package/dist/lib/monday/index.js +4 -0
  72. package/dist/lib/monday/mapper.d.ts +14 -0
  73. package/dist/lib/monday/mapper.js +89 -0
  74. package/dist/lib/monday/sync.d.ts +13 -0
  75. package/dist/lib/monday/sync.js +45 -0
  76. package/dist/lib/monday/types.d.ts +38 -0
  77. package/dist/lib/monday/types.js +4 -0
  78. package/dist/lib/pmo/schema.d.ts +10 -1
  79. package/dist/lib/pmo/schema.js +73 -0
  80. package/dist/lib/pmo/storage/base.js +32 -0
  81. package/dist/lib/prompt-json.d.ts +11 -0
  82. package/dist/lib/work-source/config.d.ts +14 -0
  83. package/dist/lib/work-source/config.js +70 -0
  84. package/dist/lib/work-source/index.d.ts +1 -0
  85. package/dist/lib/work-source/index.js +1 -0
  86. package/oclif.manifest.json +2584 -2017
  87. package/package.json +1 -1
@@ -4,6 +4,7 @@
4
4
  * Deterministically maps an IssueEnvelope to spawn context data
5
5
  * (prompt text + metadata) for agent execution.
6
6
  */
7
+ import { validateOrThrow } from './validation.js';
7
8
  /**
8
9
  * Map an IssueEnvelope to spawn context data.
9
10
  *
@@ -14,10 +15,12 @@
14
15
  * The mapping is deterministic: the same IssueEnvelope always produces
15
16
  * the same IssueSpawnContext.
16
17
  *
17
- * @param envelope - Validated IssueEnvelope
18
+ * @param input - IssueEnvelope-like input
18
19
  * @returns Spawn context with prompt and metadata
20
+ * @throws ExternalIssueError when input fails IssueEnvelope validation
19
21
  */
20
- export function mapToSpawnContext(envelope) {
22
+ export function mapToSpawnContext(input) {
23
+ const envelope = validateOrThrow(input);
21
24
  const prompt = buildPrompt(envelope);
22
25
  const metadata = buildMetadata(envelope);
23
26
  return { prompt, metadata };
@@ -0,0 +1,12 @@
1
+ import Database from 'better-sqlite3';
2
+ import { type ExternalExecutionMapping, type ExternalMappingProvider, type UpsertExternalExecutionMappingInput } from './types.js';
3
+ export declare class ExternalExecutionMappingStore {
4
+ private db;
5
+ constructor(db: Database.Database);
6
+ private ensureTables;
7
+ upsertMapping(input: UpsertExternalExecutionMappingInput): ExternalExecutionMapping;
8
+ getByExternalId(provider: ExternalMappingProvider, externalId: string): ExternalExecutionMapping | null;
9
+ getByExternalKey(provider: ExternalMappingProvider, externalKey: string): ExternalExecutionMapping | null;
10
+ findByExecutionId(executionId: string): ExternalExecutionMapping[];
11
+ private rowToMapping;
12
+ }
@@ -0,0 +1,164 @@
1
+ import { PMO_TABLES } from '../pmo/schema.js';
2
+ const T = PMO_TABLES;
3
+ function parseDate(value) {
4
+ return value ? new Date(value) : null;
5
+ }
6
+ function parseSnapshot(value) {
7
+ if (!value)
8
+ return null;
9
+ try {
10
+ const parsed = JSON.parse(value);
11
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
12
+ return parsed;
13
+ }
14
+ }
15
+ catch {
16
+ // Keep compatibility if old/invalid values are present.
17
+ }
18
+ return null;
19
+ }
20
+ export class ExternalExecutionMappingStore {
21
+ db;
22
+ constructor(db) {
23
+ this.db = db;
24
+ this.ensureTables();
25
+ }
26
+ ensureTables() {
27
+ this.db.exec(`
28
+ CREATE TABLE IF NOT EXISTS ${T.external_execution_map} (
29
+ provider TEXT NOT NULL CHECK (provider IN ('linear', 'jira', 'asana', 'monday', 'pmo')),
30
+ external_id TEXT NOT NULL,
31
+ external_key TEXT,
32
+ canonical_url TEXT,
33
+ latest_state_snapshot TEXT,
34
+ last_synced_at TIMESTAMP,
35
+ last_spawned_at TIMESTAMP,
36
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
37
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
38
+ PRIMARY KEY (provider, external_id)
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS ${T.external_execution_links} (
42
+ provider TEXT NOT NULL,
43
+ external_id TEXT NOT NULL,
44
+ execution_id TEXT NOT NULL,
45
+ linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
+ PRIMARY KEY (provider, external_id, execution_id),
47
+ FOREIGN KEY (provider, external_id)
48
+ REFERENCES ${T.external_execution_map}(provider, external_id)
49
+ ON DELETE CASCADE
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS ${T.external_execution_prs} (
53
+ provider TEXT NOT NULL,
54
+ external_id TEXT NOT NULL,
55
+ pr_url TEXT NOT NULL,
56
+ linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
57
+ PRIMARY KEY (provider, external_id, pr_url),
58
+ FOREIGN KEY (provider, external_id)
59
+ REFERENCES ${T.external_execution_map}(provider, external_id)
60
+ ON DELETE CASCADE
61
+ );
62
+
63
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_map_external_key
64
+ ON ${T.external_execution_map}(provider, external_key);
65
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_links_execution_id
66
+ ON ${T.external_execution_links}(execution_id);
67
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_prs_pr_url
68
+ ON ${T.external_execution_prs}(pr_url);
69
+ `);
70
+ }
71
+ upsertMapping(input) {
72
+ const snapshot = input.latestStateSnapshot === undefined
73
+ ? null
74
+ : JSON.stringify(input.latestStateSnapshot);
75
+ const lastSyncedAt = input.lastSyncedAt?.toISOString() ?? null;
76
+ const lastSpawnedAt = input.lastSpawnedAt?.toISOString() ?? null;
77
+ this.db.transaction(() => {
78
+ this.db.prepare(`
79
+ INSERT INTO ${T.external_execution_map}
80
+ (provider, external_id, external_key, canonical_url, latest_state_snapshot, last_synced_at, last_spawned_at, created_at, updated_at)
81
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
82
+ ON CONFLICT(provider, external_id) DO UPDATE SET
83
+ external_key = COALESCE(excluded.external_key, ${T.external_execution_map}.external_key),
84
+ canonical_url = COALESCE(excluded.canonical_url, ${T.external_execution_map}.canonical_url),
85
+ latest_state_snapshot = COALESCE(excluded.latest_state_snapshot, ${T.external_execution_map}.latest_state_snapshot),
86
+ last_synced_at = COALESCE(excluded.last_synced_at, ${T.external_execution_map}.last_synced_at),
87
+ last_spawned_at = COALESCE(excluded.last_spawned_at, ${T.external_execution_map}.last_spawned_at),
88
+ updated_at = CURRENT_TIMESTAMP
89
+ `).run(input.provider, input.externalId, input.externalKey ?? null, input.canonicalUrl ?? null, snapshot, lastSyncedAt, lastSpawnedAt);
90
+ if (input.executionId) {
91
+ this.db.prepare(`
92
+ INSERT OR IGNORE INTO ${T.external_execution_links}
93
+ (provider, external_id, execution_id, linked_at)
94
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
95
+ `).run(input.provider, input.externalId, input.executionId);
96
+ }
97
+ if (input.prUrl) {
98
+ this.db.prepare(`
99
+ INSERT OR IGNORE INTO ${T.external_execution_prs}
100
+ (provider, external_id, pr_url, linked_at)
101
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
102
+ `).run(input.provider, input.externalId, input.prUrl);
103
+ }
104
+ })();
105
+ return this.getByExternalId(input.provider, input.externalId);
106
+ }
107
+ getByExternalId(provider, externalId) {
108
+ const row = this.db.prepare(`
109
+ SELECT * FROM ${T.external_execution_map}
110
+ WHERE provider = ? AND external_id = ?
111
+ `).get(provider, externalId);
112
+ if (!row)
113
+ return null;
114
+ return this.rowToMapping(row);
115
+ }
116
+ getByExternalKey(provider, externalKey) {
117
+ const row = this.db.prepare(`
118
+ SELECT * FROM ${T.external_execution_map}
119
+ WHERE provider = ? AND external_key = ?
120
+ `).get(provider, externalKey);
121
+ if (!row)
122
+ return null;
123
+ return this.rowToMapping(row);
124
+ }
125
+ findByExecutionId(executionId) {
126
+ const rows = this.db.prepare(`
127
+ SELECT m.*
128
+ FROM ${T.external_execution_map} m
129
+ INNER JOIN ${T.external_execution_links} l
130
+ ON m.provider = l.provider
131
+ AND m.external_id = l.external_id
132
+ WHERE l.execution_id = ?
133
+ ORDER BY m.updated_at DESC
134
+ `).all(executionId);
135
+ return rows.map((row) => this.rowToMapping(row));
136
+ }
137
+ rowToMapping(row) {
138
+ const executionIds = this.db.prepare(`
139
+ SELECT execution_id AS value
140
+ FROM ${T.external_execution_links}
141
+ WHERE provider = ? AND external_id = ?
142
+ ORDER BY linked_at DESC
143
+ `).all(row.provider, row.external_id);
144
+ const prUrls = this.db.prepare(`
145
+ SELECT pr_url AS value
146
+ FROM ${T.external_execution_prs}
147
+ WHERE provider = ? AND external_id = ?
148
+ ORDER BY linked_at DESC
149
+ `).all(row.provider, row.external_id);
150
+ return {
151
+ provider: row.provider,
152
+ externalId: row.external_id,
153
+ externalKey: row.external_key,
154
+ canonicalUrl: row.canonical_url,
155
+ latestStateSnapshot: parseSnapshot(row.latest_state_snapshot),
156
+ executionIds: executionIds.map((r) => r.value),
157
+ prUrls: prUrls.map((r) => r.value),
158
+ lastSyncedAt: parseDate(row.last_synced_at),
159
+ lastSpawnedAt: parseDate(row.last_spawned_at),
160
+ createdAt: new Date(row.created_at),
161
+ updatedAt: new Date(row.updated_at),
162
+ };
163
+ }
164
+ }
@@ -14,6 +14,10 @@ export type IssueSource = 'linear' | 'jira';
14
14
  * All valid issue sources as a const array.
15
15
  */
16
16
  export declare const ISSUE_SOURCES: readonly ["linear", "jira"];
17
+ /**
18
+ * Providers supported by the external execution mapping store.
19
+ */
20
+ export type ExternalMappingProvider = 'linear' | 'jira' | 'asana' | 'monday' | 'pmo';
17
21
  /**
18
22
  * Canonical envelope for external issues/work items.
19
23
  *
@@ -129,6 +133,36 @@ export interface ExternalIssueAdapter {
129
133
  */
130
134
  fetchByQuery(query: Record<string, unknown>): Promise<IssueEnvelope[]>;
131
135
  }
136
+ /**
137
+ * Provider-agnostic external issue ↔ execution mapping record.
138
+ */
139
+ export interface ExternalExecutionMapping {
140
+ provider: ExternalMappingProvider;
141
+ externalId: string;
142
+ externalKey: string | null;
143
+ canonicalUrl: string | null;
144
+ latestStateSnapshot: Record<string, unknown> | null;
145
+ executionIds: string[];
146
+ prUrls: string[];
147
+ lastSyncedAt: Date | null;
148
+ lastSpawnedAt: Date | null;
149
+ createdAt: Date;
150
+ updatedAt: Date;
151
+ }
152
+ /**
153
+ * Upsert payload for provider-agnostic external issue mapping.
154
+ */
155
+ export interface UpsertExternalExecutionMappingInput {
156
+ provider: ExternalMappingProvider;
157
+ externalId: string;
158
+ externalKey?: string | null;
159
+ canonicalUrl?: string | null;
160
+ latestStateSnapshot?: Record<string, unknown> | null;
161
+ executionId?: string;
162
+ prUrl?: string;
163
+ lastSyncedAt?: Date;
164
+ lastSpawnedAt?: Date;
165
+ }
132
166
  /**
133
167
  * Error codes for external issue operations.
134
168
  */
@@ -16,6 +16,7 @@ const REQUIRED_STRING_FIELDS = [
16
16
  'url',
17
17
  'project_key',
18
18
  ];
19
+ const PRIORITY_VALUES = new Set(['P0', 'P1', 'P2', 'P3']);
19
20
  /**
20
21
  * Validate and construct an IssueEnvelope from untyped input.
21
22
  *
@@ -145,6 +146,16 @@ export function validateIssueEnvelope(input) {
145
146
  message: 'priority must be a string or null',
146
147
  });
147
148
  }
149
+ else if (typeof data.priority === 'string') {
150
+ const normalizedPriority = data.priority.trim().toUpperCase();
151
+ if (!PRIORITY_VALUES.has(normalizedPriority)) {
152
+ errors.push({
153
+ code: 'INVALID_FIELD_TYPE',
154
+ field: 'priority',
155
+ message: 'priority must be one of: P0, P1, P2, P3, or null',
156
+ });
157
+ }
158
+ }
148
159
  // Validate assignee (string or null)
149
160
  if (!('assignee' in data) || data.assignee === undefined) {
150
161
  errors.push({
@@ -0,0 +1,10 @@
1
+ export interface MirrorResolutionInput {
2
+ flagValue?: boolean;
3
+ envValue?: boolean | null;
4
+ configValue?: boolean | null;
5
+ }
6
+ export interface MirrorResolution {
7
+ enabled: boolean;
8
+ source: 'flag' | 'env' | 'config' | 'default';
9
+ }
10
+ export declare function resolveMirrorToPmo(input: MirrorResolutionInput): MirrorResolution;
@@ -0,0 +1,12 @@
1
+ export function resolveMirrorToPmo(input) {
2
+ if (input.flagValue !== undefined) {
3
+ return { enabled: input.flagValue, source: 'flag' };
4
+ }
5
+ if (input.envValue !== null && input.envValue !== undefined) {
6
+ return { enabled: input.envValue, source: 'env' };
7
+ }
8
+ if (input.configValue !== null && input.configValue !== undefined) {
9
+ return { enabled: input.configValue, source: 'config' };
10
+ }
11
+ return { enabled: true, source: 'default' };
12
+ }
@@ -9,6 +9,7 @@ import type { CreateTicketInput, PMOStorage, WorkflowStatus } from '../pmo/types
9
9
  import type { LinearIssue, LinearIssueMap, LinearSyncResult } from './types.js';
10
10
  export declare class LinearMapper {
11
11
  private db;
12
+ private externalMappingStore;
12
13
  constructor(db: Database.Database);
13
14
  /**
14
15
  * Ensure the linear_issue_map table exists.
@@ -64,4 +65,5 @@ export declare class LinearMapper {
64
65
  * Convert a database row to a LinearIssueMap.
65
66
  */
66
67
  private rowToMap;
68
+ private externalMappingToLinearMap;
67
69
  }
@@ -5,11 +5,14 @@
5
5
  * Handles import (Linear → PMO) and reverse lookup for sync operations.
6
6
  */
7
7
  import { PMO_TABLES } from '../pmo/schema.js';
8
+ import { ExternalExecutionMappingStore } from '../external-issues/mapping-store.js';
8
9
  import { LINEAR_STATE_TO_PMO_CATEGORY, LINEAR_PRIORITY_TO_PMO, } from './types.js';
9
10
  export class LinearMapper {
10
11
  db;
12
+ externalMappingStore;
11
13
  constructor(db) {
12
14
  this.db = db;
15
+ this.externalMappingStore = new ExternalExecutionMappingStore(db);
13
16
  this.ensureTable();
14
17
  }
15
18
  /**
@@ -146,6 +149,18 @@ export class LinearMapper {
146
149
  (pmo_ticket_id, linear_issue_id, linear_identifier, linear_team_key, linear_url, sync_direction, last_synced_at, created_at)
147
150
  VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
148
151
  `).run(map.pmoTicketId, map.linearIssueId, map.linearIdentifier, map.linearTeamKey, map.linearUrl, map.syncDirection);
152
+ this.externalMappingStore.upsertMapping({
153
+ provider: 'linear',
154
+ externalId: map.linearIssueId,
155
+ externalKey: map.linearIdentifier,
156
+ canonicalUrl: map.linearUrl,
157
+ latestStateSnapshot: {
158
+ pmoTicketId: map.pmoTicketId,
159
+ linearTeamKey: map.linearTeamKey,
160
+ syncDirection: map.syncDirection,
161
+ },
162
+ lastSyncedAt: new Date(),
163
+ });
149
164
  }
150
165
  /**
151
166
  * Get a mapping by PMO ticket ID.
@@ -163,7 +178,11 @@ export class LinearMapper {
163
178
  const row = this.db.prepare(`
164
179
  SELECT * FROM ${PMO_TABLES.linear_issue_map} WHERE linear_issue_id = ?
165
180
  `).get(linearIssueId);
166
- return row ? this.rowToMap(row) : null;
181
+ if (row) {
182
+ return this.rowToMap(row);
183
+ }
184
+ const mapping = this.externalMappingStore.getByExternalId('linear', linearIssueId);
185
+ return mapping ? this.externalMappingToLinearMap(mapping) : null;
167
186
  }
168
187
  /**
169
188
  * Get a mapping by Linear identifier (e.g., "ENG-123").
@@ -172,7 +191,11 @@ export class LinearMapper {
172
191
  const row = this.db.prepare(`
173
192
  SELECT * FROM ${PMO_TABLES.linear_issue_map} WHERE linear_identifier = ?
174
193
  `).get(identifier);
175
- return row ? this.rowToMap(row) : null;
194
+ if (row) {
195
+ return this.rowToMap(row);
196
+ }
197
+ const mapping = this.externalMappingStore.getByExternalKey('linear', identifier);
198
+ return mapping ? this.externalMappingToLinearMap(mapping) : null;
176
199
  }
177
200
  /**
178
201
  * List all mappings.
@@ -192,6 +215,21 @@ export class LinearMapper {
192
215
  SET last_synced_at = CURRENT_TIMESTAMP
193
216
  WHERE pmo_ticket_id = ?
194
217
  `).run(pmoTicketId);
218
+ const map = this.getByTicketId(pmoTicketId);
219
+ if (map) {
220
+ this.externalMappingStore.upsertMapping({
221
+ provider: 'linear',
222
+ externalId: map.linearIssueId,
223
+ externalKey: map.linearIdentifier,
224
+ canonicalUrl: map.linearUrl,
225
+ latestStateSnapshot: {
226
+ pmoTicketId: map.pmoTicketId,
227
+ linearTeamKey: map.linearTeamKey,
228
+ syncDirection: map.syncDirection,
229
+ },
230
+ lastSyncedAt: new Date(),
231
+ });
232
+ }
195
233
  }
196
234
  /**
197
235
  * Delete a mapping by PMO ticket ID.
@@ -216,4 +254,30 @@ export class LinearMapper {
216
254
  createdAt: new Date(row.created_at),
217
255
  };
218
256
  }
257
+ externalMappingToLinearMap(row) {
258
+ const snapshot = row.latestStateSnapshot ?? {};
259
+ const ticketId = typeof snapshot['pmoTicketId'] === 'string' ? snapshot['pmoTicketId'] : null;
260
+ if (!ticketId) {
261
+ return null;
262
+ }
263
+ const teamKey = typeof snapshot['linearTeamKey'] === 'string'
264
+ ? snapshot['linearTeamKey']
265
+ : (typeof snapshot['teamKey'] === 'string' ? snapshot['teamKey'] : null);
266
+ const resolvedTeamKey = teamKey
267
+ ? teamKey
268
+ : (row.externalKey?.split('-')[0] ?? 'UNKNOWN');
269
+ const syncDirection = typeof snapshot['syncDirection'] === 'string'
270
+ ? snapshot['syncDirection']
271
+ : 'inbound';
272
+ return {
273
+ pmoTicketId: ticketId,
274
+ linearIssueId: row.externalId,
275
+ linearIdentifier: row.externalKey ?? row.externalId,
276
+ linearTeamKey: resolvedTeamKey,
277
+ linearUrl: row.canonicalUrl ?? '',
278
+ syncDirection: syncDirection,
279
+ lastSyncedAt: row.lastSyncedAt ?? undefined,
280
+ createdAt: row.createdAt,
281
+ };
282
+ }
219
283
  }
@@ -0,0 +1,14 @@
1
+ import type { MondayBoard, MondayItem } from './types.js';
2
+ export declare class MondayClient {
3
+ private apiToken;
4
+ constructor(apiToken: string);
5
+ verify(): Promise<{
6
+ accountName: string;
7
+ userName: string;
8
+ email?: string;
9
+ }>;
10
+ getBoard(boardId: string): Promise<MondayBoard | null>;
11
+ createItem(boardId: string, itemName: string): Promise<MondayItem>;
12
+ updateItemName(boardId: string, itemId: string, name: string): Promise<void>;
13
+ private request;
14
+ }
@@ -0,0 +1,113 @@
1
+ const MONDAY_API_URL = 'https://api.monday.com/v2';
2
+ export class MondayClient {
3
+ apiToken;
4
+ constructor(apiToken) {
5
+ this.apiToken = apiToken;
6
+ }
7
+ async verify() {
8
+ const data = await this.request(`
9
+ query VerifyMondayConnection {
10
+ me {
11
+ id
12
+ name
13
+ email
14
+ }
15
+ account {
16
+ id
17
+ name
18
+ slug
19
+ }
20
+ }
21
+ `);
22
+ return {
23
+ accountName: data.account.name,
24
+ userName: data.me.name,
25
+ email: data.me.email,
26
+ };
27
+ }
28
+ async getBoard(boardId) {
29
+ const data = await this.request(`
30
+ query GetBoard($boardIds: [ID!]) {
31
+ boards(ids: $boardIds) {
32
+ id
33
+ name
34
+ url
35
+ }
36
+ }
37
+ `, { boardIds: [boardId] });
38
+ if (data.boards.length === 0)
39
+ return null;
40
+ return {
41
+ id: data.boards[0].id,
42
+ name: data.boards[0].name,
43
+ url: data.boards[0].url ?? undefined,
44
+ };
45
+ }
46
+ async createItem(boardId, itemName) {
47
+ const data = await this.request(`
48
+ mutation CreateItem($boardId: ID!, $itemName: String!) {
49
+ create_item(board_id: $boardId, item_name: $itemName) {
50
+ id
51
+ name
52
+ url
53
+ }
54
+ }
55
+ `, {
56
+ boardId,
57
+ itemName,
58
+ });
59
+ return {
60
+ id: data.create_item.id,
61
+ name: data.create_item.name,
62
+ url: data.create_item.url ?? undefined,
63
+ };
64
+ }
65
+ async updateItemName(boardId, itemId, name) {
66
+ await this.request(`
67
+ mutation UpdateItemName($boardId: ID!, $itemId: ID!, $value: String!) {
68
+ change_simple_column_value(
69
+ board_id: $boardId
70
+ item_id: $itemId
71
+ column_id: "name"
72
+ value: $value
73
+ ) {
74
+ id
75
+ }
76
+ }
77
+ `, {
78
+ boardId,
79
+ itemId,
80
+ value: name,
81
+ });
82
+ }
83
+ async request(query, variables) {
84
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
85
+ const response = await fetch(MONDAY_API_URL, {
86
+ method: 'POST',
87
+ headers: {
88
+ Authorization: this.apiToken,
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ body: JSON.stringify({ query, variables }),
92
+ });
93
+ const responseText = await response.text();
94
+ let payload;
95
+ try {
96
+ payload = JSON.parse(responseText);
97
+ }
98
+ catch {
99
+ throw new Error(`Monday API returned invalid JSON (${response.status}): ${responseText}`);
100
+ }
101
+ if (!response.ok) {
102
+ const message = payload.errors?.[0]?.message ?? responseText;
103
+ throw new Error(`Monday API request failed (${response.status}): ${message}`);
104
+ }
105
+ if (payload.errors && payload.errors.length > 0) {
106
+ throw new Error(payload.errors[0].message);
107
+ }
108
+ if (!payload.data) {
109
+ throw new Error('Monday API response missing data');
110
+ }
111
+ return payload.data;
112
+ }
113
+ }
@@ -0,0 +1,10 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { MondayConfig } from './types.js';
3
+ export declare function isMondayConfigured(db: Database.Database): boolean;
4
+ export declare function loadMondayConfig(db: Database.Database): MondayConfig | null;
5
+ export declare function saveMondayApiToken(db: Database.Database, apiToken: string): void;
6
+ export declare function saveMondayBoard(db: Database.Database, boardId: string, boardName: string): void;
7
+ export declare function saveMondayAccountName(db: Database.Database, accountName: string): void;
8
+ export declare function clearMondayConfig(db: Database.Database): void;
9
+ export declare function getMondayApiToken(db: Database.Database): string | null;
10
+ export declare function getMondayBoardId(db: Database.Database): string | null;
@@ -0,0 +1,64 @@
1
+ const SETTINGS_TABLE = 'workspace_settings';
2
+ const MONDAY_CONFIG_KEYS = {
3
+ apiToken: 'monday.api_token',
4
+ boardId: 'monday.board_id',
5
+ boardName: 'monday.board_name',
6
+ accountName: 'monday.account_name',
7
+ };
8
+ function getSetting(db, key) {
9
+ const row = db
10
+ .prepare(`SELECT value FROM ${SETTINGS_TABLE} WHERE key = ?`)
11
+ .get(key);
12
+ return row?.value ?? null;
13
+ }
14
+ function setSetting(db, key, value) {
15
+ db.prepare(`
16
+ INSERT INTO ${SETTINGS_TABLE} (key, value)
17
+ VALUES (?, ?)
18
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
19
+ `).run(key, value);
20
+ }
21
+ function deleteSetting(db, key) {
22
+ db.prepare(`DELETE FROM ${SETTINGS_TABLE} WHERE key = ?`).run(key);
23
+ }
24
+ export function isMondayConfigured(db) {
25
+ return getSetting(db, MONDAY_CONFIG_KEYS.apiToken) !== null;
26
+ }
27
+ export function loadMondayConfig(db) {
28
+ const apiToken = getSetting(db, MONDAY_CONFIG_KEYS.apiToken);
29
+ if (!apiToken)
30
+ return null;
31
+ return {
32
+ apiToken,
33
+ boardId: getSetting(db, MONDAY_CONFIG_KEYS.boardId) ?? undefined,
34
+ boardName: getSetting(db, MONDAY_CONFIG_KEYS.boardName) ?? undefined,
35
+ accountName: getSetting(db, MONDAY_CONFIG_KEYS.accountName) ?? undefined,
36
+ };
37
+ }
38
+ export function saveMondayApiToken(db, apiToken) {
39
+ setSetting(db, MONDAY_CONFIG_KEYS.apiToken, apiToken);
40
+ }
41
+ export function saveMondayBoard(db, boardId, boardName) {
42
+ setSetting(db, MONDAY_CONFIG_KEYS.boardId, boardId);
43
+ setSetting(db, MONDAY_CONFIG_KEYS.boardName, boardName);
44
+ }
45
+ export function saveMondayAccountName(db, accountName) {
46
+ setSetting(db, MONDAY_CONFIG_KEYS.accountName, accountName);
47
+ }
48
+ export function clearMondayConfig(db) {
49
+ for (const key of Object.values(MONDAY_CONFIG_KEYS)) {
50
+ deleteSetting(db, key);
51
+ }
52
+ }
53
+ export function getMondayApiToken(db) {
54
+ const envToken = process.env.PRLT_MONDAY_API_TOKEN || process.env.MONDAY_API_TOKEN;
55
+ if (envToken)
56
+ return envToken;
57
+ return getSetting(db, MONDAY_CONFIG_KEYS.apiToken);
58
+ }
59
+ export function getMondayBoardId(db) {
60
+ const envBoardId = process.env.PRLT_MONDAY_BOARD_ID || process.env.MONDAY_BOARD_ID;
61
+ if (envBoardId)
62
+ return envBoardId;
63
+ return getSetting(db, MONDAY_CONFIG_KEYS.boardId);
64
+ }
@@ -0,0 +1,5 @@
1
+ export { MondayClient } from './client.js';
2
+ export { isMondayConfigured, loadMondayConfig, saveMondayApiToken, saveMondayBoard, saveMondayAccountName, clearMondayConfig, getMondayApiToken, getMondayBoardId, } from './config.js';
3
+ export { MondayMapper } from './mapper.js';
4
+ export { MondaySync } from './sync.js';
5
+ export type { MondayBoard, MondayItem, MondayConfig, MondayItemMap, MondaySyncResult, } from './types.js';
@@ -0,0 +1,4 @@
1
+ export { MondayClient } from './client.js';
2
+ export { isMondayConfigured, loadMondayConfig, saveMondayApiToken, saveMondayBoard, saveMondayAccountName, clearMondayConfig, getMondayApiToken, getMondayBoardId, } from './config.js';
3
+ export { MondayMapper } from './mapper.js';
4
+ export { MondaySync } from './sync.js';
@@ -0,0 +1,14 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { MondayItemMap } from './types.js';
3
+ export declare class MondayMapper {
4
+ private db;
5
+ constructor(db: Database.Database);
6
+ private ensureTable;
7
+ createOrUpdateMapping(map: Omit<MondayItemMap, 'lastSyncedAt' | 'createdAt'>): void;
8
+ getByTicketId(ticketId: string): MondayItemMap | null;
9
+ getByMondayItemId(itemId: string): MondayItemMap | null;
10
+ listMappings(): MondayItemMap[];
11
+ updateSyncTimestamp(ticketId: string): void;
12
+ deleteMapping(ticketId: string): void;
13
+ private rowToMap;
14
+ }