panopticon-cli 0.2.0 → 0.3.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.
@@ -1,76 +1,19 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
- var __commonJS = (cb, mod) => function __require2() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
-
12
- // src/lib/paths.ts
13
- import { homedir } from "os";
14
- import { join } from "path";
15
- import { fileURLToPath } from "url";
16
- import { dirname } from "path";
17
- var PANOPTICON_HOME = join(homedir(), ".panopticon");
18
- var CONFIG_DIR = PANOPTICON_HOME;
19
- var SKILLS_DIR = join(PANOPTICON_HOME, "skills");
20
- var COMMANDS_DIR = join(PANOPTICON_HOME, "commands");
21
- var AGENTS_DIR = join(PANOPTICON_HOME, "agents");
22
- var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
23
- var COSTS_DIR = join(PANOPTICON_HOME, "costs");
24
- var TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
25
- var TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
26
- var TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
27
- var CERTS_DIR = join(PANOPTICON_HOME, "certs");
28
- var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
29
- var CLAUDE_DIR = join(homedir(), ".claude");
30
- var CODEX_DIR = join(homedir(), ".codex");
31
- var CURSOR_DIR = join(homedir(), ".cursor");
32
- var GEMINI_DIR = join(homedir(), ".gemini");
33
- var SYNC_TARGETS = {
34
- claude: {
35
- skills: join(CLAUDE_DIR, "skills"),
36
- commands: join(CLAUDE_DIR, "commands")
37
- },
38
- codex: {
39
- skills: join(CODEX_DIR, "skills"),
40
- commands: join(CODEX_DIR, "commands")
41
- },
42
- cursor: {
43
- skills: join(CURSOR_DIR, "skills"),
44
- commands: join(CURSOR_DIR, "commands")
45
- },
46
- gemini: {
47
- skills: join(GEMINI_DIR, "skills"),
48
- commands: join(GEMINI_DIR, "commands")
49
- }
50
- };
51
- var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
52
- var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
53
- var currentFile = fileURLToPath(import.meta.url);
54
- var distDir = dirname(currentFile);
55
- var packageRoot = dirname(distDir);
56
- var SOURCE_TEMPLATES_DIR = join(packageRoot, "templates");
57
- var SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
58
- var INIT_DIRS = [
59
- PANOPTICON_HOME,
60
- SKILLS_DIR,
61
- COMMANDS_DIR,
1
+ import {
62
2
  AGENTS_DIR,
63
3
  BACKUPS_DIR,
64
- COSTS_DIR,
65
- TEMPLATES_DIR,
66
- CLAUDE_MD_TEMPLATES,
67
- CERTS_DIR,
68
- TRAEFIK_DIR,
69
- TRAEFIK_DYNAMIC_DIR,
70
- TRAEFIK_CERTS_DIR
71
- ];
4
+ BIN_DIR,
5
+ COMMANDS_DIR,
6
+ CONFIG_FILE,
7
+ SKILLS_DIR,
8
+ SOURCE_SCRIPTS_DIR,
9
+ SYNC_TARGETS,
10
+ init_esm_shims,
11
+ init_paths
12
+ } from "./chunk-SG7O6I7R.js";
72
13
 
73
14
  // src/lib/config.ts
15
+ init_esm_shims();
16
+ init_paths();
74
17
  import { readFileSync, writeFileSync, existsSync } from "fs";
75
18
  import { parse, stringify } from "@iarna/toml";
76
19
  var DEFAULT_CONFIG = {
@@ -95,6 +38,20 @@ var DEFAULT_CONFIG = {
95
38
  api_port: 3002
96
39
  }
97
40
  };
41
+ function deepMerge(defaults, overrides) {
42
+ const result = { ...defaults };
43
+ for (const key of Object.keys(overrides)) {
44
+ const defaultVal = defaults[key];
45
+ const overrideVal = overrides[key];
46
+ if (overrideVal === void 0) continue;
47
+ if (typeof defaultVal === "object" && defaultVal !== null && !Array.isArray(defaultVal) && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(overrideVal)) {
48
+ result[key] = deepMerge(defaultVal, overrideVal);
49
+ } else {
50
+ result[key] = overrideVal;
51
+ }
52
+ }
53
+ return result;
54
+ }
98
55
  function loadConfig() {
99
56
  if (!existsSync(CONFIG_FILE)) {
100
57
  return DEFAULT_CONFIG;
@@ -102,7 +59,7 @@ function loadConfig() {
102
59
  try {
103
60
  const content = readFileSync(CONFIG_FILE, "utf8");
104
61
  const parsed = parse(content);
105
- return { ...DEFAULT_CONFIG, ...parsed };
62
+ return deepMerge(DEFAULT_CONFIG, parsed);
106
63
  } catch (error) {
107
64
  console.error("Warning: Failed to parse config, using defaults");
108
65
  return DEFAULT_CONFIG;
@@ -117,9 +74,10 @@ function getDefaultConfig() {
117
74
  }
118
75
 
119
76
  // src/lib/shell.ts
77
+ init_esm_shims();
120
78
  import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
121
- import { homedir as homedir2 } from "os";
122
- import { join as join2 } from "path";
79
+ import { homedir } from "os";
80
+ import { join } from "path";
123
81
  function detectShell() {
124
82
  const shell = process.env.SHELL || "";
125
83
  if (shell.includes("zsh")) return "zsh";
@@ -128,16 +86,16 @@ function detectShell() {
128
86
  return "unknown";
129
87
  }
130
88
  function getShellRcFile(shell) {
131
- const home = homedir2();
89
+ const home = homedir();
132
90
  switch (shell) {
133
91
  case "zsh":
134
- return join2(home, ".zshrc");
92
+ return join(home, ".zshrc");
135
93
  case "bash":
136
- const bashrc = join2(home, ".bashrc");
94
+ const bashrc = join(home, ".bashrc");
137
95
  if (existsSync2(bashrc)) return bashrc;
138
- return join2(home, ".bash_profile");
96
+ return join(home, ".bash_profile");
139
97
  case "fish":
140
- return join2(home, ".config", "fish", "config.fish");
98
+ return join(home, ".config", "fish", "config.fish");
141
99
  default:
142
100
  return null;
143
101
  }
@@ -168,20 +126,22 @@ function getAliasInstructions(shell) {
168
126
  }
169
127
 
170
128
  // src/lib/backup.ts
129
+ init_esm_shims();
130
+ init_paths();
171
131
  import { existsSync as existsSync3, mkdirSync, readdirSync, cpSync, rmSync } from "fs";
172
- import { join as join3, basename } from "path";
132
+ import { join as join2, basename } from "path";
173
133
  function createBackupTimestamp() {
174
134
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
175
135
  }
176
136
  function createBackup(sourceDirs) {
177
137
  const timestamp = createBackupTimestamp();
178
- const backupPath = join3(BACKUPS_DIR, timestamp);
138
+ const backupPath = join2(BACKUPS_DIR, timestamp);
179
139
  mkdirSync(backupPath, { recursive: true });
180
140
  const targets = [];
181
141
  for (const sourceDir of sourceDirs) {
182
142
  if (!existsSync3(sourceDir)) continue;
183
143
  const targetName = basename(sourceDir);
184
- const targetPath = join3(backupPath, targetName);
144
+ const targetPath = join2(backupPath, targetName);
185
145
  cpSync(sourceDir, targetPath, { recursive: true });
186
146
  targets.push(targetName);
187
147
  }
@@ -195,7 +155,7 @@ function listBackups() {
195
155
  if (!existsSync3(BACKUPS_DIR)) return [];
196
156
  const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });
197
157
  return entries.filter((e) => e.isDirectory()).map((e) => {
198
- const backupPath = join3(BACKUPS_DIR, e.name);
158
+ const backupPath = join2(BACKUPS_DIR, e.name);
199
159
  const contents = readdirSync(backupPath);
200
160
  return {
201
161
  timestamp: e.name,
@@ -205,14 +165,14 @@ function listBackups() {
205
165
  }).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
206
166
  }
207
167
  function restoreBackup(timestamp, targetDirs) {
208
- const backupPath = join3(BACKUPS_DIR, timestamp);
168
+ const backupPath = join2(BACKUPS_DIR, timestamp);
209
169
  if (!existsSync3(backupPath)) {
210
170
  throw new Error(`Backup not found: ${timestamp}`);
211
171
  }
212
172
  const contents = readdirSync(backupPath, { withFileTypes: true });
213
173
  for (const entry of contents) {
214
174
  if (!entry.isDirectory()) continue;
215
- const sourcePath = join3(backupPath, entry.name);
175
+ const sourcePath = join2(backupPath, entry.name);
216
176
  const targetPath = targetDirs[entry.name];
217
177
  if (!targetPath) continue;
218
178
  if (existsSync3(targetPath)) {
@@ -234,8 +194,10 @@ function cleanOldBackups(keepCount = 10) {
234
194
  }
235
195
 
236
196
  // src/lib/sync.ts
237
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync as rmSync2 } from "fs";
238
- import { join as join4 } from "path";
197
+ init_esm_shims();
198
+ init_paths();
199
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
200
+ import { join as join3 } from "path";
239
201
  function removeTarget(targetPath) {
240
202
  const stats = lstatSync(targetPath);
241
203
  if (stats.isDirectory() && !stats.isSymbolicLink()) {
@@ -260,13 +222,14 @@ function planSync(runtime) {
260
222
  const plan = {
261
223
  runtime,
262
224
  skills: [],
263
- commands: []
225
+ commands: [],
226
+ agents: []
264
227
  };
265
228
  if (existsSync4(SKILLS_DIR)) {
266
229
  const skills = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
267
230
  for (const skill of skills) {
268
- const sourcePath = join4(SKILLS_DIR, skill.name);
269
- const targetPath = join4(targets.skills, skill.name);
231
+ const sourcePath = join3(SKILLS_DIR, skill.name);
232
+ const targetPath = join3(targets.skills, skill.name);
270
233
  let status = "new";
271
234
  if (existsSync4(targetPath)) {
272
235
  if (isPanopticonSymlink(targetPath)) {
@@ -281,8 +244,8 @@ function planSync(runtime) {
281
244
  if (existsSync4(COMMANDS_DIR)) {
282
245
  const commands = readdirSync2(COMMANDS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
283
246
  for (const cmd of commands) {
284
- const sourcePath = join4(COMMANDS_DIR, cmd.name);
285
- const targetPath = join4(targets.commands, cmd.name);
247
+ const sourcePath = join3(COMMANDS_DIR, cmd.name);
248
+ const targetPath = join3(targets.commands, cmd.name);
286
249
  let status = "new";
287
250
  if (existsSync4(targetPath)) {
288
251
  if (isPanopticonSymlink(targetPath)) {
@@ -294,6 +257,22 @@ function planSync(runtime) {
294
257
  plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });
295
258
  }
296
259
  }
260
+ if (existsSync4(AGENTS_DIR)) {
261
+ const agents = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
262
+ for (const agent of agents) {
263
+ const sourcePath = join3(AGENTS_DIR, agent.name);
264
+ const targetPath = join3(targets.agents, agent.name);
265
+ let status = "new";
266
+ if (existsSync4(targetPath)) {
267
+ if (isPanopticonSymlink(targetPath)) {
268
+ status = "symlink";
269
+ } else {
270
+ status = "conflict";
271
+ }
272
+ }
273
+ plan.agents.push({ name: agent.name, sourcePath, targetPath, status });
274
+ }
275
+ }
297
276
  return plan;
298
277
  }
299
278
  function executeSync(runtime, options = {}) {
@@ -306,6 +285,7 @@ function executeSync(runtime, options = {}) {
306
285
  const targets = SYNC_TARGETS[runtime];
307
286
  mkdirSync2(targets.skills, { recursive: true });
308
287
  mkdirSync2(targets.commands, { recursive: true });
288
+ mkdirSync2(targets.agents, { recursive: true });
309
289
  for (const item of plan.skills) {
310
290
  if (options.dryRun) {
311
291
  if (item.status === "new" || item.status === "symlink") {
@@ -344,10 +324,62 @@ function executeSync(runtime, options = {}) {
344
324
  symlinkSync(item.sourcePath, item.targetPath);
345
325
  result.created.push(item.name);
346
326
  }
327
+ for (const item of plan.agents) {
328
+ if (options.dryRun) {
329
+ if (item.status === "new" || item.status === "symlink") {
330
+ result.created.push(item.name);
331
+ } else {
332
+ result.conflicts.push(item.name);
333
+ }
334
+ continue;
335
+ }
336
+ if (item.status === "conflict" && !options.force) {
337
+ result.conflicts.push(item.name);
338
+ continue;
339
+ }
340
+ if (existsSync4(item.targetPath)) {
341
+ removeTarget(item.targetPath);
342
+ }
343
+ symlinkSync(item.sourcePath, item.targetPath);
344
+ result.created.push(item.name);
345
+ }
346
+ return result;
347
+ }
348
+ function planHooksSync() {
349
+ const hooks = [];
350
+ if (!existsSync4(SOURCE_SCRIPTS_DIR)) {
351
+ return hooks;
352
+ }
353
+ const scripts = readdirSync2(SOURCE_SCRIPTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && !entry.name.startsWith(".") && !entry.name.includes("."));
354
+ for (const script of scripts) {
355
+ const sourcePath = join3(SOURCE_SCRIPTS_DIR, script.name);
356
+ const targetPath = join3(BIN_DIR, script.name);
357
+ let status = "new";
358
+ if (existsSync4(targetPath)) {
359
+ status = "updated";
360
+ }
361
+ hooks.push({ name: script.name, sourcePath, targetPath, status });
362
+ }
363
+ return hooks;
364
+ }
365
+ function syncHooks() {
366
+ const result = { synced: [], errors: [] };
367
+ mkdirSync2(BIN_DIR, { recursive: true });
368
+ const hooks = planHooksSync();
369
+ for (const hook of hooks) {
370
+ try {
371
+ copyFileSync(hook.sourcePath, hook.targetPath);
372
+ chmodSync(hook.targetPath, 493);
373
+ result.synced.push(hook.name);
374
+ } catch (error) {
375
+ result.errors.push(`${hook.name}: ${error}`);
376
+ }
377
+ }
347
378
  return result;
348
379
  }
349
380
 
350
381
  // src/lib/tracker/interface.ts
382
+ init_esm_shims();
351
383
  var NotImplementedError = class extends Error {
352
384
  constructor(feature) {
353
385
  super(`Not implemented: ${feature}`);
@@ -368,6 +400,7 @@ var TrackerAuthError = class extends Error {
368
400
  };
369
401
 
370
402
  // src/lib/tracker/linear.ts
403
+ init_esm_shims();
371
404
  import { LinearClient } from "@linear/sdk";
372
405
  var STATE_MAP = {
373
406
  backlog: "open",
@@ -577,6 +610,7 @@ var LinearTracker = class {
577
610
  };
578
611
 
579
612
  // src/lib/tracker/github.ts
613
+ init_esm_shims();
580
614
  import { Octokit } from "@octokit/rest";
581
615
  var GitHubTracker = class {
582
616
  name = "github";
@@ -737,6 +771,7 @@ var GitHubTracker = class {
737
771
  };
738
772
 
739
773
  // src/lib/tracker/gitlab.ts
774
+ init_esm_shims();
740
775
  var GitLabTracker = class {
741
776
  constructor(token, projectId) {
742
777
  this.token = token;
@@ -785,6 +820,385 @@ var GitLabTracker = class {
785
820
  }
786
821
  };
787
822
 
823
+ // src/lib/tracker/factory.ts
824
+ init_esm_shims();
825
+
826
+ // src/lib/tracker/rally.ts
827
+ init_esm_shims();
828
+ import rally from "rally";
829
+ var STATE_MAP2 = {
830
+ Defined: "open",
831
+ "In-Progress": "in_progress",
832
+ Completed: "closed",
833
+ Accepted: "closed"
834
+ };
835
+ var PRIORITY_MAP = {
836
+ "Resolve Immediately": 0,
837
+ High: 1,
838
+ Normal: 2,
839
+ Low: 3
840
+ };
841
+ var REVERSE_PRIORITY_MAP = {
842
+ 0: "Resolve Immediately",
843
+ 1: "High",
844
+ 2: "Normal",
845
+ 3: "Low",
846
+ 4: "Low"
847
+ };
848
+ var RallyTracker = class {
849
+ name = "rally";
850
+ restApi;
851
+ workspace;
852
+ project;
853
+ constructor(config) {
854
+ if (!config.apiKey) {
855
+ throw new TrackerAuthError("rally", "API key is required");
856
+ }
857
+ this.restApi = rally({
858
+ apiKey: config.apiKey,
859
+ server: config.server || "https://rally1.rallydev.com",
860
+ requestOptions: {
861
+ headers: {
862
+ "X-RallyIntegrationType": "Panopticon",
863
+ "X-RallyIntegrationName": "Panopticon CLI",
864
+ "X-RallyIntegrationVendor": "Mind Your Now",
865
+ "X-RallyIntegrationVersion": "0.2.0"
866
+ }
867
+ }
868
+ });
869
+ this.workspace = config.workspace;
870
+ this.project = config.project;
871
+ }
872
+ async listIssues(filters) {
873
+ const query = {
874
+ type: "artifact",
875
+ // Query all artifact types
876
+ fetch: [
877
+ "FormattedID",
878
+ "Name",
879
+ "Description",
880
+ "ScheduleState",
881
+ "State",
882
+ // For Defects
883
+ "Tags",
884
+ "Owner",
885
+ "Priority",
886
+ "DueDate",
887
+ "CreationDate",
888
+ "LastUpdateDate",
889
+ "Parent",
890
+ "_type"
891
+ ],
892
+ limit: filters?.limit ?? 50,
893
+ query: this.buildQueryString(filters)
894
+ };
895
+ if (this.workspace) {
896
+ query.workspace = this.workspace;
897
+ }
898
+ if (this.project) {
899
+ query.project = this.project;
900
+ query.projectScopeDown = true;
901
+ }
902
+ try {
903
+ const result = await this.queryRally(query);
904
+ return result.Results.map((artifact) => this.normalizeIssue(artifact));
905
+ } catch (error) {
906
+ if (error.message?.includes("Unauthorized") || error.message?.includes("401")) {
907
+ throw new TrackerAuthError("rally", "Invalid API key or insufficient permissions");
908
+ }
909
+ throw error;
910
+ }
911
+ }
912
+ async getIssue(id) {
913
+ try {
914
+ const query = {
915
+ type: "artifact",
916
+ fetch: [
917
+ "FormattedID",
918
+ "Name",
919
+ "Description",
920
+ "ScheduleState",
921
+ "State",
922
+ "Tags",
923
+ "Owner",
924
+ "Priority",
925
+ "DueDate",
926
+ "CreationDate",
927
+ "LastUpdateDate",
928
+ "Parent",
929
+ "_type"
930
+ ],
931
+ query: `(FormattedID = "${id}")`
932
+ };
933
+ if (this.workspace) {
934
+ query.workspace = this.workspace;
935
+ }
936
+ const result = await this.queryRally(query);
937
+ if (!result.Results || result.Results.length === 0) {
938
+ throw new IssueNotFoundError(id, "rally");
939
+ }
940
+ return this.normalizeIssue(result.Results[0]);
941
+ } catch (error) {
942
+ if (error instanceof IssueNotFoundError) throw error;
943
+ throw new IssueNotFoundError(id, "rally");
944
+ }
945
+ }
946
+ async updateIssue(id, update) {
947
+ const issue = await this.getIssue(id);
948
+ const query = {
949
+ type: "artifact",
950
+ fetch: ["ObjectID", "_ref", "_type"],
951
+ query: `(FormattedID = "${id}")`
952
+ };
953
+ if (this.workspace) {
954
+ query.workspace = this.workspace;
955
+ }
956
+ const result = await this.queryRally(query);
957
+ if (!result.Results || result.Results.length === 0) {
958
+ throw new IssueNotFoundError(id, "rally");
959
+ }
960
+ const artifact = result.Results[0];
961
+ const updatePayload = {};
962
+ if (update.title !== void 0) {
963
+ updatePayload.Name = update.title;
964
+ }
965
+ if (update.description !== void 0) {
966
+ updatePayload.Description = update.description;
967
+ }
968
+ if (update.state !== void 0) {
969
+ const rallyState = this.reverseMapState(update.state);
970
+ if (artifact._type === "Defect") {
971
+ updatePayload.State = rallyState;
972
+ } else {
973
+ updatePayload.ScheduleState = rallyState;
974
+ }
975
+ }
976
+ if (update.priority !== void 0) {
977
+ updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || "Normal";
978
+ }
979
+ if (update.dueDate !== void 0) {
980
+ updatePayload.DueDate = update.dueDate;
981
+ }
982
+ if (Object.keys(updatePayload).length > 0) {
983
+ await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);
984
+ }
985
+ return this.getIssue(id);
986
+ }
987
+ async createIssue(newIssue) {
988
+ if (!this.project && !newIssue.team) {
989
+ throw new Error("Project is required to create an issue. Set it in config or provide team field.");
990
+ }
991
+ const project = newIssue.team || this.project;
992
+ const createPayload = {
993
+ Name: newIssue.title,
994
+ Description: newIssue.description || "",
995
+ Project: project
996
+ };
997
+ if (newIssue.priority !== void 0) {
998
+ createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || "Normal";
999
+ }
1000
+ if (newIssue.dueDate) {
1001
+ createPayload.DueDate = newIssue.dueDate;
1002
+ }
1003
+ if (this.workspace) {
1004
+ createPayload.Workspace = this.workspace;
1005
+ }
1006
+ const result = await this.createRally("hierarchicalrequirement", createPayload);
1007
+ return this.getIssue(result.Object.FormattedID);
1008
+ }
1009
+ async getComments(issueId) {
1010
+ const issue = await this.getIssue(issueId);
1011
+ const query = {
1012
+ type: "artifact",
1013
+ fetch: ["ObjectID", "_ref", "Discussion"],
1014
+ query: `(FormattedID = "${issueId}")`
1015
+ };
1016
+ if (this.workspace) {
1017
+ query.workspace = this.workspace;
1018
+ }
1019
+ const result = await this.queryRally(query);
1020
+ if (!result.Results || result.Results.length === 0) {
1021
+ return [];
1022
+ }
1023
+ const artifact = result.Results[0];
1024
+ if (!artifact.Discussion) {
1025
+ return [];
1026
+ }
1027
+ const postsQuery = {
1028
+ type: "conversationpost",
1029
+ fetch: ["ObjectID", "Text", "User", "CreationDate", "PostNumber"],
1030
+ query: `(Discussion = "${artifact.Discussion._ref}")`,
1031
+ order: "PostNumber"
1032
+ };
1033
+ const postsResult = await this.queryRally(postsQuery);
1034
+ return (postsResult.Results || []).map((post) => ({
1035
+ id: post.ObjectID,
1036
+ issueId,
1037
+ body: post.Text || "",
1038
+ author: post.User?._refObjectName || "Unknown",
1039
+ createdAt: post.CreationDate,
1040
+ updatedAt: post.CreationDate
1041
+ // Rally doesn't track comment updates separately
1042
+ }));
1043
+ }
1044
+ async addComment(issueId, body) {
1045
+ const query = {
1046
+ type: "artifact",
1047
+ fetch: ["ObjectID", "_ref", "Discussion"],
1048
+ query: `(FormattedID = "${issueId}")`
1049
+ };
1050
+ if (this.workspace) {
1051
+ query.workspace = this.workspace;
1052
+ }
1053
+ const result = await this.queryRally(query);
1054
+ if (!result.Results || result.Results.length === 0) {
1055
+ throw new IssueNotFoundError(issueId, "rally");
1056
+ }
1057
+ const artifact = result.Results[0];
1058
+ let discussionRef = artifact.Discussion?._ref;
1059
+ if (!discussionRef) {
1060
+ const discussionResult = await this.createRally("conversationpost", {
1061
+ Artifact: artifact._ref,
1062
+ Text: body
1063
+ });
1064
+ return {
1065
+ id: discussionResult.Object.ObjectID,
1066
+ issueId,
1067
+ body,
1068
+ author: "Panopticon",
1069
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1070
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1071
+ };
1072
+ }
1073
+ const postResult = await this.createRally("conversationpost", {
1074
+ Artifact: artifact._ref,
1075
+ Text: body
1076
+ });
1077
+ return {
1078
+ id: postResult.Object.ObjectID,
1079
+ issueId,
1080
+ body,
1081
+ author: "Panopticon",
1082
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1083
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1084
+ };
1085
+ }
1086
+ async transitionIssue(id, state) {
1087
+ await this.updateIssue(id, { state });
1088
+ }
1089
+ async linkPR(issueId, prUrl) {
1090
+ await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);
1091
+ }
1092
+ // Private helper methods
1093
+ buildQueryString(filters) {
1094
+ const conditions = [];
1095
+ if (filters?.state && !filters.includeClosed) {
1096
+ const rallyState = this.reverseMapState(filters.state);
1097
+ conditions.push(`((ScheduleState = "${rallyState}") OR (State = "${rallyState}"))`);
1098
+ }
1099
+ if (!filters?.includeClosed) {
1100
+ conditions.push('((ScheduleState != "Completed") AND (ScheduleState != "Accepted") AND (State != "Closed"))');
1101
+ }
1102
+ if (filters?.assignee) {
1103
+ conditions.push(`(Owner.Name contains "${filters.assignee}")`);
1104
+ }
1105
+ if (filters?.labels && filters.labels.length > 0) {
1106
+ const labelConditions = filters.labels.map(
1107
+ (label) => `(Tags.Name contains "${label}")`
1108
+ );
1109
+ conditions.push(`(${labelConditions.join(" AND ")})`);
1110
+ }
1111
+ if (filters?.query) {
1112
+ conditions.push(`((Name contains "${filters.query}") OR (Description contains "${filters.query}"))`);
1113
+ }
1114
+ return conditions.length > 0 ? conditions.join(" AND ") : "";
1115
+ }
1116
+ normalizeIssue(rallyArtifact) {
1117
+ const stateValue = rallyArtifact.ScheduleState || rallyArtifact.State || "Defined";
1118
+ const state = this.mapState(stateValue);
1119
+ const labels = [];
1120
+ if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {
1121
+ labels.push(...rallyArtifact.Tags._tagsNameArray);
1122
+ }
1123
+ const priority = rallyArtifact.Priority ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2 : void 0;
1124
+ const baseUrl = this.restApi.server.replace("/slm/webservice/", "");
1125
+ const url = `${baseUrl}/#/detail/${rallyArtifact._type.toLowerCase()}/${rallyArtifact.ObjectID}`;
1126
+ return {
1127
+ id: rallyArtifact.ObjectID,
1128
+ ref: rallyArtifact.FormattedID,
1129
+ title: rallyArtifact.Name || "",
1130
+ description: rallyArtifact.Description || "",
1131
+ state,
1132
+ labels,
1133
+ assignee: rallyArtifact.Owner?._refObjectName,
1134
+ url,
1135
+ tracker: "rally",
1136
+ priority,
1137
+ dueDate: rallyArtifact.DueDate,
1138
+ createdAt: rallyArtifact.CreationDate,
1139
+ updatedAt: rallyArtifact.LastUpdateDate
1140
+ };
1141
+ }
1142
+ mapState(rallyState) {
1143
+ return STATE_MAP2[rallyState] ?? "open";
1144
+ }
1145
+ reverseMapState(state) {
1146
+ switch (state) {
1147
+ case "open":
1148
+ return "Defined";
1149
+ case "in_progress":
1150
+ return "In-Progress";
1151
+ case "closed":
1152
+ return "Completed";
1153
+ default:
1154
+ return "Defined";
1155
+ }
1156
+ }
1157
+ // Rally API wrapper methods
1158
+ queryRally(queryConfig) {
1159
+ return new Promise((resolve, reject) => {
1160
+ this.restApi.query(queryConfig, (error, result) => {
1161
+ if (error) {
1162
+ reject(new Error(error.message || "Rally API query failed"));
1163
+ } else {
1164
+ resolve(result);
1165
+ }
1166
+ });
1167
+ });
1168
+ }
1169
+ createRally(type, data) {
1170
+ return new Promise((resolve, reject) => {
1171
+ this.restApi.create({
1172
+ type,
1173
+ data,
1174
+ fetch: ["FormattedID", "ObjectID", "_ref"]
1175
+ }, (error, result) => {
1176
+ if (error) {
1177
+ reject(new Error(error.message || "Rally API create failed"));
1178
+ } else {
1179
+ resolve(result);
1180
+ }
1181
+ });
1182
+ });
1183
+ }
1184
+ updateRally(type, ref, data) {
1185
+ return new Promise((resolve, reject) => {
1186
+ this.restApi.update({
1187
+ type,
1188
+ ref,
1189
+ data,
1190
+ fetch: ["FormattedID", "ObjectID"]
1191
+ }, (error, result) => {
1192
+ if (error) {
1193
+ reject(new Error(error.message || "Rally API update failed"));
1194
+ } else {
1195
+ resolve(result);
1196
+ }
1197
+ });
1198
+ });
1199
+ }
1200
+ };
1201
+
788
1202
  // src/lib/tracker/factory.ts
789
1203
  function createTracker(config) {
790
1204
  switch (config.type) {
@@ -826,6 +1240,21 @@ function createTracker(config) {
826
1240
  }
827
1241
  return new GitLabTracker(token, config.projectId);
828
1242
  }
1243
+ case "rally": {
1244
+ const apiKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.RALLY_API_KEY;
1245
+ if (!apiKey) {
1246
+ throw new TrackerAuthError(
1247
+ "rally",
1248
+ `API key not found. Set ${config.apiKeyEnv ?? "RALLY_API_KEY"} environment variable.`
1249
+ );
1250
+ }
1251
+ return new RallyTracker({
1252
+ apiKey,
1253
+ server: config.server,
1254
+ workspace: config.workspace,
1255
+ project: config.project
1256
+ });
1257
+ }
829
1258
  default:
830
1259
  throw new Error(`Unknown tracker type: ${config.type}`);
831
1260
  }
@@ -858,9 +1287,10 @@ function getAllTrackers(trackersConfig) {
858
1287
  }
859
1288
 
860
1289
  // src/lib/tracker/linking.ts
1290
+ init_esm_shims();
861
1291
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
862
- import { join as join5 } from "path";
863
- import { homedir as homedir3 } from "os";
1292
+ import { join as join4 } from "path";
1293
+ import { homedir as homedir2 } from "os";
864
1294
  function parseIssueRef(ref) {
865
1295
  if (ref.startsWith("github#")) {
866
1296
  return { tracker: "github", ref: `#${ref.slice(7)}` };
@@ -892,7 +1322,7 @@ var LinkManager = class {
892
1322
  storePath;
893
1323
  store;
894
1324
  constructor(storePath) {
895
- this.storePath = storePath ?? join5(homedir3(), ".panopticon", "links.json");
1325
+ this.storePath = storePath ?? join4(homedir2(), ".panopticon", "links.json");
896
1326
  this.store = this.load();
897
1327
  }
898
1328
  load() {
@@ -908,7 +1338,7 @@ var LinkManager = class {
908
1338
  return { version: 1, links: [] };
909
1339
  }
910
1340
  save() {
911
- const dir = join5(this.storePath, "..");
1341
+ const dir = join4(this.storePath, "..");
912
1342
  if (!existsSync5(dir)) {
913
1343
  mkdirSync3(dir, { recursive: true });
914
1344
  }
@@ -998,31 +1428,10 @@ function getLinkManager() {
998
1428
  return _linkManager;
999
1429
  }
1000
1430
 
1431
+ // src/lib/tracker/index.ts
1432
+ init_esm_shims();
1433
+
1001
1434
  export {
1002
- __require,
1003
- __commonJS,
1004
- PANOPTICON_HOME,
1005
- CONFIG_DIR,
1006
- SKILLS_DIR,
1007
- COMMANDS_DIR,
1008
- AGENTS_DIR,
1009
- BACKUPS_DIR,
1010
- COSTS_DIR,
1011
- TRAEFIK_DIR,
1012
- TRAEFIK_DYNAMIC_DIR,
1013
- TRAEFIK_CERTS_DIR,
1014
- CERTS_DIR,
1015
- CONFIG_FILE,
1016
- CLAUDE_DIR,
1017
- CODEX_DIR,
1018
- CURSOR_DIR,
1019
- GEMINI_DIR,
1020
- SYNC_TARGETS,
1021
- TEMPLATES_DIR,
1022
- CLAUDE_MD_TEMPLATES,
1023
- SOURCE_TEMPLATES_DIR,
1024
- SOURCE_TRAEFIK_TEMPLATES,
1025
- INIT_DIRS,
1026
1435
  loadConfig,
1027
1436
  saveConfig,
1028
1437
  getDefaultConfig,
@@ -1039,6 +1448,8 @@ export {
1039
1448
  isPanopticonSymlink,
1040
1449
  planSync,
1041
1450
  executeSync,
1451
+ planHooksSync,
1452
+ syncHooks,
1042
1453
  NotImplementedError,
1043
1454
  IssueNotFoundError,
1044
1455
  TrackerAuthError,
@@ -1055,4 +1466,4 @@ export {
1055
1466
  LinkManager,
1056
1467
  getLinkManager
1057
1468
  };
1058
- //# sourceMappingURL=chunk-J7JUNJGH.js.map
1469
+ //# sourceMappingURL=chunk-B2JBBOJN.js.map