@hienlh/ppm 0.13.75 → 0.13.76

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 (128) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/cli-reference.md +4 -4
  4. package/assets/skills/ppm/references/http-api.md +7 -1
  5. package/bun.lock +2142 -0
  6. package/bunfig.toml +2 -0
  7. package/dist/web/assets/ai-settings-section-D0VMZ4aE.js +1 -0
  8. package/dist/web/assets/{api-settings-BJTjIG4U.js → api-settings-Byph7lae.js} +1 -1
  9. package/dist/web/assets/architecture-PBZL5I3N-DyeqgxGw.js +1 -0
  10. package/dist/web/assets/{audio-preview-BQUzmaMW.js → audio-preview-DXCxll7f.js} +1 -1
  11. package/dist/web/assets/chat-tab-D5j5gP5j.js +16 -0
  12. package/dist/web/assets/code-editor-CL2gcvDj.js +10 -0
  13. package/dist/web/assets/{conflict-editor-CrpXDFE1.js → conflict-editor-RMsNwUKp.js} +1 -1
  14. package/dist/web/assets/{csv-preview-asMfgR0r.js → csv-preview-CwEbP_iZ.js} +1 -1
  15. package/dist/web/assets/{data-grid-overlay-editor-DGjqvYn6.js → data-grid-overlay-editor-C36FRqE8.js} +1 -1
  16. package/dist/web/assets/database-viewer-2TbU0m7s.js +1 -0
  17. package/dist/web/assets/{diff-viewer-CdZ15fOk.js → diff-viewer--SSMwMoS.js} +1 -1
  18. package/dist/web/assets/{docx-preview-AgoO5zwo.js → docx-preview-BL398ELS.js} +1 -1
  19. package/dist/web/assets/{esm-xVTUq__o.js → esm-DH3rpl0I.js} +1 -1
  20. package/dist/web/assets/{extension-webview-C4D4PnO3.js → extension-webview-DrnDwQpM.js} +2 -2
  21. package/dist/web/assets/git-log-panel-u6ehykcl.js +1 -0
  22. package/dist/web/assets/gitGraph-HDMCJU4V-CZAWeLUi.js +1 -0
  23. package/dist/web/assets/{glide-data-grid-BfXnM2CC.js → glide-data-grid-Benw7NI4.js} +6 -6
  24. package/dist/web/assets/globe-CQ8NAYvi.js +1 -0
  25. package/dist/web/assets/{image-preview-CyL9Lj_O.js → image-preview-DeNH71Ez.js} +1 -1
  26. package/dist/web/assets/index-BS3CQIT3.js +27 -0
  27. package/dist/web/assets/info-3K5VOQVL-BqVOLnRc.js +1 -0
  28. package/dist/web/assets/{input-CArJe9WS.js → input-DSELw5zU.js} +1 -1
  29. package/dist/web/assets/keybindings-store-gFa7vUoe.js +1 -0
  30. package/dist/web/assets/{markdown-renderer-Cat4uT8B.js → markdown-renderer-D5B629qw.js} +3 -3
  31. package/dist/web/assets/notification-store-qViiZZaY.js +1 -0
  32. package/dist/web/assets/{number-overlay-editor-DtUBprPW.js → number-overlay-editor-JsUdft7z.js} +1 -1
  33. package/dist/web/assets/packet-RMMSAZCW-BGMrAgbD.js +1 -0
  34. package/dist/web/assets/{panel-store-C9VAhbZz.js → panel-store-DlvwzOll.js} +1 -1
  35. package/dist/web/assets/{pdf-preview-BSrz_N0g.js → pdf-preview-yUMARG8r.js} +1 -1
  36. package/dist/web/assets/pie-UPGHQEXC-9IRPAyAe.js +1 -0
  37. package/dist/web/assets/port-forwarding-tab-D7rVcasa.js +1 -0
  38. package/dist/web/assets/{postgres-viewer-GpXLzmnT.js → postgres-viewer-C28tRuh5.js} +3 -3
  39. package/dist/web/assets/{project-store-DlbHpIq0.js → project-store-CpC02pIv.js} +1 -1
  40. package/dist/web/assets/radar-KQ55EAFF-CxjdSb6M.js +1 -0
  41. package/dist/web/assets/refresh-cw-CRD2qr4U.js +1 -0
  42. package/dist/web/assets/{settings-store-DQUFTPk2.js → settings-store-MXJgFUnl.js} +2 -2
  43. package/dist/web/assets/settings-tab-0kWZtlCi.js +1 -0
  44. package/dist/web/assets/{sql-query-editor-PFN7evxv.js → sql-query-editor-DCx7kPlY.js} +1 -1
  45. package/dist/web/assets/sqlite-viewer-W1RhaCr0.js +1 -0
  46. package/dist/web/assets/system-monitor-tab-CJojQd3x.js +1 -0
  47. package/dist/web/assets/{tab-store-CIcbSn0c.js → tab-store-Bdw8DIbZ.js} +1 -1
  48. package/dist/web/assets/{terminal-tab-D2OwQv1h.js → terminal-tab-B1_sAuIi.js} +2 -2
  49. package/dist/web/assets/treemap-KZPCXAKY-BYrmfSj5.js +1 -0
  50. package/dist/web/assets/{use-blob-url-DrPfBQBM.js → use-blob-url-CBi0HMq5.js} +1 -1
  51. package/dist/web/assets/{use-monaco-theme-BLIgarH5.js → use-monaco-theme-CbzQcrwD.js} +1 -1
  52. package/dist/web/assets/{vendor-mermaid-DkqjpqJK.js → vendor-mermaid-D2cOxeao.js} +3 -3
  53. package/dist/web/assets/{video-preview-49zZk7VQ.js → video-preview-D-nAiQsv.js} +1 -1
  54. package/dist/web/index.html +20 -17
  55. package/dist/web/sw.js +1 -1
  56. package/package.json +1 -1
  57. package/src/index.ts +0 -0
  58. package/src/providers/claude-agent-sdk.ts +6 -2
  59. package/src/server/index.ts +2 -0
  60. package/src/server/routes/chat.ts +7 -6
  61. package/src/server/routes/push.ts +54 -0
  62. package/src/services/cloud-ws.service.ts +1 -27
  63. package/src/services/config.service.ts +1 -1
  64. package/src/services/db.service.ts +24 -0
  65. package/src/services/notification.service.ts +5 -14
  66. package/src/services/push-notification.service.ts +104 -0
  67. package/src/types/config.ts +7 -0
  68. package/src/web/components/chat/chat-tab.tsx +10 -0
  69. package/src/web/components/settings/settings-tab.tsx +66 -12
  70. package/src/web/hooks/use-push-notification.ts +114 -0
  71. package/src/web/sw.ts +45 -0
  72. package/.opencode/.env.example +0 -98
  73. package/.opencode/skills/ads-management/scripts/.env.example +0 -13
  74. package/.opencode/skills/ai-multimodal/.env.example +0 -230
  75. package/.opencode/skills/cip-design/.env.example +0 -6
  76. package/.opencode/skills/devops/.env.example +0 -76
  77. package/.opencode/skills/docs-seeker/.env.example +0 -15
  78. package/.opencode/skills/elevenlabs/.env.example +0 -3
  79. package/.opencode/skills/marketing-dashboard/.env.example +0 -15
  80. package/.opencode/skills/marketing-dashboard/app/.env.example +0 -2
  81. package/.opencode/skills/marketing-dashboard/server/.env.example +0 -2
  82. package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +0 -70
  83. package/.opencode/skills/mcp-management/scripts/dist/cli.js +0 -160
  84. package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +0 -183
  85. package/.opencode/skills/payment-integration/scripts/.env.example +0 -20
  86. package/.opencode/skills/sequential-thinking/.env.example +0 -8
  87. package/dist/web/assets/architecture-PBZL5I3N-CkdUQjA_.js +0 -1
  88. package/dist/web/assets/chat-tab-B80a21gx.js +0 -16
  89. package/dist/web/assets/code-editor-hFO6vAKT.js +0 -10
  90. package/dist/web/assets/database-viewer-CAjJc7bL.js +0 -1
  91. package/dist/web/assets/git-log-panel-J0pVWvZl.js +0 -1
  92. package/dist/web/assets/gitGraph-HDMCJU4V-D3UR56AG.js +0 -1
  93. package/dist/web/assets/index-Dd6YEaE6.js +0 -27
  94. package/dist/web/assets/info-3K5VOQVL-DUhLSKI2.js +0 -1
  95. package/dist/web/assets/keybindings-store-C1yHwKEc.js +0 -1
  96. package/dist/web/assets/notification-store-CBTVrgEf.js +0 -1
  97. package/dist/web/assets/packet-RMMSAZCW-BIpeVUGW.js +0 -1
  98. package/dist/web/assets/pie-UPGHQEXC-CNoizzjb.js +0 -1
  99. package/dist/web/assets/port-forwarding-tab-BpeFzbAM.js +0 -1
  100. package/dist/web/assets/radar-KQ55EAFF-7dns-ho5.js +0 -1
  101. package/dist/web/assets/settings-tab-Cjiu2egJ.js +0 -1
  102. package/dist/web/assets/sqlite-viewer-ONh1tmxh.js +0 -1
  103. package/dist/web/assets/system-monitor-tab-BznIBj8p.js +0 -1
  104. package/dist/web/assets/treemap-KZPCXAKY-D3DZCLoE.js +0 -1
  105. /package/dist/web/assets/{api-client-BK4NPNoY.js → api-client-DG9qwosT.js} +0 -0
  106. /package/dist/web/assets/{chevron-down-CiFNPrfI.js → chevron-down-BMo4cBth.js} +0 -0
  107. /package/dist/web/assets/{chevron-right-BzAdxJRG.js → chevron-right-CD8e6Aj4.js} +0 -0
  108. /package/dist/web/assets/{code-CuravVys.js → code-DiNmA3eR.js} +0 -0
  109. /package/dist/web/assets/{csv-parser-D1b_lg2T.js → csv-parser-B_TuHmnd.js} +0 -0
  110. /package/dist/web/assets/{data-grid-types-DzL5W2em.js → data-grid-types-CO_3iSwd.js} +0 -0
  111. /package/dist/web/assets/{database-NmqHg29g.js → database-Dc8mr-dP.js} +0 -0
  112. /package/dist/web/assets/{dist-CohudVKa.js → dist-C1jciI67.js} +0 -0
  113. /package/dist/web/assets/{dist-BM2EHhLH.js → dist-D4dFaZkK.js} +0 -0
  114. /package/dist/web/assets/{file-exclamation-point-Baz81y5z.js → file-exclamation-point-B__2Hrd6.js} +0 -0
  115. /package/dist/web/assets/{katex-CHaeM9QC.js → katex-DveWxdWJ.js} +0 -0
  116. /package/dist/web/assets/{lib-LPmTkMu4.js → lib-D4YDpYv4.js} +0 -0
  117. /package/dist/web/assets/{react-DHBl6KRc.js → react-BXxixfbh.js} +0 -0
  118. /package/dist/web/assets/{search-BEy08Exr.js → search-D90WJ5fo.js} +0 -0
  119. /package/dist/web/assets/{shield-check-77W0OMbn.js → shield-check-DeIMQtEj.js} +0 -0
  120. /package/dist/web/assets/{shield-off-C_MK1u09.js → shield-off-D4jBmG5E.js} +0 -0
  121. /package/dist/web/assets/{sparkles-CulWHe4c.js → sparkles-DyeiGE7Q.js} +0 -0
  122. /package/dist/web/assets/{table-BzjWcs87.js → table-DCYlHUNQ.js} +0 -0
  123. /package/dist/web/assets/{text-wrap-DJz9Bgpa.js → text-wrap-B3mYv9Yo.js} +0 -0
  124. /package/dist/web/assets/{trash-2-D5P4y8p_.js → trash-2-DkIfBY8d.js} +0 -0
  125. /package/dist/web/assets/{utils-CSCvNZxE.js → utils-Bs_TFEQf.js} +0 -0
  126. /package/dist/web/assets/{vendor-xterm-t3d5xZdz.js → vendor-xterm-BHJtSw6L.js} +0 -0
  127. /package/dist/web/assets/{wifi-CgM9T6HR.js → wifi-DifNnmbA.js} +0 -0
  128. /package/dist/web/assets/{x-Dx3jsRgu.js → x-WwAMX3EB.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useRef } from "react";
2
2
  import {
3
- Moon, Sun, Monitor, Bell, Check, ChevronRight, ArrowLeft,
3
+ Moon, Sun, Monitor, Bell, BellOff, Check, ChevronRight, ArrowLeft,
4
4
  Bot, BellRing, Keyboard, Globe, Plug, Puzzle, Bug, FolderSearch,
5
5
  } from "lucide-react";
6
6
  import { Button } from "@/components/ui/button";
@@ -19,12 +19,18 @@ import { ExtensionManagerSection } from "./extension-manager-section";
19
19
  import { PPMBotSettingsSection } from "./ppmbot-settings-section";
20
20
  import { ChangePasswordSection } from "./change-password-section";
21
21
  import { FilesSettingsSection } from "./files-settings-section";
22
+ import { usePushNotification } from "@/hooks/use-push-notification";
23
+
22
24
  const THEME_OPTIONS: { value: Theme; label: string; icon: React.ElementType }[] = [
23
25
  { value: "light", label: "Light", icon: Sun },
24
26
  { value: "dark", label: "Dark", icon: Moon },
25
27
  { value: "system", label: "System", icon: Monitor },
26
28
  ];
27
29
 
30
+ const pushSupported = "PushManager" in window && "serviceWorker" in navigator;
31
+ const isIosNonPwa = /iPhone|iPad/.test(navigator.userAgent) &&
32
+ !window.matchMedia("(display-mode: standalone)").matches;
33
+
28
34
  type SettingsCategory = "ai" | "notifications" | "clawbot" | "jira" | "proxy" | "shortcuts" | "mcp" | "extensions" | "files";
29
35
 
30
36
  const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
@@ -41,6 +47,7 @@ const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; ic
41
47
 
42
48
  export function SettingsTab() {
43
49
  const { theme, setTheme, deviceName, setDeviceName, version, jiraEnabled, setJiraEnabled } = useSettingsStore(useShallow((s) => ({ theme: s.theme, setTheme: s.setTheme, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version, jiraEnabled: s.jiraEnabled, setJiraEnabled: s.setJiraEnabled })));
50
+ const { permission, isSubscribed, loading, error: pushError, subscribe, unsubscribe } = usePushNotification();
44
51
  const [activeCategory, setActiveCategory] = useState<SettingsCategory | null>(null);
45
52
  const [nameInput, setNameInput] = useState(deviceName ?? "");
46
53
  const [nameSaving, setNameSaving] = useState(false);
@@ -85,7 +92,7 @@ export function SettingsTab() {
85
92
  <ScrollArea className="flex-1 min-h-0">
86
93
  <div className="p-3">
87
94
  {activeCategory === "ai" && <AISettingsSection compact />}
88
- {activeCategory === "notifications" && <NotificationsContent />}
95
+ {activeCategory === "notifications" && <NotificationsContent isSubscribed={isSubscribed} loading={loading} permission={permission} pushError={pushError} subscribe={subscribe} unsubscribe={unsubscribe} />}
89
96
  {activeCategory === "clawbot" && <PPMBotSettingsSection />}
90
97
  {/* Jira is now a sidebar tab with a toggle below */}
91
98
  {activeCategory === "proxy" && <ProxySettingsSection />}
@@ -224,20 +231,67 @@ export function SettingsTab() {
224
231
  }
225
232
 
226
233
  /** Notifications detail content — extracted to keep SettingsTab clean */
227
- function NotificationsContent() {
234
+ function NotificationsContent({ isSubscribed, loading, permission, pushError, subscribe, unsubscribe }: {
235
+ isSubscribed: boolean;
236
+ loading: boolean;
237
+ permission: NotificationPermission;
238
+ pushError: string | null;
239
+ subscribe: () => void;
240
+ unsubscribe: () => void;
241
+ }) {
228
242
  return (
229
243
  <div className="space-y-4">
230
- {/* Cloud Push */}
244
+ {/* Push */}
231
245
  <section className="space-y-2">
232
246
  <h3 className="text-xs font-medium text-muted-foreground">Push Notifications</h3>
233
- <div className="flex items-center gap-1.5">
234
- <Bell className="size-3.5 text-muted-foreground" />
235
- <span className="text-xs">Managed via PPM Cloud</span>
236
- </div>
237
- <p className="text-[11px] text-muted-foreground">
238
- Push notifications are now dispatched through PPM Cloud.
239
- Visit your Cloud dashboard to manage subscriptions.
240
- </p>
247
+ {!pushSupported ? (
248
+ <p className="text-[11px] text-muted-foreground">
249
+ Push notifications not supported in this browser.
250
+ </p>
251
+ ) : (
252
+ <div className="space-y-2">
253
+ <div className="flex items-center justify-between">
254
+ <div className="flex items-center gap-1.5">
255
+ {isSubscribed ? <Bell className="size-3.5" /> : <BellOff className="size-3.5" />}
256
+ <span className="text-xs">Push notifications</span>
257
+ </div>
258
+ <Button
259
+ variant={isSubscribed ? "default" : "outline"}
260
+ size="sm"
261
+ className="h-7 text-xs cursor-pointer"
262
+ disabled={loading || permission === "denied"}
263
+ onClick={() => (isSubscribed ? unsubscribe() : subscribe())}
264
+ >
265
+ {loading ? "..." : isSubscribed ? "On" : "Off"}
266
+ </Button>
267
+ </div>
268
+ {isSubscribed && (
269
+ <Button
270
+ variant="outline"
271
+ size="sm"
272
+ className="h-7 text-xs w-full cursor-pointer"
273
+ onClick={() => {
274
+ new Notification("PPM Test", { body: "Push notifications are working!" });
275
+ }}
276
+ >
277
+ Test notification
278
+ </Button>
279
+ )}
280
+ {pushError && (
281
+ <p className="text-[11px] text-destructive">{pushError}</p>
282
+ )}
283
+ {permission === "denied" && (
284
+ <p className="text-[11px] text-destructive">
285
+ Notifications blocked. Enable in browser settings.
286
+ </p>
287
+ )}
288
+ {isIosNonPwa && (
289
+ <p className="text-[11px] text-muted-foreground">
290
+ On iOS, install PPM to Home Screen for push notifications.
291
+ </p>
292
+ )}
293
+ </div>
294
+ )}
241
295
  </section>
242
296
 
243
297
  <Separator />
@@ -0,0 +1,114 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { getAuthToken } from "@/lib/api-client";
3
+
4
+ /** Convert VAPID public key from base64url to Uint8Array for PushManager */
5
+ function urlBase64ToUint8Array(base64String: string): Uint8Array {
6
+ const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
7
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
8
+ const rawData = atob(base64);
9
+ return Uint8Array.from(rawData, (char) => char.charCodeAt(0));
10
+ }
11
+
12
+ export function usePushNotification() {
13
+ const [permission, setPermission] = useState<NotificationPermission>("default");
14
+ const [isSubscribed, setIsSubscribed] = useState(false);
15
+ const [loading, setLoading] = useState(false);
16
+ const [error, setError] = useState<string | null>(null);
17
+
18
+ // Check current permission and subscription state on mount
19
+ useEffect(() => {
20
+ if ("Notification" in window) {
21
+ setPermission(Notification.permission);
22
+ }
23
+ setIsSubscribed(localStorage.getItem("ppm-push-subscribed") === "true");
24
+ }, []);
25
+
26
+ const subscribe = useCallback(async () => {
27
+ setLoading(true);
28
+ setError(null);
29
+ try {
30
+ // 1. Request notification permission
31
+ const perm = await Notification.requestPermission();
32
+ setPermission(perm);
33
+ if (perm !== "granted") {
34
+ setError("Permission denied");
35
+ return;
36
+ }
37
+
38
+ // 2. Check service worker is available (with timeout)
39
+ if (!navigator.serviceWorker?.controller && !navigator.serviceWorker?.ready) {
40
+ setError("Service worker not available (dev mode?)");
41
+ return;
42
+ }
43
+
44
+ const swReady = await Promise.race([
45
+ navigator.serviceWorker.ready,
46
+ new Promise<null>((_, reject) => setTimeout(() => reject(new Error("Service worker timeout")), 5000)),
47
+ ]);
48
+ if (!swReady) throw new Error("Service worker not ready");
49
+
50
+ // 3. Get VAPID public key from server
51
+ const headers: Record<string, string> = {};
52
+ const token = getAuthToken();
53
+ if (token) headers.Authorization = `Bearer ${token}`;
54
+
55
+ const res = await fetch("/api/push/vapid-key", { headers });
56
+ const json = await res.json();
57
+ if (!json.ok) throw new Error(json.error || "Failed to get VAPID key");
58
+
59
+ // 4. Subscribe via PushManager
60
+ const sub = await swReady.pushManager.subscribe({
61
+ userVisibleOnly: true,
62
+ applicationServerKey: urlBase64ToUint8Array(json.data.publicKey).buffer as ArrayBuffer,
63
+ });
64
+
65
+ // 5. Send subscription to server
66
+ await fetch("/api/push/subscribe", {
67
+ method: "POST",
68
+ headers: { ...headers, "Content-Type": "application/json" },
69
+ body: JSON.stringify(sub.toJSON()),
70
+ });
71
+
72
+ setIsSubscribed(true);
73
+ localStorage.setItem("ppm-push-subscribed", "true");
74
+ } catch (err) {
75
+ const msg = err instanceof Error ? err.message : "Subscribe failed";
76
+ setError(msg);
77
+ console.error("[push] Subscribe failed:", err);
78
+ } finally {
79
+ setLoading(false);
80
+ }
81
+ }, []);
82
+
83
+ const unsubscribe = useCallback(async () => {
84
+ setLoading(true);
85
+ try {
86
+ const reg = await navigator.serviceWorker.ready;
87
+ const sub = await reg.pushManager.getSubscription();
88
+ if (sub) {
89
+ // Remove from server
90
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
91
+ const token = getAuthToken();
92
+ if (token) headers.Authorization = `Bearer ${token}`;
93
+
94
+ await fetch("/api/push/subscribe", {
95
+ method: "DELETE",
96
+ headers,
97
+ body: JSON.stringify({ endpoint: sub.endpoint }),
98
+ });
99
+
100
+ // Unsubscribe locally
101
+ await sub.unsubscribe();
102
+ }
103
+
104
+ setIsSubscribed(false);
105
+ localStorage.removeItem("ppm-push-subscribed");
106
+ } catch (err) {
107
+ console.error("[push] Unsubscribe failed:", err);
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ }, []);
112
+
113
+ return { permission, isSubscribed, loading, error, subscribe, unsubscribe };
114
+ }
package/src/web/sw.ts CHANGED
@@ -5,3 +5,48 @@ declare const self: ServiceWorkerGlobalScope;
5
5
 
6
6
  // Workbox injects precache manifest here
7
7
  precacheAndRoute(self.__WB_MANIFEST);
8
+
9
+ // Handle push notifications from server
10
+ self.addEventListener("push", (event) => {
11
+ event.waitUntil(
12
+ self.clients
13
+ .matchAll({ type: "window", includeUncontrolled: true })
14
+ .then((clients) => {
15
+ // Skip notification if any PPM tab is currently visible
16
+ const hasVisibleClient = clients.some(
17
+ (c) => c.visibilityState === "visible",
18
+ );
19
+ if (hasVisibleClient) return;
20
+
21
+ const data = event.data?.json() ?? {
22
+ title: "PPM",
23
+ body: "Chat completed",
24
+ };
25
+ return self.registration.showNotification(data.title, {
26
+ body: data.body,
27
+ icon: "/icon-192.svg",
28
+ badge: "/icon-192.svg",
29
+ tag: "ppm-chat-done",
30
+ silent: false,
31
+ data: { url: self.location.origin },
32
+ });
33
+ }),
34
+ );
35
+ });
36
+
37
+ // Handle notification click — focus existing tab or open new one
38
+ self.addEventListener("notificationclick", (event) => {
39
+ event.notification.close();
40
+ event.waitUntil(
41
+ self.clients
42
+ .matchAll({ type: "window", includeUncontrolled: true })
43
+ .then((clients) => {
44
+ for (const client of clients) {
45
+ if (client.url.includes(self.location.origin) && "focus" in client) {
46
+ return client.focus();
47
+ }
48
+ }
49
+ return self.clients.openWindow(event.notification.data?.url || "/");
50
+ }),
51
+ );
52
+ });
@@ -1,98 +0,0 @@
1
- # Claude Code - Global Environment Variables
2
- # Location: .claude/.env
3
- # Priority: LOWEST (overridden by skills/.env and skill-specific .env)
4
- # Scope: Project-wide configuration, global defaults
5
- # Setup: Copy to .claude/.env and configure
6
-
7
- # ============================================
8
- # Environment Variable Hierarchy
9
- # ============================================
10
- # Priority order (highest to lowest):
11
- # 1. process.env - Runtime environment (HIGHEST)
12
- # 2. .claude/skills/<skill>/.env - Skill-specific overrides
13
- # 3. .claude/skills/.env - Shared across all skills
14
- # 4. .claude/.env - Global defaults (this file, LOWEST)
15
- #
16
- # All skills use centralized resolver: ~/.claude/scripts/resolve_env.py
17
- # Debug hierarchy: python ~/.claude/scripts/resolve_env.py --show-hierarchy
18
-
19
- # ============================================
20
- # ClaudeKit API Key (for VidCap, ReviewWeb services)
21
- # ============================================
22
- # Get your API key from https://claudekit.cc/api-keys
23
- # Required for accessing ClaudeKit services via skills
24
- CLAUDEKIT_API_KEY=
25
-
26
- # ============================================
27
- # Claude Code Notification Hooks
28
- # ============================================
29
- # Discord Webhook URL (for Discord notifications)
30
- # Get from: Server Settings → Integrations → Webhooks → New Webhook
31
- DISCORD_WEBHOOK_URL=
32
-
33
- # Telegram Bot Token (for Telegram notifications)
34
- # Get from: @BotFather in Telegram
35
- TELEGRAM_BOT_TOKEN=
36
-
37
- # Telegram Chat ID (your chat ID or group ID)
38
- # Get from: https://api.telegram.org/bot<BOT_TOKEN>/getUpdates
39
- TELEGRAM_CHAT_ID=
40
-
41
- # ============================================
42
- # AI/ML API Keys (Global Defaults)
43
- # ============================================
44
- # Google Gemini API (for ai-multimodal, docs-seeker skills)
45
- # Get from: https://aistudio.google.com/apikey
46
- GEMINI_API_KEY=
47
-
48
- # Vertex AI Configuration (Optional alternative to AI Studio)
49
- # GEMINI_USE_VERTEX=true
50
- # VERTEX_PROJECT_ID=
51
- # VERTEX_LOCATION=us-central1
52
-
53
- # OpenAI API Key (if using OpenAI-based skills)
54
- # OPENAI_API_KEY=
55
-
56
- # Anthropic API Key (if using Claude API directly)
57
- # ANTHROPIC_API_KEY=
58
-
59
- # ElevenLabs API Key
60
- # Get your key at: https://elevenlabs.io/app/settings/api-keys
61
- # ELEVENLABS_API_KEY=
62
-
63
- # ============================================
64
- # Development & CI/CD
65
- # ============================================
66
- # NODE_ENV=development
67
- # DEBUG=false
68
- # LOG_LEVEL=info
69
-
70
- # ============================================
71
- # Project Configuration
72
- # ============================================
73
- # PROJECT_NAME=claudekit-engineer
74
- # ENVIRONMENT=local
75
-
76
- # ============================================
77
- # Example Usage Scenarios
78
- # ============================================
79
- # Scenario 1: Global default for all skills
80
- # .claude/.env (this file): GEMINI_API_KEY=global-dev-key
81
- # Result: All skills use global-dev-key
82
- #
83
- # Scenario 2: Override for all skills
84
- # .claude/.env (this file): GEMINI_API_KEY=global-dev-key
85
- # .claude/skills/.env: GEMINI_API_KEY=skills-prod-key
86
- # Result: All skills use skills-prod-key
87
- #
88
- # Scenario 3: Skill-specific override
89
- # .claude/.env (this file): GEMINI_API_KEY=global-key
90
- # .claude/skills/.env: GEMINI_API_KEY=shared-key
91
- # .claude/skills/ai-multimodal/.env: GEMINI_API_KEY=high-quota-key
92
- # Result: ai-multimodal uses high-quota-key, other skills use shared-key
93
- #
94
- # Scenario 4: Runtime testing
95
- # export GEMINI_API_KEY=test-key
96
- # Result: All skills use test-key regardless of config files
97
- #
98
- # Priority: runtime > skill-specific > shared > global (this file)
@@ -1,13 +0,0 @@
1
- # Google Ads API credentials
2
- GOOGLE_ADS_DEVELOPER_TOKEN=your_developer_token
3
- GOOGLE_ADS_CLIENT_ID=your_client_id.apps.googleusercontent.com
4
- GOOGLE_ADS_CLIENT_SECRET=your_client_secret
5
- GOOGLE_ADS_REFRESH_TOKEN=1//your_refresh_token
6
- GOOGLE_ADS_CUSTOMER_ID=1234567890
7
- GOOGLE_ADS_LOGIN_CUSTOMER_ID=your_mcc_id
8
-
9
- # Meta/Facebook Ads API credentials
10
- META_APP_ID=your_app_id
11
- META_APP_SECRET=your_app_secret
12
- META_ACCESS_TOKEN=your_system_user_token
13
- META_AD_ACCOUNT_ID=act_123456789
@@ -1,230 +0,0 @@
1
- # Google Gemini API Configuration
2
-
3
- # ============================================================================
4
- # OPTION 1: Google AI Studio (Default - Recommended for most users)
5
- # ============================================================================
6
- # Get your API key: https://aistudio.google.com/apikey
7
- GEMINI_API_KEY=your_api_key_here
8
-
9
- # ============================================================================
10
- # API Key Rotation (Optional - For high-volume usage)
11
- # ============================================================================
12
- # Add multiple API keys for automatic rotation on rate limit errors.
13
- # Free tier accounts are heavily rate-limited; rotation helps distribute load.
14
- #
15
- # Format: GEMINI_API_KEY_N where N is 2, 3, 4, etc.
16
- # The primary GEMINI_API_KEY is always used first.
17
- #
18
- # GEMINI_API_KEY_2=your_second_api_key
19
- # GEMINI_API_KEY_3=your_third_api_key
20
- # GEMINI_API_KEY_4=your_fourth_api_key
21
- #
22
- # Features:
23
- # - Auto-rotates on RESOURCE_EXHAUSTED / 429 errors
24
- # - 60-second cooldown per key after rate limit
25
- # - Logs rotation events with --verbose flag
26
- # - Backward compatible: single key still works
27
-
28
- # ============================================================================
29
- # OPTION 2: Vertex AI (Google Cloud Platform)
30
- # ============================================================================
31
- # Uncomment these lines to use Vertex AI instead of Google AI Studio
32
- # GEMINI_USE_VERTEX=true
33
- # VERTEX_PROJECT_ID=your-gcp-project-id
34
- # VERTEX_LOCATION=us-central1
35
-
36
- # ============================================================================
37
- # Model Selection (Optional)
38
- # ============================================================================
39
- # Override default models for specific capabilities
40
- # If not set, intelligent defaults are used based on task type
41
-
42
- # --- Image Generation ---
43
- # Used by: --task generate (image)
44
- # Default: gemini-2.5-flash-image (Nano Banana Flash - fast, cost-effective)
45
- # Alternative: imagen-4.0-generate-001 (production quality)
46
- # NOTE: All image generation requires billing - no free tier available (limit: 0)
47
- # Options:
48
- # gemini-2.5-flash-image - Nano Banana Flash: fast, ~$1/1M tokens (DEFAULT)
49
- # gemini-3-pro-image-preview - Nano Banana Pro: 4K text, reasoning (requires billing)
50
- # imagen-4.0-generate-001 - Imagen 4 Standard: production quality (~$0.02/image)
51
- # imagen-4.0-ultra-generate-001 - Imagen 4 Ultra: maximum quality (~$0.04/image)
52
- # imagen-4.0-fast-generate-001 - Imagen 4 Fast: speed-optimized (~$0.01/image)
53
- # IMAGE_GEN_MODEL=gemini-2.5-flash-image
54
-
55
- # --- Video Generation ---
56
- # Used by: --task generate-video (new capability)
57
- # Default: veo-3.1-generate-preview
58
- # NOTE: Video generation requires billing - no free tier fallback available
59
- # Options:
60
- # veo-3.1-generate-preview - Latest, native audio, frame control (requires billing)
61
- # veo-3.1-fast-generate-preview - Speed-optimized for business (requires billing)
62
- # veo-3.0-generate-001 - Stable, native audio, 8s videos (requires billing)
63
- # veo-3.0-fast-generate-001 - Stable fast variant (requires billing)
64
- # VIDEO_GEN_MODEL=veo-3.1-generate-preview
65
-
66
- # --- Multimodal Analysis ---
67
- # Used by: --task analyze, transcribe, extract
68
- # Default: gemini-2.5-flash
69
- # Options:
70
- # gemini-3-pro-preview - Latest, agentic workflows, 1M context
71
- # gemini-2.5-flash - Best price/performance (recommended)
72
- # gemini-2.5-pro - Highest quality
73
- # MULTIMODAL_MODEL=gemini-2.5-flash
74
-
75
- # --- Legacy Compatibility ---
76
- # Generic model override (use specific variables above instead)
77
- # GEMINI_MODEL=gemini-2.5-flash
78
- # GEMINI_IMAGE_GEN_MODEL=gemini-2.5-flash-image
79
-
80
- # ============================================================================
81
- # MiniMax API Configuration (Optional - for image/video/speech/music generation)
82
- # ============================================================================
83
- # Get your API key: https://platform.minimax.io/user-center/basic-information/interface-key
84
- # MINIMAX_API_KEY=your_minimax_api_key_here
85
-
86
- # --- MiniMax Image Generation ---
87
- # Models: image-01 (standard), image-01-live (enhanced)
88
- # Cost: ~$0.03/image | Rate: 10 RPM
89
- # MINIMAX_IMAGE_MODEL=image-01
90
-
91
- # --- MiniMax Video Generation (Hailuo) ---
92
- # Models: MiniMax-Hailuo-2.3, MiniMax-Hailuo-2.3-Fast, MiniMax-Hailuo-02, S2V-01
93
- # Cost: $0.25-0.52/video | Rate: 5 RPM
94
- # MINIMAX_VIDEO_MODEL=MiniMax-Hailuo-2.3
95
-
96
- # --- MiniMax Speech/TTS ---
97
- # Models: speech-2.8-hd (best), speech-2.8-turbo (fast)
98
- # Cost: $30-50/1M chars | Rate: 60 RPM | 300+ voices, 40+ languages
99
- # MINIMAX_SPEECH_MODEL=speech-2.8-hd
100
-
101
- # --- MiniMax Music Generation ---
102
- # Models: music-2.5 (4-minute songs with vocals)
103
- # Cost: $0.03-0.075/gen | Rate: 120 RPM
104
- # MINIMAX_MUSIC_MODEL=music-2.5
105
-
106
- # ============================================================================
107
- # Rate Limiting Configuration (Optional)
108
- # ============================================================================
109
- # Requests per minute limit (adjust based on your tier)
110
- # GEMINI_RPM_LIMIT=15
111
-
112
- # Tokens per minute limit
113
- # GEMINI_TPM_LIMIT=4000000
114
-
115
- # Requests per day limit
116
- # GEMINI_RPD_LIMIT=1500
117
-
118
- # ============================================================================
119
- # Video Generation Options (Optional)
120
- # ============================================================================
121
- # Video duration in seconds (8s only for now)
122
- # VEO_DURATION=8
123
-
124
- # Video resolution: 720p or 1080p
125
- # VEO_RESOLUTION=1080p
126
-
127
- # Aspect ratio: 16:9, 9:16, 1:1 (16:9 is default)
128
- # VEO_ASPECT_RATIO=16:9
129
-
130
- # Frame rate: 24fps (fixed for now)
131
- # VEO_FPS=24
132
-
133
- # Enable native audio generation
134
- # VEO_AUDIO=true
135
-
136
- # ============================================================================
137
- # Image Generation Options (Optional)
138
- # ============================================================================
139
- # Number of images to generate (1-4)
140
- # IMAGEN_NUM_IMAGES=1
141
-
142
- # Image size: 1K or 2K (Ultra/Standard only)
143
- # IMAGEN_SIZE=1K
144
-
145
- # Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4
146
- # IMAGEN_ASPECT_RATIO=1:1
147
-
148
- # Enable person generation (restricted in EEA, CH, UK)
149
- # IMAGEN_PERSON_GENERATION=true
150
-
151
- # Add SynthID watermark (always enabled by default)
152
- # IMAGEN_WATERMARK=true
153
-
154
- # ============================================================================
155
- # Processing Options (Optional)
156
- # ============================================================================
157
- # Video resolution mode: default or low-res
158
- # low-res uses ~100 tokens/second vs ~300 for default
159
- # GEMINI_VIDEO_RESOLUTION=default
160
-
161
- # Audio quality: default (16 Kbps mono, auto-downsampled)
162
- # GEMINI_AUDIO_QUALITY=default
163
-
164
- # PDF processing mode: inline (<20MB) or file-api (>20MB, automatic)
165
- # GEMINI_PDF_MODE=auto
166
-
167
- # ============================================================================
168
- # Retry Configuration (Optional)
169
- # ============================================================================
170
- # Maximum retry attempts for failed requests
171
- # GEMINI_MAX_RETRIES=3
172
-
173
- # Initial retry delay in seconds (uses exponential backoff)
174
- # GEMINI_RETRY_DELAY=1
175
-
176
- # ============================================================================
177
- # Output Configuration (Optional)
178
- # ============================================================================
179
- # Default output directory for generated images
180
- # OUTPUT_DIR=./output
181
-
182
- # Image output format (png or jpeg)
183
- # IMAGE_FORMAT=png
184
-
185
- # Image quality for JPEG (1-100)
186
- # IMAGE_QUALITY=95
187
-
188
- # ============================================================================
189
- # Context Caching (Optional)
190
- # ============================================================================
191
- # Enable context caching for repeated queries on same file
192
- # GEMINI_ENABLE_CACHING=true
193
-
194
- # Cache TTL in seconds (default: 1800 = 30 minutes)
195
- # GEMINI_CACHE_TTL=1800
196
-
197
- # ============================================================================
198
- # Logging (Optional)
199
- # ============================================================================
200
- # Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
201
- # LOG_LEVEL=INFO
202
-
203
- # Log file path
204
- # LOG_FILE=./logs/gemini.log
205
-
206
- # ============================================================================
207
- # Pricing Reference (as of 2025-11)
208
- # ============================================================================
209
- # Gemini 2.5 Flash: $1.00/1M input, $0.10/1M output
210
- # Gemini 2.5 Pro: $3.00/1M input, $12.00/1M output
211
- # Gemini 3 Pro: $2.00/1M input (<200k), $4.00 (>200k), $12/$18 output
212
- # Imagen 4: ~$0.01-$0.04 per image (varies by variant)
213
- # Veo 3: TBD (preview pricing)
214
- # Monitor: https://ai.google.dev/pricing
215
-
216
- # ============================================================================
217
- # Notes
218
- # ============================================================================
219
- # 1. Never commit API keys to version control
220
- # 2. Add .env to .gitignore
221
- # 3. API keys can be restricted in Google Cloud Console
222
- # 4. Monitor usage at: https://aistudio.google.com/apikey
223
- # 5. Free tier limits: 15 RPM, 1M-4M TPM, 1,500 RPD
224
- # 6. Vertex AI requires GCP authentication via gcloud CLI
225
- # 7. Model defaults (Dec 2025):
226
- # - Image gen: gemini-2.5-flash-image (Nano Banana Flash - default)
227
- # - Image gen: imagen-4.0-generate-001 (alternative for production)
228
- # - Video gen: veo-3.1-generate-preview
229
- # - Analysis: gemini-2.5-flash
230
- # 8. Preview models (veo-3.1, gemini-3) may have API changes
@@ -1,6 +0,0 @@
1
- # CIP Design Skill Environment Variables
2
- # Copy this file to .env and fill in your API key
3
-
4
- # Gemini API Key (required for image generation)
5
- # Get from: https://aistudio.google.com/apikey
6
- GEMINI_API_KEY=your-gemini-api-key-here