@vidiemme/copilot-proxy 0.1.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.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/attribution.d.ts +34 -0
- package/dist/attribution.js +118 -0
- package/dist/attribution.js.map +1 -0
- package/dist/autostart.d.ts +10 -0
- package/dist/autostart.js +119 -0
- package/dist/autostart.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +393 -0
- package/dist/cli.js.map +1 -0
- package/dist/collector/forwarder.d.ts +44 -0
- package/dist/collector/forwarder.js +203 -0
- package/dist/collector/forwarder.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +105 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults.d.ts +33 -0
- package/dist/defaults.js +35 -0
- package/dist/defaults.js.map +1 -0
- package/dist/gitRepository.d.ts +21 -0
- package/dist/gitRepository.js +147 -0
- package/dist/gitRepository.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.js +39 -0
- package/dist/paths.js.map +1 -0
- package/dist/projectDetector.d.ts +48 -0
- package/dist/projectDetector.js +130 -0
- package/dist/projectDetector.js.map +1 -0
- package/dist/proxy/handler.d.ts +5 -0
- package/dist/proxy/handler.js +149 -0
- package/dist/proxy/handler.js.map +1 -0
- package/dist/proxy/headers.d.ts +6 -0
- package/dist/proxy/headers.js +52 -0
- package/dist/proxy/headers.js.map +1 -0
- package/dist/proxy/inspectRequest.d.ts +18 -0
- package/dist/proxy/inspectRequest.js +41 -0
- package/dist/proxy/inspectRequest.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +66 -0
- package/dist/server.js.map +1 -0
- package/dist/usage/collector.d.ts +25 -0
- package/dist/usage/collector.js +63 -0
- package/dist/usage/collector.js.map +1 -0
- package/dist/usage/parseAnthropic.d.ts +23 -0
- package/dist/usage/parseAnthropic.js +83 -0
- package/dist/usage/parseAnthropic.js.map +1 -0
- package/dist/usage/parseOpenAI.d.ts +24 -0
- package/dist/usage/parseOpenAI.js +72 -0
- package/dist/usage/parseOpenAI.js.map +1 -0
- package/dist/usage/sse.d.ts +17 -0
- package/dist/usage/sse.js +59 -0
- package/dist/usage/sse.js.map +1 -0
- package/dist/usage/types.d.ts +23 -0
- package/dist/usage/types.js +19 -0
- package/dist/usage/types.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vidiemme
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @vidiemme/copilot-proxy
|
|
2
|
+
|
|
3
|
+
Proxy locale che misura il consumo di token di GitHub Copilot e lo attribuisce
|
|
4
|
+
al progetto su cui si sta lavorando.
|
|
5
|
+
|
|
6
|
+
Le statistiche native di Copilot sono per persona. Questo strumento risponde a
|
|
7
|
+
un'altra domanda: **quanto e' costato il progetto X**, sommando il lavoro di
|
|
8
|
+
tutte le persone che ci hanno messo mano.
|
|
9
|
+
|
|
10
|
+
## Come funziona
|
|
11
|
+
|
|
12
|
+
VS Code parla con il proxy invece che direttamente con Copilot. Il proxy inoltra
|
|
13
|
+
la richiesta cosi' com'e', legge dalla risposta i contatori di token e li manda
|
|
14
|
+
a un servizio di raccolta aziendale, che applica il listino e li salva.
|
|
15
|
+
|
|
16
|
+
Il progetto viene riconosciuto dal repository git su cui si sta lavorando: nessun
|
|
17
|
+
profilo da cambiare quando si passa da un progetto all'altro.
|
|
18
|
+
|
|
19
|
+
Non vengono mai salvati prompt ne' risposte: solo contatori. L'identita' della
|
|
20
|
+
persona e' uno pseudonimo, cioe' un hash con salt non invertibile.
|
|
21
|
+
|
|
22
|
+
## Uso
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# una volta sola, e poi basta
|
|
26
|
+
npx @vidiemme/copilot-proxy@latest setup --vscode --autostart
|
|
27
|
+
|
|
28
|
+
# se qualcosa non torna
|
|
29
|
+
npx @vidiemme/copilot-proxy@latest doctor
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Non c'e' nulla da configurare a mano: endpoint di raccolta, salt e cartelle dove
|
|
33
|
+
cercare i progetti sono gia' nel pacchetto. L'unica cosa da procurarsi e' il
|
|
34
|
+
token di raccolta, che il comando chiede in modo nascosto.
|
|
35
|
+
|
|
36
|
+
`--vscode` scrive le impostazioni utente di VS Code, `--autostart` registra il
|
|
37
|
+
proxy fra i servizi dell'utente: parte subito e a ogni accesso, senza che nessuno
|
|
38
|
+
debba ricordarsi di lanciarlo. Con entrambe, dopo questo comando non c'e' piu'
|
|
39
|
+
nulla da fare. Il comando stampa come disattivarlo.
|
|
40
|
+
|
|
41
|
+
Senza `--autostart` il proxy si avvia a mano:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @vidiemme/copilot-proxy@latest start
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Il token di raccolta non si passa come opzione: finirebbe nella cronologia della
|
|
48
|
+
shell e nella lista dei processi. Lo chiede il comando in modo nascosto, oppure
|
|
49
|
+
si mette nella variabile `COLLECTOR_TOKEN`.
|
|
50
|
+
|
|
51
|
+
La configurazione finisce in `~/.config/copilot-proxy/config.json` con permessi
|
|
52
|
+
`0600`.
|
|
53
|
+
|
|
54
|
+
## Requisiti
|
|
55
|
+
|
|
56
|
+
Node.js >= 22.9 e il token di ingest del servizio di raccolta: chiedilo a chi lo
|
|
57
|
+
amministra, e' lo stesso per tutta l'organizzazione.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ClientConfig } from './config.js';
|
|
2
|
+
export type AttributionSource = 'path' | 'header' | 'proxy-auth' | 'token' | 'workspace' | 'session' | 'pseudonym' | 'fallback';
|
|
3
|
+
export interface Attribution {
|
|
4
|
+
projectId: string;
|
|
5
|
+
developerId: string;
|
|
6
|
+
/** Da dove e' stato ricavato il progetto: utile per misurare la copertura. */
|
|
7
|
+
projectSource: AttributionSource;
|
|
8
|
+
/** Da dove arriva il developer: `pseudonym` = hash del token, non un nome reale. */
|
|
9
|
+
developerSource: AttributionSource;
|
|
10
|
+
clientName: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface AttributionInput {
|
|
13
|
+
/** Path della richiesta, comprensivo degli eventuali prefissi `/u/` e `/p/`. */
|
|
14
|
+
path: string;
|
|
15
|
+
headers: Record<string, string | string[] | undefined>;
|
|
16
|
+
}
|
|
17
|
+
export interface AttributionResult extends Attribution {
|
|
18
|
+
/** Path ripulito dai prefissi di attribuzione, da inoltrare upstream. */
|
|
19
|
+
upstreamPath: string;
|
|
20
|
+
}
|
|
21
|
+
/** Riduce un valore arbitrario a un identificatore stabile e sicuro da indicizzare. */
|
|
22
|
+
export declare function sanitizeIdentifier(value: string | undefined): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Determina progetto e developer di una richiesta.
|
|
25
|
+
*
|
|
26
|
+
* L'ordine di precedenza riflette quanto e' esplicito il segnale:
|
|
27
|
+
* 1. prefissi di path `/u/<developer>/p/<progetto>` — base URL dedicata
|
|
28
|
+
* 2. header `X-Project-Id` / `X-Developer-Id`
|
|
29
|
+
* 3. username delle credenziali proxy — client con forward proxy configurabile
|
|
30
|
+
* 4. mappa token statica
|
|
31
|
+
* 5. per il developer, hash salato del token upstream (pseudonimo stabile);
|
|
32
|
+
* per il progetto, `unassigned`
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveAttribution(input: AttributionInput, config: ClientConfig): AttributionResult;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { pseudonymize, stableIdentity } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Prefissi di attribuzione: `/u/<developer>` e `/p/<progetto>`, in qualsiasi
|
|
4
|
+
* ordine e opzionali. Il lookahead impone che segua altro path, cosi' una
|
|
5
|
+
* richiesta a `/p/acme` senza endpoint non viene interpretata come prefisso.
|
|
6
|
+
*/
|
|
7
|
+
const ATTRIBUTION_SEGMENT = /^\/(u|p)\/([A-Za-z0-9][A-Za-z0-9._-]{0,63})(?=\/)/;
|
|
8
|
+
function headerValue(headers, name) {
|
|
9
|
+
const value = headers[name];
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value[0];
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function decodeBasic(value) {
|
|
15
|
+
if (!value)
|
|
16
|
+
return undefined;
|
|
17
|
+
const match = /^basic\s+(.+)$/i.exec(value.trim());
|
|
18
|
+
if (!match)
|
|
19
|
+
return undefined;
|
|
20
|
+
const decoded = Buffer.from(match[1], 'base64').toString('utf-8');
|
|
21
|
+
const separator = decoded.indexOf(':');
|
|
22
|
+
if (separator < 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
return { user: decoded.slice(0, separator), pass: decoded.slice(separator + 1) };
|
|
25
|
+
}
|
|
26
|
+
function bearerToken(value) {
|
|
27
|
+
if (!value)
|
|
28
|
+
return undefined;
|
|
29
|
+
const match = /^bearer\s+(.+)$/i.exec(value.trim());
|
|
30
|
+
return match ? match[1].trim() : value.trim();
|
|
31
|
+
}
|
|
32
|
+
/** Riduce un valore arbitrario a un identificatore stabile e sicuro da indicizzare. */
|
|
33
|
+
export function sanitizeIdentifier(value) {
|
|
34
|
+
if (!value)
|
|
35
|
+
return undefined;
|
|
36
|
+
const cleaned = value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, '-').slice(0, 64);
|
|
37
|
+
return cleaned.length > 0 ? cleaned : undefined;
|
|
38
|
+
}
|
|
39
|
+
const sanitize = sanitizeIdentifier;
|
|
40
|
+
/**
|
|
41
|
+
* Determina progetto e developer di una richiesta.
|
|
42
|
+
*
|
|
43
|
+
* L'ordine di precedenza riflette quanto e' esplicito il segnale:
|
|
44
|
+
* 1. prefissi di path `/u/<developer>/p/<progetto>` — base URL dedicata
|
|
45
|
+
* 2. header `X-Project-Id` / `X-Developer-Id`
|
|
46
|
+
* 3. username delle credenziali proxy — client con forward proxy configurabile
|
|
47
|
+
* 4. mappa token statica
|
|
48
|
+
* 5. per il developer, hash salato del token upstream (pseudonimo stabile);
|
|
49
|
+
* per il progetto, `unassigned`
|
|
50
|
+
*/
|
|
51
|
+
export function resolveAttribution(input, config) {
|
|
52
|
+
const headers = input.headers;
|
|
53
|
+
let projectId;
|
|
54
|
+
let projectSource = 'fallback';
|
|
55
|
+
let developerId;
|
|
56
|
+
let developerSource = 'fallback';
|
|
57
|
+
let upstreamPath = input.path;
|
|
58
|
+
for (;;) {
|
|
59
|
+
const match = ATTRIBUTION_SEGMENT.exec(upstreamPath);
|
|
60
|
+
if (!match)
|
|
61
|
+
break;
|
|
62
|
+
upstreamPath = upstreamPath.slice(match[0].length);
|
|
63
|
+
const value = sanitize(match[2]);
|
|
64
|
+
if (!value)
|
|
65
|
+
continue;
|
|
66
|
+
if (match[1] === 'p') {
|
|
67
|
+
projectId ??= value;
|
|
68
|
+
projectSource = 'path';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
developerId ??= value;
|
|
72
|
+
developerSource = 'path';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!projectId) {
|
|
76
|
+
projectId = sanitize(headerValue(headers, config.projectHeader));
|
|
77
|
+
if (projectId)
|
|
78
|
+
projectSource = 'header';
|
|
79
|
+
}
|
|
80
|
+
if (!developerId) {
|
|
81
|
+
developerId = sanitize(headerValue(headers, 'x-developer-id'));
|
|
82
|
+
if (developerId)
|
|
83
|
+
developerSource = 'header';
|
|
84
|
+
}
|
|
85
|
+
const proxyAuth = decodeBasic(headerValue(headers, 'proxy-authorization'));
|
|
86
|
+
if (!projectId && proxyAuth) {
|
|
87
|
+
projectId = sanitize(proxyAuth.user);
|
|
88
|
+
if (projectId)
|
|
89
|
+
projectSource = 'proxy-auth';
|
|
90
|
+
}
|
|
91
|
+
const upstreamToken = bearerToken(headerValue(headers, 'authorization'));
|
|
92
|
+
const mapping = (upstreamToken ? config.attributionTokens[upstreamToken] : undefined) ??
|
|
93
|
+
(proxyAuth ? config.attributionTokens[proxyAuth.pass] : undefined);
|
|
94
|
+
if (!projectId && mapping?.projectId) {
|
|
95
|
+
projectId = sanitize(mapping.projectId);
|
|
96
|
+
if (projectId)
|
|
97
|
+
projectSource = 'token';
|
|
98
|
+
}
|
|
99
|
+
if (!developerId && mapping?.developerId) {
|
|
100
|
+
developerId = sanitize(mapping.developerId);
|
|
101
|
+
if (developerId)
|
|
102
|
+
developerSource = 'token';
|
|
103
|
+
}
|
|
104
|
+
if (!developerId && upstreamToken) {
|
|
105
|
+
developerId = pseudonymize(stableIdentity(upstreamToken), config.developerIdSalt);
|
|
106
|
+
developerSource = 'pseudonym';
|
|
107
|
+
}
|
|
108
|
+
const userAgent = headerValue(headers, 'user-agent');
|
|
109
|
+
return {
|
|
110
|
+
projectId: projectId ?? config.unassignedProject,
|
|
111
|
+
projectSource,
|
|
112
|
+
developerId: developerId ?? 'unknown',
|
|
113
|
+
developerSource,
|
|
114
|
+
clientName: userAgent ? userAgent.slice(0, 200) : null,
|
|
115
|
+
upstreamPath,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=attribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AutostartResult {
|
|
2
|
+
file: string;
|
|
3
|
+
/** Comando per disattivarlo: va stampato, cosi' chi installa sa come tornare indietro. */
|
|
4
|
+
removeHint: string;
|
|
5
|
+
/** Avvertenza se il percorso registrato potrebbe non sopravvivere. */
|
|
6
|
+
warning?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function autostartFile(): string | undefined;
|
|
9
|
+
export declare function isAutostartInstalled(): boolean;
|
|
10
|
+
export declare function installAutostart(): AutostartResult;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { configDir } from './paths.js';
|
|
7
|
+
const LABEL = 'com.vidiemme.copilot-proxy';
|
|
8
|
+
const UNIT = 'copilot-proxy.service';
|
|
9
|
+
export function autostartFile() {
|
|
10
|
+
if (process.platform === 'darwin') {
|
|
11
|
+
return join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
12
|
+
}
|
|
13
|
+
if (process.platform === 'linux') {
|
|
14
|
+
return join(homedir(), '.config', 'systemd', 'user', UNIT);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export function isAutostartInstalled() {
|
|
19
|
+
const file = autostartFile();
|
|
20
|
+
return file !== undefined && existsSync(file);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Percorso del `cli.js` compilato, risolto rispetto a questo modulo.
|
|
24
|
+
*
|
|
25
|
+
* Non si usa `process.argv[1]`: con un `bin` di npm quello e' uno shim, e il
|
|
26
|
+
* servizio deve puntare al file vero.
|
|
27
|
+
*/
|
|
28
|
+
function cliEntry() {
|
|
29
|
+
return fileURLToPath(new URL('cli.js', import.meta.url));
|
|
30
|
+
}
|
|
31
|
+
function escapeXml(value) {
|
|
32
|
+
return value.replace(/[&<>]/g, (char) => (char === '&' ? '&' : char === '<' ? '<' : '>'));
|
|
33
|
+
}
|
|
34
|
+
function environmentOverrides() {
|
|
35
|
+
const home = process.env.COPILOT_PROXY_HOME?.trim();
|
|
36
|
+
// Il servizio parte senza l'ambiente della shell: se la configurazione non e'
|
|
37
|
+
// nella posizione di default, il percorso va inciso nel servizio stesso.
|
|
38
|
+
return home ? { COPILOT_PROXY_HOME: home } : {};
|
|
39
|
+
}
|
|
40
|
+
export function installAutostart() {
|
|
41
|
+
const file = autostartFile();
|
|
42
|
+
if (file === undefined) {
|
|
43
|
+
throw new Error(`Avvio automatico non gestito su ${process.platform}. Su Windows aggiungi a mano un collegamento a "${process.execPath} ${cliEntry()} start" nella cartella Esecuzione automatica.`);
|
|
44
|
+
}
|
|
45
|
+
const entry = cliEntry();
|
|
46
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
47
|
+
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
48
|
+
const result = process.platform === 'darwin' ? installLaunchd(file, entry) : installSystemd(file, entry);
|
|
49
|
+
// La cache di npx non e' una posizione stabile: `npm cache clean` la svuota e
|
|
50
|
+
// il servizio resterebbe a puntare nel vuoto.
|
|
51
|
+
if (entry.includes('/_npx/')) {
|
|
52
|
+
result.warning =
|
|
53
|
+
'Il servizio punta dentro la cache di npx, che puo\' essere ripulita. Per un avvio automatico stabile: npm i -g @vidiemme/copilot-proxy, poi rilancia il setup con --autostart';
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function installLaunchd(file, entry) {
|
|
58
|
+
const log = join(configDir(), 'proxy.log');
|
|
59
|
+
const env = environmentOverrides();
|
|
60
|
+
const envBlock = Object.keys(env).length === 0
|
|
61
|
+
? ''
|
|
62
|
+
: ` <key>EnvironmentVariables</key>\n <dict>\n${Object.entries(env)
|
|
63
|
+
.map(([key, value]) => ` <key>${escapeXml(key)}</key><string>${escapeXml(value)}</string>`)
|
|
64
|
+
.join('\n')}\n </dict>\n`;
|
|
65
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
67
|
+
<plist version="1.0">
|
|
68
|
+
<dict>
|
|
69
|
+
<key>Label</key><string>${LABEL}</string>
|
|
70
|
+
<key>ProgramArguments</key>
|
|
71
|
+
<array>
|
|
72
|
+
<string>${escapeXml(process.execPath)}</string>
|
|
73
|
+
<string>${escapeXml(entry)}</string>
|
|
74
|
+
<string>start</string>
|
|
75
|
+
</array>
|
|
76
|
+
<key>RunAtLoad</key><true/>
|
|
77
|
+
<key>KeepAlive</key><true/>
|
|
78
|
+
${envBlock} <key>StandardOutPath</key><string>${escapeXml(log)}</string>
|
|
79
|
+
<key>StandardErrorPath</key><string>${escapeXml(log)}</string>
|
|
80
|
+
</dict>
|
|
81
|
+
</plist>
|
|
82
|
+
`;
|
|
83
|
+
writeFileSync(file, plist);
|
|
84
|
+
const domain = `gui/${process.getuid?.() ?? ''}`;
|
|
85
|
+
// Un servizio gia' caricato fa fallire `bootstrap`: si scarica prima, e se
|
|
86
|
+
// non c'era l'errore non interessa.
|
|
87
|
+
try {
|
|
88
|
+
execFileSync('launchctl', ['bootout', `${domain}/${LABEL}`], { stdio: 'ignore' });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* non era caricato */
|
|
92
|
+
}
|
|
93
|
+
execFileSync('launchctl', ['bootstrap', domain, file], { stdio: 'pipe' });
|
|
94
|
+
return { file, removeHint: `launchctl bootout ${domain}/${LABEL} && rm ${file}` };
|
|
95
|
+
}
|
|
96
|
+
function installSystemd(file, entry) {
|
|
97
|
+
const env = environmentOverrides();
|
|
98
|
+
const envLines = Object.entries(env)
|
|
99
|
+
.map(([key, value]) => `Environment=${key}=${value}`)
|
|
100
|
+
.join('\n');
|
|
101
|
+
const unit = `[Unit]
|
|
102
|
+
Description=Proxy locale di misurazione del consumo Copilot
|
|
103
|
+
After=network-online.target
|
|
104
|
+
|
|
105
|
+
[Service]
|
|
106
|
+
ExecStart=${process.execPath} ${entry} start
|
|
107
|
+
Restart=always
|
|
108
|
+
RestartSec=5
|
|
109
|
+
${envLines}
|
|
110
|
+
|
|
111
|
+
[Install]
|
|
112
|
+
WantedBy=default.target
|
|
113
|
+
`;
|
|
114
|
+
writeFileSync(file, unit);
|
|
115
|
+
execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'pipe' });
|
|
116
|
+
execFileSync('systemctl', ['--user', 'enable', '--now', UNIT], { stdio: 'pipe' });
|
|
117
|
+
return { file, removeHint: `systemctl --user disable --now ${UNIT} && rm ${file}` };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=autostart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED