panopticon-cli 0.4.10 → 0.4.12

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.
@@ -17,8 +17,8 @@
17
17
  }
18
18
  })();
19
19
  </script>
20
- <script type="module" crossorigin src="/assets/index-GwTde56u.js"></script>
21
- <link rel="stylesheet" crossorigin href="/assets/index-iqMjcW09.css">
20
+ <script type="module" crossorigin src="/assets/index-Bwu9d0w1.js"></script>
21
+ <link rel="stylesheet" crossorigin href="/assets/index-Bp7DyPLO.css">
22
22
  </head>
23
23
  <body class="bg-surface text-content transition-colors duration-150">
24
24
  <div id="root"></div>
@@ -51114,6 +51114,10 @@ var init_shadow_state = __esm({
51114
51114
  });
51115
51115
 
51116
51116
  // ../../lib/tracker/rally-api.ts
51117
+ var rally_api_exports = {};
51118
+ __export(rally_api_exports, {
51119
+ RallyRestApi: () => RallyRestApi
51120
+ });
51117
51121
  var RallyRestApi;
51118
51122
  var init_rally_api = __esm({
51119
51123
  "../../lib/tracker/rally-api.ts"() {
@@ -51170,7 +51174,12 @@ var init_rally_api = __esm({
51170
51174
  }
51171
51175
  const result = await response.json();
51172
51176
  if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {
51173
- throw new Error(`Rally API query failed: ${result.QueryResult.Errors.join(", ")}`);
51177
+ const errorDetail = result.QueryResult.Errors.join(", ");
51178
+ const queryDetail = config2.query ? ` (Query: ${config2.query})` : "";
51179
+ if (process.env.DEBUG?.includes("rally")) {
51180
+ console.error("[Rally WSAPI] Query failed:", { query: config2.query, errors: result.QueryResult.Errors });
51181
+ }
51182
+ throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);
51174
51183
  }
51175
51184
  return result;
51176
51185
  }
@@ -51316,6 +51325,11 @@ var init_rally = __esm({
51316
51325
  this.project = config2.project;
51317
51326
  }
51318
51327
  async listIssues(filters) {
51328
+ const queryString = this.buildQueryString(filters);
51329
+ if (process.env.DEBUG?.includes("rally")) {
51330
+ console.debug("[Rally] Query filters:", JSON.stringify(filters));
51331
+ console.debug("[Rally] Generated query:", queryString);
51332
+ }
51319
51333
  const query = {
51320
51334
  type: "artifact",
51321
51335
  // Query all artifact types
@@ -51336,7 +51350,7 @@ var init_rally = __esm({
51336
51350
  "_type"
51337
51351
  ],
51338
51352
  limit: filters?.limit ?? 50,
51339
- query: this.buildQueryString(filters)
51353
+ query: queryString
51340
51354
  };
51341
51355
  if (this.workspace) {
51342
51356
  query.workspace = this.workspace;
@@ -51537,6 +51551,17 @@ var init_rally = __esm({
51537
51551
  await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);
51538
51552
  }
51539
51553
  // Private helper methods
51554
+ /**
51555
+ * Build a Rally WSAPI query string from issue filters.
51556
+ *
51557
+ * Rally WSAPI v2.0 requires the entire compound query expression to be wrapped
51558
+ * in outer parentheses when multiple conditions are joined with AND/OR.
51559
+ * Without the outer parens, the WSAPI parser fails with:
51560
+ * "Could not parse: Error parsing expression -- expected ")" but saw "AND" instead."
51561
+ *
51562
+ * Valid: (((ScheduleState != "Completed") AND (State != "Closed")) AND (Owner.Name contains "John"))
51563
+ * Invalid: ((ScheduleState != "Completed") AND (State != "Closed")) AND (Owner.Name contains "John")
51564
+ */
51540
51565
  buildQueryString(filters) {
51541
51566
  const conditions = [];
51542
51567
  if (filters?.state && !filters.includeClosed) {
@@ -51544,7 +51569,7 @@ var init_rally = __esm({
51544
51569
  conditions.push(`((ScheduleState = "${rallyState}") OR (State = "${rallyState}"))`);
51545
51570
  }
51546
51571
  if (!filters?.includeClosed) {
51547
- conditions.push('((ScheduleState != "Completed") AND (ScheduleState != "Accepted") AND (State != "Closed"))');
51572
+ conditions.push('(((ScheduleState != "Completed") AND (ScheduleState != "Accepted")) AND (State != "Closed"))');
51548
51573
  }
51549
51574
  if (filters?.assignee) {
51550
51575
  conditions.push(`(Owner.Name contains "${filters.assignee}")`);
@@ -51553,12 +51578,13 @@ var init_rally = __esm({
51553
51578
  const labelConditions = filters.labels.map(
51554
51579
  (label) => `(Tags.Name contains "${label}")`
51555
51580
  );
51556
- conditions.push(`(${labelConditions.join(" AND ")})`);
51581
+ const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
51582
+ conditions.push(labelExpr);
51557
51583
  }
51558
51584
  if (filters?.query) {
51559
51585
  conditions.push(`((Name contains "${filters.query}") OR (Description contains "${filters.query}"))`);
51560
51586
  }
51561
- return conditions.length > 0 ? conditions.join(" AND ") : "";
51587
+ return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
51562
51588
  }
51563
51589
  normalizeIssue(rallyArtifact) {
51564
51590
  const stateValue = rallyArtifact.ScheduleState || rallyArtifact.State || "Defined";
@@ -84863,6 +84889,12 @@ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
84863
84889
  import { join as join3 } from "path";
84864
84890
  import { homedir as homedir3 } from "os";
84865
84891
  function getLinearApiKey() {
84892
+ try {
84893
+ const yamlConfig = loadConfig();
84894
+ if (yamlConfig.trackerKeys.linear)
84895
+ return yamlConfig.trackerKeys.linear;
84896
+ } catch {
84897
+ }
84866
84898
  const envFile = join3(homedir3(), ".panopticon.env");
84867
84899
  if (existsSync3(envFile)) {
84868
84900
  const content = readFileSync2(envFile, "utf-8");
@@ -84870,27 +84902,27 @@ function getLinearApiKey() {
84870
84902
  if (match)
84871
84903
  return match[1].trim();
84872
84904
  }
84873
- if (process.env.LINEAR_API_KEY)
84874
- return process.env.LINEAR_API_KEY;
84875
- try {
84876
- const yamlConfig = loadConfig();
84877
- if (yamlConfig.trackerKeys.linear)
84878
- return yamlConfig.trackerKeys.linear;
84879
- } catch {
84880
- }
84881
- return null;
84905
+ return process.env.LINEAR_API_KEY || null;
84882
84906
  }
84883
84907
  function getRallyConfig() {
84884
- const envFile = join3(homedir3(), ".panopticon.env");
84885
84908
  let apiKey;
84886
84909
  let server2;
84887
84910
  let workspace;
84888
84911
  let project;
84912
+ try {
84913
+ const yamlConfig = loadConfig();
84914
+ if (yamlConfig.trackerKeys.rally)
84915
+ apiKey = yamlConfig.trackerKeys.rally;
84916
+ } catch {
84917
+ }
84918
+ const envFile = join3(homedir3(), ".panopticon.env");
84889
84919
  if (existsSync3(envFile)) {
84890
84920
  const content = readFileSync2(envFile, "utf-8");
84891
- const apiKeyMatch = content.match(/RALLY_API_KEY=(.+)/);
84892
- if (apiKeyMatch)
84893
- apiKey = apiKeyMatch[1].trim();
84921
+ if (!apiKey) {
84922
+ const apiKeyMatch = content.match(/RALLY_API_KEY=(.+)/);
84923
+ if (apiKeyMatch)
84924
+ apiKey = apiKeyMatch[1].trim();
84925
+ }
84894
84926
  const serverMatch = content.match(/RALLY_SERVER=(.+)/);
84895
84927
  server2 = serverMatch?.[1].trim();
84896
84928
  const workspaceMatch = content.match(/RALLY_WORKSPACE=(.+)/);
@@ -84900,27 +84932,41 @@ function getRallyConfig() {
84900
84932
  }
84901
84933
  if (!apiKey)
84902
84934
  apiKey = process.env.RALLY_API_KEY;
84903
- if (!apiKey) {
84904
- try {
84905
- const yamlConfig = loadConfig();
84906
- if (yamlConfig.trackerKeys.rally)
84907
- apiKey = yamlConfig.trackerKeys.rally;
84908
- } catch {
84909
- }
84910
- }
84911
84935
  if (!apiKey)
84912
84936
  return null;
84913
84937
  return { apiKey, server: server2, workspace, project };
84914
84938
  }
84939
+ function validateRallyConfig(config2) {
84940
+ const warnings = [];
84941
+ const errors = [];
84942
+ if (!config2.apiKey) {
84943
+ errors.push("RALLY_API_KEY is required");
84944
+ }
84945
+ if (!config2.workspace) {
84946
+ warnings.push("RALLY_WORKSPACE not configured - queries may return unexpected results");
84947
+ }
84948
+ if (!config2.project) {
84949
+ warnings.push("RALLY_PROJECT not configured - queries will search all projects");
84950
+ }
84951
+ return { valid: errors.length === 0, warnings, errors };
84952
+ }
84915
84953
  function getGitHubConfig() {
84916
- const envFile = join3(homedir3(), ".panopticon.env");
84917
84954
  let token;
84918
84955
  let repos = [];
84956
+ try {
84957
+ const yamlConfig = loadConfig();
84958
+ if (yamlConfig.trackerKeys.github)
84959
+ token = yamlConfig.trackerKeys.github;
84960
+ } catch {
84961
+ }
84962
+ const envFile = join3(homedir3(), ".panopticon.env");
84919
84963
  if (existsSync3(envFile)) {
84920
84964
  const content = readFileSync2(envFile, "utf-8");
84921
- const tokenMatch = content.match(/GITHUB_TOKEN=(.+)/);
84922
- if (tokenMatch)
84923
- token = tokenMatch[1].trim();
84965
+ if (!token) {
84966
+ const tokenMatch = content.match(/GITHUB_TOKEN=(.+)/);
84967
+ if (tokenMatch)
84968
+ token = tokenMatch[1].trim();
84969
+ }
84924
84970
  const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
84925
84971
  if (reposMatch) {
84926
84972
  repos = reposMatch[1].trim().split(",").map((r) => {
@@ -84932,14 +84978,6 @@ function getGitHubConfig() {
84932
84978
  }
84933
84979
  if (!token)
84934
84980
  token = process.env.GITHUB_TOKEN;
84935
- if (!token) {
84936
- try {
84937
- const yamlConfig = loadConfig();
84938
- if (yamlConfig.trackerKeys.github)
84939
- token = yamlConfig.trackerKeys.github;
84940
- } catch {
84941
- }
84942
- }
84943
84981
  if (!token || repos.length === 0)
84944
84982
  return null;
84945
84983
  return { token, repos };
@@ -85527,6 +85565,12 @@ var IssueDataService = class {
85527
85565
  this.trackers.rally.lastFetchedIssues = [];
85528
85566
  return;
85529
85567
  }
85568
+ if (!this.trackers.rally.lastFetchedAt) {
85569
+ const validation = validateRallyConfig(config2);
85570
+ if (validation.warnings.length > 0) {
85571
+ console.warn("[Rally] Configuration warnings:", validation.warnings.join("; "));
85572
+ }
85573
+ }
85530
85574
  if (!this.cache.isStale("rally", "issues") && this.trackers.rally.lastFetchedIssues.length > 0) {
85531
85575
  return;
85532
85576
  }
@@ -85580,8 +85624,9 @@ var IssueDataService = class {
85580
85624
  this.pushMeta();
85581
85625
  }
85582
85626
  } catch (err) {
85583
- console.error("[IssueDataService] Rally poll error:", err.message);
85584
- this.trackers.rally.lastError = err.message;
85627
+ const errorMsg = err.message?.includes("Could not parse") ? `${err.message} - Check Rally workspace/project configuration. Enable DEBUG=rally for query details.` : err.message;
85628
+ console.error("[IssueDataService] Rally poll error:", errorMsg);
85629
+ this.trackers.rally.lastError = errorMsg;
85585
85630
  }
85586
85631
  }
85587
85632
  };
@@ -90078,11 +90123,6 @@ function getReviewStatus(issueId, filePath = DEFAULT_STATUS_FILE) {
90078
90123
  const statuses = loadReviewStatuses(filePath);
90079
90124
  return statuses[issueId] || null;
90080
90125
  }
90081
- function clearReviewStatus(issueId, filePath = DEFAULT_STATUS_FILE) {
90082
- const statuses = loadReviewStatuses(filePath);
90083
- delete statuses[issueId];
90084
- saveReviewStatuses(statuses, filePath);
90085
- }
90086
90126
 
90087
90127
  // ../../lib/remote/index.ts
90088
90128
  init_exe_provider();
@@ -92100,6 +92140,41 @@ app.get("/api/tracker-status", (_req, res) => {
92100
92140
  res.status(500).json({ error: "Failed to check tracker status: " + error.message });
92101
92141
  }
92102
92142
  });
92143
+ app.post("/api/rally/validate", async (req, res) => {
92144
+ try {
92145
+ const { apiKey, server: server2, workspace, project } = req.body;
92146
+ if (!apiKey) {
92147
+ res.status(400).json({ valid: false, error: "API key is required" });
92148
+ return;
92149
+ }
92150
+ const { RallyRestApi: RallyRestApi2 } = await Promise.resolve().then(() => (init_rally_api(), rally_api_exports));
92151
+ const api = new RallyRestApi2({
92152
+ apiKey,
92153
+ server: server2 || "https://rally1.rallydev.com"
92154
+ });
92155
+ const result = await api.query({
92156
+ type: "artifact",
92157
+ fetch: ["FormattedID"],
92158
+ query: '((State = "Open"))',
92159
+ limit: 1,
92160
+ workspace,
92161
+ project
92162
+ });
92163
+ res.json({
92164
+ valid: true,
92165
+ message: "Rally connection successful",
92166
+ testQueryResult: `Found ${result.QueryResult.TotalResultCount} artifacts`
92167
+ });
92168
+ } catch (err) {
92169
+ const isAuthError = err.message?.includes("Unauthorized") || err.message?.includes("401");
92170
+ const isParseError = err.message?.includes("Could not parse");
92171
+ res.status(400).json({
92172
+ valid: false,
92173
+ error: err.message,
92174
+ errorType: isAuthError ? "auth" : isParseError ? "query" : "network"
92175
+ });
92176
+ }
92177
+ });
92103
92178
  app.get("/api/cloister/config", (_req, res) => {
92104
92179
  try {
92105
92180
  const config2 = loadCloisterConfig();
@@ -94731,7 +94806,6 @@ app.post("/api/workspaces/:issueId/merge", async (req, res) => {
94731
94806
  );
94732
94807
  console.log(`[merge] PR merge output: ${mergeOutput}`);
94733
94808
  setReviewStatus2(issueId, { mergeStatus: "merged", readyForMerge: false });
94734
- clearReviewStatus(issueId);
94735
94809
  completePendingOperation(issueId, null);
94736
94810
  await closeIssueAfterMerge(issueId);
94737
94811
  return res.json({
@@ -94770,7 +94844,7 @@ app.post("/api/workspaces/:issueId/merge", async (req, res) => {
94770
94844
  );
94771
94845
  if (mergeResult.success && mergeResult.testsStatus === "PASS") {
94772
94846
  console.log(`[merge] Successfully merged ${issueId}`);
94773
- clearReviewStatus(issueId);
94847
+ setReviewStatus2(issueId, { mergeStatus: "merged", readyForMerge: false });
94774
94848
  completePendingOperation(issueId, null);
94775
94849
  await closeIssueAfterMerge(issueId);
94776
94850
  return res.json({
@@ -94780,7 +94854,7 @@ app.post("/api/workspaces/:issueId/merge", async (req, res) => {
94780
94854
  });
94781
94855
  } else if (mergeResult.success) {
94782
94856
  console.log(`[merge] Merged ${issueId} (tests: ${mergeResult.testsStatus})`);
94783
- clearReviewStatus(issueId);
94857
+ setReviewStatus2(issueId, { mergeStatus: "merged", readyForMerge: false });
94784
94858
  completePendingOperation(issueId, null);
94785
94859
  await closeIssueAfterMerge(issueId);
94786
94860
  return res.json({
@@ -98791,6 +98865,28 @@ ${stateMd}`
98791
98865
  }
98792
98866
  const centralStatus = getReviewStatus(issueId.toUpperCase());
98793
98867
  if (centralStatus?.history && centralStatus.history.length > 0) {
98868
+ const tasksDir = join43(homedir20(), ".panopticon", "specialists", "tasks");
98869
+ const taskFilesByType = { review: [], test: [], merge: [] };
98870
+ try {
98871
+ if (existsSync44(tasksDir)) {
98872
+ const taskFiles = readdirSync17(tasksDir).filter((f) => f.endsWith(".md"));
98873
+ for (const f of taskFiles) {
98874
+ const content = readFileSync37(join43(tasksDir, f), "utf-8");
98875
+ if (content.includes(issueId.toUpperCase()) || content.includes(issueId)) {
98876
+ if (f.startsWith("review-agent"))
98877
+ taskFilesByType.review.push(f);
98878
+ else if (f.startsWith("test-agent"))
98879
+ taskFilesByType.test.push(f);
98880
+ else if (f.startsWith("merge-agent"))
98881
+ taskFilesByType.merge.push(f);
98882
+ }
98883
+ }
98884
+ for (const type2 of Object.keys(taskFilesByType)) {
98885
+ taskFilesByType[type2].sort();
98886
+ }
98887
+ }
98888
+ } catch {
98889
+ }
98794
98890
  const typeMap = { review: "review", test: "test", merge: "merge" };
98795
98891
  let currentSection = null;
98796
98892
  const specialistSections = [];
@@ -98815,11 +98911,48 @@ ${stateMd}`
98815
98911
  }
98816
98912
  if (currentSection)
98817
98913
  specialistSections.push(currentSection);
98914
+ const taskFileIndex = { review: 0, test: 0, merge: 0 };
98818
98915
  for (const ss of specialistSections) {
98819
98916
  const duration = ss.startedAt && ss.endedAt ? Math.floor((new Date(ss.endedAt).getTime() - new Date(ss.startedAt).getTime()) / 1e3) : null;
98820
- const transcript = ss.notes ? `${ss.type.toUpperCase()} ${ss.status === "completed" ? "PASSED" : ss.status.toUpperCase()}
98821
-
98822
- ${ss.notes}` : `${ss.type.toUpperCase()} ${ss.status === "completed" ? "PASSED" : ss.status === "running" ? "IN PROGRESS..." : ss.status.toUpperCase()}`;
98917
+ const transcriptParts = [];
98918
+ const statusLabel = ss.status === "completed" ? "PASSED" : ss.status === "running" ? "IN PROGRESS..." : ss.status.toUpperCase();
98919
+ transcriptParts.push(`${ss.type.toUpperCase()} ${statusLabel}`);
98920
+ const taskFiles = taskFilesByType[ss.type] || [];
98921
+ const taskIdx = taskFileIndex[ss.type] || 0;
98922
+ if (taskIdx < taskFiles.length) {
98923
+ try {
98924
+ const taskContent = readFileSync37(join43(tasksDir, taskFiles[taskIdx]), "utf-8");
98925
+ const taskLines = taskContent.split("\n");
98926
+ const meaningfulLines = taskLines.filter(
98927
+ (l) => !l.startsWith("```") && !l.startsWith("# EXECUTE") && !l.startsWith("\u26A0\uFE0F")
98928
+ );
98929
+ transcriptParts.push(`
98930
+ --- Task ---
98931
+ ${meaningfulLines.slice(0, 5).join("\n")}`);
98932
+ } catch {
98933
+ }
98934
+ taskFileIndex[ss.type] = taskIdx + 1;
98935
+ }
98936
+ if (ss.status === "running") {
98937
+ const tmuxName = `specialist-${ss.type === "review" ? "review-agent" : ss.type === "test" ? "test-agent" : "merge-agent"}`;
98938
+ try {
98939
+ const { stdout } = await execAsync15(
98940
+ `tmux capture-pane -t ${tmuxName} -p -S -100 2>/dev/null || echo ""`,
98941
+ { encoding: "utf-8", timeout: 5e3 }
98942
+ );
98943
+ if (stdout.trim()) {
98944
+ transcriptParts.push(`
98945
+ --- Live Output ---
98946
+ ${stdout.trim()}`);
98947
+ }
98948
+ } catch {
98949
+ }
98950
+ }
98951
+ if (ss.notes) {
98952
+ transcriptParts.push(`
98953
+ --- Results ---
98954
+ ${ss.notes}`);
98955
+ }
98823
98956
  sections.push({
98824
98957
  type: ss.type,
98825
98958
  sessionId: `specialist-${ss.type}-${ss.startedAt}`,
@@ -98827,7 +98960,7 @@ ${ss.notes}` : `${ss.type.toUpperCase()} ${ss.status === "completed" ? "PASSED"
98827
98960
  startedAt: ss.startedAt,
98828
98961
  duration,
98829
98962
  status: ss.status,
98830
- transcript
98963
+ transcript: transcriptParts.join("\n")
98831
98964
  });
98832
98965
  }
98833
98966
  }
@@ -98838,7 +98971,23 @@ ${ss.notes}` : `${ss.type.toUpperCase()} ${ss.status === "completed" ? "PASSED"
98838
98971
  return -1;
98839
98972
  return new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime();
98840
98973
  });
98841
- res.json({ issueId, sections });
98974
+ let costByStage = {};
98975
+ let totalCost = 0;
98976
+ try {
98977
+ syncCache();
98978
+ const issueData = getCostsForIssue(issueId.toUpperCase());
98979
+ if (issueData) {
98980
+ totalCost = issueData.totalCost;
98981
+ costByStage = Object.fromEntries(
98982
+ Object.entries(issueData.stages || {}).map(([stage, stats]) => [
98983
+ stage,
98984
+ { cost: stats.cost, tokens: stats.tokens }
98985
+ ])
98986
+ );
98987
+ }
98988
+ } catch {
98989
+ }
98990
+ res.json({ issueId, sections, costByStage, totalCost });
98842
98991
  } catch (error) {
98843
98992
  res.status(500).json({ error: "Failed to fetch activity: " + error.message });
98844
98993
  }
package/dist/index.d.ts CHANGED
@@ -466,7 +466,8 @@ interface TrackerConfig {
466
466
  project?: string;
467
467
  }
468
468
  /**
469
- * Create a tracker instance from configuration
469
+ * Create a tracker instance from configuration.
470
+ * Priority: config.yaml (Settings) > environment variable > custom env var name
470
471
  */
471
472
  declare function createTracker(config: TrackerConfig): IssueTracker;
472
473
  /**
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  planSync,
30
30
  restoreBackup,
31
31
  syncHooks
32
- } from "./chunk-NRPHRN6M.js";
32
+ } from "./chunk-WLL3UEYI.js";
33
33
  import {
34
34
  PROVIDERS,
35
35
  getAgentCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panopticon-cli",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
5
5
  "keywords": [
6
6
  "ai-agents",