@takagaki/cortex-decisions-viewer 0.4.45 → 0.4.48

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.
package/dist/build.js CHANGED
@@ -182,7 +182,10 @@ async function readFleetSources(repoRoot) {
182
182
  tools = undefined;
183
183
  }
184
184
  const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
185
- return { externalSources, internalSources, pipelines, tools, fleetGeneratedAt };
185
+ const secretSources = obj.secretSources && typeof obj.secretSources === "object"
186
+ ? obj.secretSources
187
+ : undefined;
188
+ return { externalSources, internalSources, pipelines, tools, fleetGeneratedAt, secretSources };
186
189
  }
187
190
  /** パスがディレクトリとして存在するか */
188
191
  async function isDirectory(dirAbs) {
@@ -975,15 +978,20 @@ async function build(opts) {
975
978
  graph,
976
979
  categories,
977
980
  months,
978
- intake: opts.intakeUrl && opts.projectKey
981
+ intake: opts.intakeUrl && opts.projectKey && opts.intakeToken
979
982
  ? {
980
983
  url: opts.intakeUrl,
981
984
  token: opts.intakeToken ?? "",
982
985
  projectKey: opts.projectKey,
983
986
  }
984
987
  : undefined,
988
+ secretKinds: (opts.secretKinds ?? "")
989
+ .split(",")
990
+ .map((s) => s.trim())
991
+ .filter(Boolean),
985
992
  externalSources: fleet.externalSources,
986
993
  fleetGeneratedAt: fleet.fleetGeneratedAt,
994
+ secretSources: fleet.secretSources,
987
995
  internalSources: fleet.internalSources,
988
996
  pipelines: fleet.pipelines,
989
997
  tools: fleet.tools,
package/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ program
19
19
  .option("--intake-url <url>", "投入フォームのLambda URL(環境変数 INTAKE_URL でも可)")
20
20
  .option("--intake-token <token>", "投入フォームの共有秘密トークン(環境変数 INTAKE_TOKEN でも可)")
21
21
  .option("--project-key <key>", "案件キー(投入フォームのルーティングキー)")
22
+ .option("--secret-kinds <kinds>", "鍵の入力欄を出す連携(カンマ区切り。例: figma,external-sources,backlog)")
22
23
  .action(async (dir, opts) => {
23
24
  try {
24
25
  const result = await (0, build_1.build)({
@@ -32,6 +33,7 @@ program
32
33
  intakeUrl: opts.intakeUrl ?? process.env.INTAKE_URL,
33
34
  intakeToken: opts.intakeToken ?? process.env.INTAKE_TOKEN,
34
35
  projectKey: opts.projectKey,
36
+ secretKinds: opts.secretKinds,
35
37
  });
36
38
  console.log(`✓ ${result.count} 件のレコードを ${result.outDir} にビルドしました`);
37
39
  }
package/dist/render.js CHANGED
@@ -743,6 +743,13 @@ a.ds-name:hover { text-decoration: underline; }
743
743
  .badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
744
744
  .badge.ds-excluded { background: #eceef1; color: #6b7280; }
745
745
  .badge.ds-undeclared { background: #fff4e5; color: #9a6700; }
746
+ .ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
747
+ .ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
748
+ .ds-toggle-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
749
+ .ds-toggle-btn:disabled { opacity: .6; cursor: default; }
750
+ .ds-toggle-msg { font-size: 12px; color: var(--muted); }
751
+ .ds-toggle-msg.is-ok { color: #1a7f37; }
752
+ .ds-toggle-msg.is-error { color: #b42318; }
746
753
  .badge.ds-notify { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
747
754
  .ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
748
755
  .ds-gate::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; }
@@ -760,6 +767,12 @@ a.ds-name:hover { text-decoration: underline; }
760
767
  /* ---- 操作タブ ---- */
761
768
  .ops-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 4px 0 8px; }
762
769
  .ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
770
+ .sec-state { font-size: 12.5px; font-weight: 600; margin: 0 0 8px; color: var(--muted); }
771
+ .sec-state.is-managed { color: #1a7f37; }
772
+ .sec-state.is-none { color: var(--muted); font-weight: 400; }
773
+ .sec-how { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 0 0 10px; }
774
+ .sec-input { width: 100%; box-sizing: border-box; font-size: 13px; padding: 7px 10px; margin-bottom: 8px; border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); }
775
+ .sec-input:focus { outline: none; border-color: var(--accent); }
763
776
  .ops-card {
764
777
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
765
778
  padding: 18px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 10px;
@@ -1525,6 +1538,93 @@ const CLIENT_JS = `
1525
1538
  ]);
1526
1539
  }
1527
1540
  // 外部ソース1件のカード(グループ化表示・展開表示の両方から使う)
1541
+ /**
1542
+ * Gold昇格の切り替えボタン。押すと設定ファイル(channels.json)が書き換わる。
1543
+ *
1544
+ * **広げる方向(OFF→ON)には確認を出す。** そのチャンネルの内容をAIが読んで、顧客も見る
1545
+ * Decision・用語集に蒸留するようになる。社内限定のチャンネルをONにすると、顧客が見られない
1546
+ * 会話が顧客可視の層に出てくる(実際にその事故が起きている)。狭める方向は安全なので確認しない。
1547
+ *
1548
+ * 保存しても画面はすぐには変わらない(接続状況は夜間バッチのスナップショット)。
1549
+ * それを伝えないと「保存できていない」と受け取られるので、応答のメッセージをそのまま出す。
1550
+ */
1551
+ function goldToggle(s) {
1552
+ var wrap = el("div", { class: "ds-toggle" });
1553
+ var on = s.gold === true;
1554
+ var btn = el("button", {
1555
+ class: "ds-toggle-btn" + (on ? " is-on" : ""),
1556
+ type: "button",
1557
+ text: on ? "Gold昇格を止める" : "Gold昇格を始める",
1558
+ });
1559
+ var msg = el("div", { class: "ds-toggle-msg" });
1560
+ wrap.appendChild(btn);
1561
+ wrap.appendChild(msg);
1562
+
1563
+ btn.addEventListener("click", async function () {
1564
+ var next = !on;
1565
+ if (next) {
1566
+ var ok = window.confirm(
1567
+ "「" + (s.name || s.ref) + "」の内容を、顧客も見る Decision・用語集に蒸留するようになります。" +
1568
+ String.fromCharCode(10, 10) +
1569
+ "社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。" +
1570
+ String.fromCharCode(10) +
1571
+ "顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ有効にしてください。" +
1572
+ String.fromCharCode(10, 10) +
1573
+ "※ 今夜の蒸留(22時ごろ)までに止めれば取り込まれません。一度生成された記録は自動では消えません。" +
1574
+ String.fromCharCode(10, 10) +
1575
+ "有効にしますか?",
1576
+ );
1577
+ if (!ok) return;
1578
+ }
1579
+ var editor = window.prompt("変更者のお名前を入力してください(履歴に残ります)", "");
1580
+ if (editor === null) return;
1581
+ if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
1582
+
1583
+ btn.disabled = true;
1584
+ msg.className = "ds-toggle-msg";
1585
+ msg.textContent = "保存中…";
1586
+ var intake = SITE.intake;
1587
+ var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
1588
+ try {
1589
+ var res = await fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
1590
+ method: "POST",
1591
+ headers: { "content-type": "application/json" },
1592
+ body: JSON.stringify({
1593
+ file: "チャット/channels.json",
1594
+ op: "set-channel-gold",
1595
+ ref: s.ref,
1596
+ value: next,
1597
+ editor: editor,
1598
+ }),
1599
+ });
1600
+ var j = await res.json().catch(function () { return {}; });
1601
+ if (!res.ok) {
1602
+ msg.className = "ds-toggle-msg is-error";
1603
+ msg.textContent = "✗ " + (j.message || "保存できませんでした。");
1604
+ btn.disabled = false;
1605
+ return;
1606
+ }
1607
+ msg.className = "ds-toggle-msg is-ok";
1608
+ msg.textContent = "✓ " + (j.message || "保存しました。");
1609
+ // **押した内容をボタンに反映し、逆向きの操作を必ず残す。**
1610
+ // バッジ(Gold昇格の読み取り対象/除外)は夜間バッチのスナップショット由来なので変えない。
1611
+ // 変えると「もう効いている」と誤解させるため。変えるのはボタンだけ。
1612
+ // ここを disabled のままにすると、誤って有効にした直後に**同じ画面から止められなくなる**。
1613
+ // 表示が直るのは翌朝で、その間に22時のGold昇格が走り、生成されたレコードは自動削除されない
1614
+ // (Gold層は追記専用)。危険な方向は押せるのに戻す方向が塞がる、という状態は許容できない。
1615
+ on = next;
1616
+ btn.textContent = on ? "Gold昇格を止める" : "Gold昇格を始める";
1617
+ btn.className = "ds-toggle-btn" + (on ? " is-on" : "");
1618
+ btn.disabled = false;
1619
+ } catch (e) {
1620
+ msg.className = "ds-toggle-msg is-error";
1621
+ msg.textContent = "✗ 通信に失敗しました。";
1622
+ btn.disabled = false;
1623
+ }
1624
+ });
1625
+ return wrap;
1626
+ }
1627
+
1528
1628
  function extSourceCard(s, meta) {
1529
1629
  var badges = el("div", { class: "ds-badges" });
1530
1630
  // 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
@@ -1534,7 +1634,7 @@ const CLIENT_JS = `
1534
1634
  else if (s.gold === false) badges.appendChild(el("span", { class: "badge ds-excluded", text: "除外" }));
1535
1635
  if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
1536
1636
  var gate = DS_GATE[s.gate] || DS_GATE["unknown"];
1537
- return el("div", { class: "ds-card" }, [
1637
+ var card = el("div", { class: "ds-card" }, [
1538
1638
  el("div", { class: "ds-card-main" }, [
1539
1639
  el("span", { class: "ds-icon", text: meta.icon }),
1540
1640
  el("span", { class: "ds-type", text: meta.label }),
@@ -1543,6 +1643,12 @@ const CLIENT_JS = `
1543
1643
  badges,
1544
1644
  el("span", { class: "ds-gate " + gate.cls, text: gate.text }),
1545
1645
  ]);
1646
+ // Gold昇格の切り替え。書き込み経路(intake Lambda)がある案件でだけ出す。
1647
+ // Slack以外は読み手が導出しないので出さない(押せても効かないボタンは出さない)。
1648
+ if (SITE.intake && s.type === "slack" && s.ref) {
1649
+ card.appendChild(goldToggle(s));
1650
+ }
1651
+ return card;
1546
1652
  }
1547
1653
 
1548
1654
  // ---------- 操作タブ(接続マップ・データソースと自動化・取り込み。今後この受け皿に機能を足す) ----------
@@ -1556,9 +1662,116 @@ const CLIENT_JS = `
1556
1662
  grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
1557
1663
  grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
1558
1664
  app.appendChild(grid);
1665
+ renderSecrets();
1559
1666
  }
1560
1667
  }
1561
1668
 
1669
+ /** 鍵の入力欄に出す説明。kind は Lambda 側(resolveSecretTarget)と綴りを合わせること */
1670
+ var SECRET_FORMS = {
1671
+ "figma": {
1672
+ icon: "🎨",
1673
+ title: "Figma のトークン",
1674
+ desc: "デザインの画面インベントリを毎晩同期するために使います。",
1675
+ how: "Figma → Settings → Security → Personal access tokens で発行(file_content:read があれば足ります)",
1676
+ },
1677
+ "backlog": {
1678
+ icon: "📋",
1679
+ title: "Backlog のAPIキー",
1680
+ desc: "課題・Wiki・ドキュメントを毎晩同期するために使います。",
1681
+ how: "Backlog → 個人設定 → API で発行",
1682
+ },
1683
+ "external-sources": {
1684
+ icon: "🐙",
1685
+ title: "GitHub の読み取りトークン",
1686
+ desc: "開発リポジトリの Issues / Discussions を読み、Gold の原料にするために使います。",
1687
+ how: "GitHub → Settings → Developer settings → Personal access tokens で、対象リポジトリに Contents / Issues の Read-only を付けて発行",
1688
+ },
1689
+ };
1690
+
1691
+ /**
1692
+ * 連携の鍵(トークン)の投入。
1693
+ *
1694
+ * **入れた値は読み出せない。** 表示・確認の経路は作っていないので、間違えたら入れ直す。
1695
+ * 保存先は案件ごとに分かれており、他の案件には影響しない。
1696
+ * 反映は次回のワークフロー実行から(接続状況の表示が変わるのは翌朝の自己チェック後)。
1697
+ */
1698
+ function renderSecrets() {
1699
+ var kinds = (SITE.secretKinds || []).filter(function (k) { return SECRET_FORMS[k]; });
1700
+ if (!kinds.length) return;
1701
+
1702
+ app.appendChild(el("div", { class: "section-label", text: "連携の鍵" }));
1703
+ app.appendChild(el("p", { class: "ops-intro", text: "各ツールを読むためのトークンです。入れた値はここには表示されません(保存だけできます)。保存先は案件ごとに分かれているので、他の案件には影響しません。" }));
1704
+ var grid = el("div", { class: "ops-grid" });
1705
+ kinds.forEach(function (k) { grid.appendChild(secretCard(k, SECRET_FORMS[k])); });
1706
+ app.appendChild(grid);
1707
+ }
1708
+
1709
+ function secretCard(kind, meta) {
1710
+ // **いま何で動いているか**を先に出す。入れ直したのに反映されていない状態が
1711
+ // 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
1712
+ // 保存の直後は前日の値のまま。時刻は上の節に出している)。
1713
+ var src = (SITE.secretSources || {})[kind] || "";
1714
+ var state = src
1715
+ ? el("p", { class: "sec-state" + (src === "Secrets Manager" ? " is-managed" : ""),
1716
+ text: "現在: " + src + " から取得中" })
1717
+ : el("p", { class: "sec-state is-none", text: "現在: 未設定" });
1718
+
1719
+ var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
1720
+ var who = el("input", { class: "sec-input", type: "text", placeholder: "変更者のお名前(履歴に残ります)", autocomplete: "off" });
1721
+ var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
1722
+ var msg = el("div", { class: "ds-toggle-msg" });
1723
+
1724
+ btn.addEventListener("click", async function () {
1725
+ var value = input.value;
1726
+ var editor = who.value;
1727
+ if (!value.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ トークンを入力してください。"; return; }
1728
+ if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
1729
+
1730
+ btn.disabled = true;
1731
+ msg.className = "ds-toggle-msg";
1732
+ msg.textContent = "保存中…";
1733
+ var intake = SITE.intake;
1734
+ var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
1735
+ try {
1736
+ var res = await fetch(pfx + "op=secret&t=" + encodeURIComponent(intake.token), {
1737
+ method: "POST",
1738
+ headers: { "content-type": "application/json" },
1739
+ // **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
1740
+ body: JSON.stringify({ projectKey: intake.projectKey, kind: kind, value: value, editor: editor }),
1741
+ });
1742
+ var j = await res.json().catch(function () { return {}; });
1743
+ if (!res.ok) {
1744
+ msg.className = "ds-toggle-msg is-error";
1745
+ msg.textContent = "✗ " + (j.message || "保存できませんでした。");
1746
+ btn.disabled = false;
1747
+ return;
1748
+ }
1749
+ msg.className = "ds-toggle-msg is-ok";
1750
+ msg.textContent = "✓ " + (j.message || "保存しました。");
1751
+ // 入れた値は画面に残さない(肩越しに見えるのを防ぐ)。お名前は続けて直せるよう残す。
1752
+ input.value = "";
1753
+ btn.disabled = false;
1754
+ } catch (e) {
1755
+ msg.className = "ds-toggle-msg is-error";
1756
+ msg.textContent = "✗ 通信に失敗しました。";
1757
+ btn.disabled = false;
1758
+ }
1759
+ });
1760
+
1761
+ return el("div", { class: "ops-card" }, [
1762
+ el("div", { class: "ops-card-head" }, [
1763
+ el("span", { class: "ops-card-icon", text: meta.icon }),
1764
+ el("h3", { class: "ops-card-title", text: meta.title }),
1765
+ ]),
1766
+ el("p", { class: "ops-card-desc", text: meta.desc }),
1767
+ state,
1768
+ el("p", { class: "sec-how", text: meta.how }),
1769
+ input,
1770
+ who,
1771
+ el("div", { class: "ds-toggle" }, [btn, msg]),
1772
+ ]);
1773
+ }
1774
+
1562
1775
  // ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
1563
1776
  function renderConnMap() {
1564
1777
  var tpl = document.getElementById("conn-map");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.45",
3
+ "version": "0.4.48",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.0",
32
+ "linkedom": "^0.18.13",
32
33
  "typescript": "^5.7.0"
33
34
  }
34
35
  }