claude-code-discord-presence 1.0.0 → 1.0.1
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/README.md +3 -0
- package/dist/discord/rpc-client.js +19 -2
- package/dist/index.js +15 -2
- package/package.json +1 -1
- package/scripts/remove-autostart.ps1 +35 -0
- package/scripts/run-service.ps1 +17 -1
- package/scripts/setup-autostart.ps1 +2 -0
package/README.md
CHANGED
|
@@ -85,6 +85,9 @@ The command ensures hooks are installed before registering startup. Remove it wi
|
|
|
85
85
|
claude-code-presence autostart:remove
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
`Ctrl+C` stops only the foreground instance in the current terminal. The removal command also
|
|
89
|
+
gracefully stops an active background instance so Discord activity is cleared before exit.
|
|
90
|
+
|
|
88
91
|
## Optional configuration
|
|
89
92
|
|
|
90
93
|
Defaults work without configuration. Pass options directly:
|
|
@@ -36,6 +36,7 @@ export class RpcClient {
|
|
|
36
36
|
reconnectTimer;
|
|
37
37
|
reconnectAttempt = 0;
|
|
38
38
|
connectionGeneration = 0;
|
|
39
|
+
activeSend;
|
|
39
40
|
constructor(applicationId, transport) {
|
|
40
41
|
if (typeof transport === "function") {
|
|
41
42
|
this.factory = transport;
|
|
@@ -68,6 +69,9 @@ export class RpcClient {
|
|
|
68
69
|
this.flushTimer = undefined;
|
|
69
70
|
this.reconnectTimer = undefined;
|
|
70
71
|
this.refreshTimer = undefined;
|
|
72
|
+
const activeSend = this.activeSend;
|
|
73
|
+
if (activeSend)
|
|
74
|
+
await activeSend.catch(() => undefined);
|
|
71
75
|
const client = this.client;
|
|
72
76
|
if (!client)
|
|
73
77
|
return;
|
|
@@ -102,6 +106,8 @@ export class RpcClient {
|
|
|
102
106
|
return;
|
|
103
107
|
this.connectionGeneration++;
|
|
104
108
|
this.ready = false;
|
|
109
|
+
if (this.stopped)
|
|
110
|
+
return;
|
|
105
111
|
log.warn("disconnected from Discord");
|
|
106
112
|
this.scheduleReconnect();
|
|
107
113
|
});
|
|
@@ -185,12 +191,23 @@ export class RpcClient {
|
|
|
185
191
|
if (desired === null) {
|
|
186
192
|
if (this.lastSent === null && this.lastSentPid === pid)
|
|
187
193
|
return;
|
|
188
|
-
|
|
194
|
+
this.beginSend(desired, pid);
|
|
189
195
|
return;
|
|
190
196
|
}
|
|
191
197
|
if (this.lastSentPid === pid && this.lastSent !== null && activityEquals(desired, this.lastSent))
|
|
192
198
|
return;
|
|
193
|
-
|
|
199
|
+
this.beginSend(desired, pid);
|
|
200
|
+
}
|
|
201
|
+
beginSend(activity, pid) {
|
|
202
|
+
const pending = this.send(activity, pid);
|
|
203
|
+
this.activeSend = pending;
|
|
204
|
+
void pending.then(() => {
|
|
205
|
+
if (this.activeSend === pending)
|
|
206
|
+
this.activeSend = undefined;
|
|
207
|
+
}, () => {
|
|
208
|
+
if (this.activeSend === pending)
|
|
209
|
+
this.activeSend = undefined;
|
|
210
|
+
});
|
|
194
211
|
}
|
|
195
212
|
async send(activity, pid) {
|
|
196
213
|
const client = this.client;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { loadConfig } from "./config.js";
|
|
3
3
|
import { startServer } from "./server/http-server.js";
|
|
4
4
|
import { SessionStore } from "./claude/session-store.js";
|
|
@@ -134,6 +134,8 @@ monthlyUsage.start();
|
|
|
134
134
|
remoteTunnels.start();
|
|
135
135
|
void refreshPlan();
|
|
136
136
|
const planTimer = setInterval(() => void refreshPlan(), 30 * 60 * 1000);
|
|
137
|
+
const stopFile = process.env.PRESENCE_STOP_FILE?.trim();
|
|
138
|
+
let stopFileTimer;
|
|
137
139
|
let shuttingDown = false;
|
|
138
140
|
async function shutdown(signal) {
|
|
139
141
|
if (shuttingDown)
|
|
@@ -141,6 +143,8 @@ async function shutdown(signal) {
|
|
|
141
143
|
shuttingDown = true;
|
|
142
144
|
log.info(`shutting down (${signal})`);
|
|
143
145
|
clearInterval(planTimer);
|
|
146
|
+
if (stopFileTimer)
|
|
147
|
+
clearInterval(stopFileTimer);
|
|
144
148
|
themeWatcher.stop();
|
|
145
149
|
poller.stop();
|
|
146
150
|
desktopFocus?.stop();
|
|
@@ -151,10 +155,19 @@ async function shutdown(signal) {
|
|
|
151
155
|
monthlyUsage.stop();
|
|
152
156
|
remoteTunnels.stop();
|
|
153
157
|
store.dispose();
|
|
154
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
155
158
|
await rpc.stop();
|
|
159
|
+
await new Promise((resolve) => {
|
|
160
|
+
server.close(() => resolve());
|
|
161
|
+
server.closeAllConnections();
|
|
162
|
+
});
|
|
156
163
|
process.exit(0);
|
|
157
164
|
}
|
|
158
165
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
159
166
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
167
|
+
if (stopFile) {
|
|
168
|
+
stopFileTimer = setInterval(() => {
|
|
169
|
+
if (existsSync(stopFile))
|
|
170
|
+
void shutdown("autostart removal");
|
|
171
|
+
}, 500);
|
|
172
|
+
}
|
|
160
173
|
log.info("Claude Code Discord Presence started");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-discord-presence",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Cross-platform Discord Rich Presence for Claude Code Desktop and CLI with live models, limits, costs, tools, agents, statusline, and secure SSH remotes.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
#Requires -Version 5.1
|
|
2
2
|
$ErrorActionPreference = 'Stop'
|
|
3
3
|
|
|
4
|
+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
5
|
+
$runScript = Join-Path $scriptDir 'run-service.ps1'
|
|
6
|
+
$stopFile = Join-Path $env:LOCALAPPDATA 'Claude Code Discord Presence\stop.request'
|
|
4
7
|
$name = 'ClaudeCodeDiscordPresence'
|
|
5
8
|
$runKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
|
|
6
9
|
Remove-ItemProperty -Path $runKey -Name $name -ErrorAction SilentlyContinue
|
|
10
|
+
|
|
11
|
+
$escapedRunScript = [WildcardPattern]::Escape($runScript)
|
|
12
|
+
function Get-PresenceSupervisor {
|
|
13
|
+
@(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
|
14
|
+
$_.Name -match '^(?:powershell|pwsh)\.exe$' -and
|
|
15
|
+
$_.CommandLine -like "*$escapedRunScript*"
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
$supervisors = @(Get-PresenceSupervisor)
|
|
20
|
+
if ($supervisors.Count -gt 0) {
|
|
21
|
+
$stopDir = Split-Path -Parent $stopFile
|
|
22
|
+
if (-not (Test-Path -LiteralPath $stopDir)) {
|
|
23
|
+
New-Item -ItemType Directory -Path $stopDir -Force | Out-Null
|
|
24
|
+
}
|
|
25
|
+
[IO.File]::WriteAllText(
|
|
26
|
+
$stopFile,
|
|
27
|
+
[DateTimeOffset]::Now.ToString('O'),
|
|
28
|
+
[Text.UTF8Encoding]::new($false)
|
|
29
|
+
)
|
|
30
|
+
$deadline = [DateTimeOffset]::Now.AddSeconds(15)
|
|
31
|
+
do {
|
|
32
|
+
Start-Sleep -Milliseconds 250
|
|
33
|
+
$supervisors = @(Get-PresenceSupervisor)
|
|
34
|
+
} while ($supervisors.Count -gt 0 -and [DateTimeOffset]::Now -lt $deadline)
|
|
35
|
+
if ($supervisors.Count -gt 0) {
|
|
36
|
+
throw 'The background service did not stop within 15 seconds.'
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
Remove-Item -LiteralPath $stopFile -Force -ErrorAction SilentlyContinue
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
Write-Host "Removed '$name' from $runKey."
|
package/scripts/run-service.ps1
CHANGED
|
@@ -3,7 +3,9 @@ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
|
3
3
|
$projectDir = Split-Path -Parent $scriptDir
|
|
4
4
|
$entryPoint = Join-Path $projectDir 'dist\index.js'
|
|
5
5
|
$logFile = Join-Path $env:LOCALAPPDATA 'Claude Code Discord Presence\supervisor.log'
|
|
6
|
+
$stopFile = Join-Path $env:LOCALAPPDATA 'Claude Code Discord Presence\stop.request'
|
|
6
7
|
$maxLogBytes = 1MB
|
|
8
|
+
$env:PRESENCE_STOP_FILE = $stopFile
|
|
7
9
|
|
|
8
10
|
$created = $false
|
|
9
11
|
$mutex = New-Object System.Threading.Mutex($true, 'Local\ClaudeCodeDiscordPresenceSupervisor', [ref]$created)
|
|
@@ -32,13 +34,27 @@ function Write-BoundedLog([string]$message) {
|
|
|
32
34
|
Rotate-Log
|
|
33
35
|
$fastExits = 0
|
|
34
36
|
while ($true) {
|
|
37
|
+
if (Test-Path -LiteralPath $stopFile) {
|
|
38
|
+
Remove-Item -LiteralPath $stopFile -Force -ErrorAction SilentlyContinue
|
|
39
|
+
exit 0
|
|
40
|
+
}
|
|
35
41
|
$startedAt = Get-Date
|
|
36
42
|
& node --disable-warning=ExperimentalWarning --env-file-if-exists=.env --enable-source-maps $entryPoint 2>&1 |
|
|
37
43
|
ForEach-Object { Write-BoundedLog ([string]$_) }
|
|
38
44
|
$exitCode = $LASTEXITCODE
|
|
39
45
|
$uptimeSeconds = [int]((Get-Date) - $startedAt).TotalSeconds
|
|
40
46
|
Write-BoundedLog "[$(Get-Date -Format o)] supervisor: service exited code=$exitCode uptime=${uptimeSeconds}s"
|
|
47
|
+
if (Test-Path -LiteralPath $stopFile) {
|
|
48
|
+
Remove-Item -LiteralPath $stopFile -Force -ErrorAction SilentlyContinue
|
|
49
|
+
exit 0
|
|
50
|
+
}
|
|
41
51
|
if ($uptimeSeconds -lt 60) { $fastExits++ } else { $fastExits = 0 }
|
|
42
52
|
$delay = [int][Math]::Min(60, 5 * [Math]::Pow(2, [Math]::Min($fastExits, 4)))
|
|
43
|
-
|
|
53
|
+
for ($i = 0; $i -lt $delay * 2; $i++) {
|
|
54
|
+
if (Test-Path -LiteralPath $stopFile) {
|
|
55
|
+
Remove-Item -LiteralPath $stopFile -Force -ErrorAction SilentlyContinue
|
|
56
|
+
exit 0
|
|
57
|
+
}
|
|
58
|
+
Start-Sleep -Milliseconds 500
|
|
59
|
+
}
|
|
44
60
|
}
|
|
@@ -4,6 +4,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
4
4
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
5
5
|
$projectDir = Split-Path -Parent $scriptDir
|
|
6
6
|
$launcher = Join-Path $scriptDir 'run-hidden.vbs'
|
|
7
|
+
$stopFile = Join-Path $env:LOCALAPPDATA 'Claude Code Discord Presence\stop.request'
|
|
7
8
|
$entryPoint = Join-Path $projectDir 'dist\index.js'
|
|
8
9
|
$name = 'ClaudeCodeDiscordPresence'
|
|
9
10
|
$runKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
|
|
@@ -20,6 +21,7 @@ if (-not (Test-Path -LiteralPath $entryPoint)) {
|
|
|
20
21
|
|
|
21
22
|
$command = "wscript.exe `"$launcher`""
|
|
22
23
|
Set-ItemProperty -Path $runKey -Name $name -Value $command
|
|
24
|
+
Remove-Item -LiteralPath $stopFile -Force -ErrorAction SilentlyContinue
|
|
23
25
|
|
|
24
26
|
$escapedEntryPoint = [WildcardPattern]::Escape($entryPoint)
|
|
25
27
|
$existing = Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction SilentlyContinue |
|