sidekick-shared 0.26.4 → 0.26.6
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/CHANGELOG.md +52 -0
- package/README.md +59 -51
- package/dist/accountChanges.js +21 -2
- package/dist/accountHealth.d.ts +72 -0
- package/dist/accountHealth.js +286 -0
- package/dist/accountLaunch.d.ts +30 -0
- package/dist/accountLaunch.js +124 -0
- package/dist/accountManager.d.ts +33 -8
- package/dist/accountManager.js +183 -50
- package/dist/accountRegistry.d.ts +10 -0
- package/dist/accountRegistry.js +42 -22
- package/dist/accountSwitch.d.ts +43 -0
- package/dist/accountSwitch.js +115 -0
- package/dist/accountSync.d.ts +30 -0
- package/dist/accountSync.js +180 -0
- package/dist/accountSyncTypes.d.ts +25 -0
- package/dist/accountSyncTypes.js +7 -0
- package/dist/accounts.d.ts +58 -6
- package/dist/accounts.js +408 -189
- package/dist/claudeCredentials.d.ts +38 -0
- package/dist/claudeCredentials.js +74 -0
- package/dist/claudeProfiles.d.ts +40 -0
- package/dist/claudeProfiles.js +102 -10
- package/dist/codexAuth.d.ts +32 -0
- package/dist/codexAuth.js +127 -0
- package/dist/codexPaths.d.ts +21 -0
- package/dist/codexPaths.js +117 -0
- package/dist/codexProfiles.d.ts +58 -5
- package/dist/codexProfiles.js +574 -289
- package/dist/credentialIO.d.ts +12 -5
- package/dist/credentialIO.js +63 -29
- package/dist/credentials.d.ts +8 -0
- package/dist/credentials.js +28 -32
- package/dist/ensureDefaultAccounts.d.ts +8 -0
- package/dist/ensureDefaultAccounts.js +42 -82
- package/dist/index.d.ts +31 -12
- package/dist/index.js +68 -13
- package/dist/keepAlive.d.ts +44 -0
- package/dist/keepAlive.js +106 -0
- package/dist/node.d.ts +4 -1
- package/dist/node.js +4 -1
- package/dist/observedSessionCollector.d.ts +117 -5
- package/dist/observedSessionCollector.js +483 -76
- package/dist/processDetection.d.ts +32 -0
- package/dist/processDetection.js +210 -0
- package/dist/providers/claudeCode.d.ts +23 -2
- package/dist/providers/claudeCode.js +143 -51
- package/dist/providers/codex.d.ts +10 -2
- package/dist/providers/codex.js +95 -12
- package/dist/providers/directoryListingCache.d.ts +85 -0
- package/dist/providers/directoryListingCache.js +244 -0
- package/dist/providers/rolloutWalker.d.ts +28 -5
- package/dist/providers/rolloutWalker.js +100 -16
- package/dist/providers/types.d.ts +48 -1
- package/dist/schemas/accountManager.d.ts +283 -1
- package/dist/schemas/accountManager.js +122 -1
- package/dist/schemas/assistantTurn.d.ts +2 -2
- package/dist/schemas/index.d.ts +6 -1
- package/dist/schemas/index.js +14 -1
- package/dist/schemas/observedSessionV1.d.ts +21 -21
- package/dist/schemas/quota.d.ts +18 -18
- package/dist/schemas/quotaHistory.d.ts +1 -1
- package/dist/schemas/stateFile.d.ts +5 -5
- package/dist/sessionMonitor.js +24 -2
- package/dist/terminalSync.d.ts +5 -5
- package/dist/terminalSync.js +7 -112
- package/dist/writers/atomic.d.ts +2 -0
- package/dist/writers/atomic.js +49 -2
- package/package.json +1 -1
package/dist/sessionMonitor.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.SessionMonitor = void 0;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
38
39
|
const EventAggregator_1 = require("./aggregation/EventAggregator");
|
|
39
40
|
const observedSessionCollector_1 = require("./observedSessionCollector");
|
|
40
41
|
/**
|
|
@@ -119,6 +120,22 @@ class SessionMonitor {
|
|
|
119
120
|
];
|
|
120
121
|
},
|
|
121
122
|
read: () => null,
|
|
123
|
+
// One session is watched, so an event naming any other file costs
|
|
124
|
+
// nothing; an event naming this file costs one stat.
|
|
125
|
+
resolveReference: async (signal) => {
|
|
126
|
+
if (!signal.root || typeof signal.filename !== 'string' || !signal.filename) {
|
|
127
|
+
return { status: 'unknown' };
|
|
128
|
+
}
|
|
129
|
+
if (!this.sessionPath)
|
|
130
|
+
return { status: 'ignored' };
|
|
131
|
+
const eventPath = path.resolve(signal.root, signal.filename);
|
|
132
|
+
if (eventPath !== path.resolve(this.sessionPath))
|
|
133
|
+
return { status: 'ignored' };
|
|
134
|
+
const [reference] = await source.discover();
|
|
135
|
+
return reference
|
|
136
|
+
? { status: 'present', reference }
|
|
137
|
+
: { status: 'missing', sessionId: this.provider.getSessionId(this.sessionPath) };
|
|
138
|
+
},
|
|
122
139
|
subscribe: (listener, sourceOptions) => {
|
|
123
140
|
const watchers = new Set();
|
|
124
141
|
let pollTimer = null;
|
|
@@ -131,7 +148,12 @@ class SessionMonitor {
|
|
|
131
148
|
}
|
|
132
149
|
for (const root of new Set(roots)) {
|
|
133
150
|
try {
|
|
134
|
-
const watcher = fs.watch(root, { persistent: false, recursive: true }, listener
|
|
151
|
+
const watcher = fs.watch(root, { persistent: false, recursive: true }, (eventType, filename) => listener({
|
|
152
|
+
trigger: 'event',
|
|
153
|
+
root,
|
|
154
|
+
eventType,
|
|
155
|
+
filename: filename === null || filename === undefined ? null : String(filename),
|
|
156
|
+
}));
|
|
135
157
|
watchers.add(watcher);
|
|
136
158
|
watcher.on('error', () => {
|
|
137
159
|
watchers.delete(watcher);
|
|
@@ -149,7 +171,7 @@ class SessionMonitor {
|
|
|
149
171
|
}
|
|
150
172
|
const pollIntervalMs = sourceOptions?.pollIntervalMs ?? 0;
|
|
151
173
|
if (pollIntervalMs > 0) {
|
|
152
|
-
pollTimer = setInterval(listener, pollIntervalMs);
|
|
174
|
+
pollTimer = setInterval(() => listener({ trigger: 'poll' }), pollIntervalMs);
|
|
153
175
|
pollTimer.unref?.();
|
|
154
176
|
}
|
|
155
177
|
return {
|
package/dist/terminalSync.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { AccountProviderId } from './accountRegistry';
|
|
2
|
+
/**
|
|
3
|
+
* Write a POSIX launcher script that runs the provider CLI against a profile
|
|
4
|
+
* home. Unix only; on Windows use `sidekick accounts env --shell powershell`.
|
|
5
|
+
*/
|
|
6
6
|
export declare function writeLauncher(name: string, provider: AccountProviderId, profileHome: string): void;
|
|
7
7
|
export declare function removeLauncher(name: string): void;
|
package/dist/terminalSync.js
CHANGED
|
@@ -33,130 +33,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.setTerminalActiveProfile = setTerminalActiveProfile;
|
|
37
|
-
exports.installShellHook = installShellHook;
|
|
38
|
-
exports.uninstallShellHook = uninstallShellHook;
|
|
39
|
-
exports.isShellHookInstalled = isShellHookInstalled;
|
|
40
36
|
exports.writeLauncher = writeLauncher;
|
|
41
37
|
exports.removeLauncher = removeLauncher;
|
|
42
38
|
const fs = __importStar(require("fs"));
|
|
43
39
|
const os = __importStar(require("os"));
|
|
44
40
|
const path = __importStar(require("path"));
|
|
45
|
-
const
|
|
46
|
-
const HOOK_START = '# >>> sidekick >>>';
|
|
47
|
-
const HOOK_END = '# <<< sidekick <<<';
|
|
41
|
+
const atomic_1 = require("./writers/atomic");
|
|
48
42
|
const LAUNCHER_MARKER = '# sidekick-launcher v1';
|
|
49
43
|
const LAUNCHER_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
50
|
-
function providerPointerName(provider) {
|
|
51
|
-
return provider === 'claude-code' ? 'claude' : 'codex';
|
|
52
|
-
}
|
|
53
44
|
function providerEnvVar(provider) {
|
|
54
45
|
return provider === 'claude-code' ? 'CLAUDE_CONFIG_DIR' : 'CODEX_HOME';
|
|
55
46
|
}
|
|
56
47
|
function providerBinary(provider) {
|
|
57
48
|
return provider === 'claude-code' ? 'claude' : 'codex';
|
|
58
49
|
}
|
|
59
|
-
function getActiveProfilesDir() {
|
|
60
|
-
return path.join((0, accountRegistry_1.getAccountsDir)(), 'active');
|
|
61
|
-
}
|
|
62
|
-
function getActiveProfilePath(provider) {
|
|
63
|
-
return path.join(getActiveProfilesDir(), `${providerPointerName(provider)}.profile`);
|
|
64
|
-
}
|
|
65
|
-
function atomicWriteFile(filePath, content, mode = 0o600) {
|
|
66
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
67
|
-
const tmp = `${filePath}.tmp`;
|
|
68
|
-
try {
|
|
69
|
-
fs.writeFileSync(tmp, content, { encoding: 'utf8', mode });
|
|
70
|
-
fs.renameSync(tmp, filePath);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
try {
|
|
74
|
-
fs.rmSync(tmp, { force: true });
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
/* nothing to clean up */
|
|
78
|
-
}
|
|
79
|
-
throw err;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function setTerminalActiveProfile(provider, home) {
|
|
83
|
-
const pointerPath = getActiveProfilePath(provider);
|
|
84
|
-
if (home === null) {
|
|
85
|
-
fs.rmSync(pointerPath, { force: true });
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
atomicWriteFile(pointerPath, `${home}\n`);
|
|
89
|
-
}
|
|
90
|
-
function shellQuote(value) {
|
|
91
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
92
|
-
}
|
|
93
|
-
function buildShellHookBlock() {
|
|
94
|
-
const claudePointer = getActiveProfilePath('claude-code');
|
|
95
|
-
const codexPointer = getActiveProfilePath('codex');
|
|
96
|
-
return [
|
|
97
|
-
HOOK_START,
|
|
98
|
-
'sidekick_sync() {',
|
|
99
|
-
` if [ -r ${shellQuote(claudePointer)} ]; then export CLAUDE_CONFIG_DIR="$(cat ${shellQuote(claudePointer)})"; else unset CLAUDE_CONFIG_DIR; fi`,
|
|
100
|
-
` if [ -r ${shellQuote(codexPointer)} ]; then export CODEX_HOME="$(cat ${shellQuote(codexPointer)})"; else unset CODEX_HOME; fi`,
|
|
101
|
-
'}',
|
|
102
|
-
'sidekick_sync >/dev/null 2>&1',
|
|
103
|
-
HOOK_END,
|
|
104
|
-
'',
|
|
105
|
-
].join('\n');
|
|
106
|
-
}
|
|
107
|
-
function getShellRcPaths() {
|
|
108
|
-
const zshrc = path.join(os.homedir(), '.zshrc');
|
|
109
|
-
const bashrc = path.join(os.homedir(), '.bashrc');
|
|
110
|
-
const existing = [zshrc, bashrc].filter((filePath) => fs.existsSync(filePath));
|
|
111
|
-
if (existing.length > 0)
|
|
112
|
-
return existing;
|
|
113
|
-
const shell = path.basename(process.env.SHELL ?? 'zsh');
|
|
114
|
-
return shell === 'bash' ? [bashrc] : [zshrc];
|
|
115
|
-
}
|
|
116
|
-
function resolveShellRcTarget(filePath) {
|
|
117
|
-
if (!fs.existsSync(filePath))
|
|
118
|
-
return { filePath, mode: 0o600 };
|
|
119
|
-
const realPath = fs.realpathSync(filePath);
|
|
120
|
-
return { filePath: realPath, mode: fs.statSync(realPath).mode & 0o777 };
|
|
121
|
-
}
|
|
122
|
-
function stripShellHook(content) {
|
|
123
|
-
const pattern = new RegExp(`${HOOK_START}[\\s\\S]*?${HOOK_END}\\n?`, 'g');
|
|
124
|
-
return content.replace(pattern, '').replace(/\n{3,}/g, '\n\n');
|
|
125
|
-
}
|
|
126
|
-
function installHookInFile(filePath) {
|
|
127
|
-
const target = resolveShellRcTarget(filePath);
|
|
128
|
-
const existing = fs.existsSync(target.filePath) ? fs.readFileSync(target.filePath, 'utf8') : '';
|
|
129
|
-
const cleaned = stripShellHook(existing).replace(/\s*$/, '');
|
|
130
|
-
const next = cleaned ? `${cleaned}\n\n${buildShellHookBlock()}` : buildShellHookBlock();
|
|
131
|
-
atomicWriteFile(target.filePath, next, target.mode);
|
|
132
|
-
}
|
|
133
|
-
function installShellHook() {
|
|
134
|
-
for (const rcPath of getShellRcPaths()) {
|
|
135
|
-
installHookInFile(rcPath);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function uninstallHookInFile(filePath) {
|
|
139
|
-
if (!fs.existsSync(filePath))
|
|
140
|
-
return;
|
|
141
|
-
const target = resolveShellRcTarget(filePath);
|
|
142
|
-
atomicWriteFile(target.filePath, stripShellHook(fs.readFileSync(target.filePath, 'utf8')), target.mode);
|
|
143
|
-
}
|
|
144
|
-
function uninstallShellHook() {
|
|
145
|
-
for (const rcPath of getShellRcPaths()) {
|
|
146
|
-
uninstallHookInFile(rcPath);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function isShellHookInstalled() {
|
|
150
|
-
return getShellRcPaths().some((rcPath) => {
|
|
151
|
-
try {
|
|
152
|
-
const content = fs.readFileSync(rcPath, 'utf8');
|
|
153
|
-
return content.includes(HOOK_START) && content.includes(HOOK_END);
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
50
|
function getLauncherDir() {
|
|
161
51
|
return path.join(os.homedir(), '.local', 'bin');
|
|
162
52
|
}
|
|
@@ -189,6 +79,10 @@ function assertNoLauncherCollision(name, targetPath) {
|
|
|
189
79
|
}
|
|
190
80
|
}
|
|
191
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Write a POSIX launcher script that runs the provider CLI against a profile
|
|
84
|
+
* home. Unix only; on Windows use `sidekick accounts env --shell powershell`.
|
|
85
|
+
*/
|
|
192
86
|
function writeLauncher(name, provider, profileHome) {
|
|
193
87
|
assertValidLauncherName(name);
|
|
194
88
|
const launcherPath = getLauncherPath(name);
|
|
@@ -199,10 +93,11 @@ function writeLauncher(name, provider, profileHome) {
|
|
|
199
93
|
'#!/bin/sh',
|
|
200
94
|
LAUNCHER_MARKER,
|
|
201
95
|
`export ${envVar}=${JSON.stringify(profileHome)}`,
|
|
96
|
+
...(provider === 'claude-code' ? ['unset CLAUDE_SECURESTORAGE_CONFIG_DIR'] : []),
|
|
202
97
|
`exec ${binary} "$@"`,
|
|
203
98
|
'',
|
|
204
99
|
].join('\n');
|
|
205
|
-
|
|
100
|
+
(0, atomic_1.atomicWriteFileSync)(launcherPath, script, 0o755);
|
|
206
101
|
}
|
|
207
102
|
function removeLauncher(name) {
|
|
208
103
|
assertValidLauncherName(name);
|
package/dist/writers/atomic.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare function renameSyncWithRetry(from: string, to: string): void;
|
|
2
|
+
export declare function renameWithRetry(from: string, to: string): Promise<void>;
|
|
1
3
|
export declare function atomicWriteJson(filePath: string, value: unknown): Promise<void>;
|
|
2
4
|
export declare function atomicWriteJsonSync(filePath: string, value: unknown): void;
|
|
3
5
|
/** Raw-string sibling of {@link atomicWriteJsonSync} for non-JSON payloads. */
|
package/dist/writers/atomic.js
CHANGED
|
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.renameSyncWithRetry = renameSyncWithRetry;
|
|
37
|
+
exports.renameWithRetry = renameWithRetry;
|
|
36
38
|
exports.atomicWriteJson = atomicWriteJson;
|
|
37
39
|
exports.atomicWriteJsonSync = atomicWriteJsonSync;
|
|
38
40
|
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
@@ -71,6 +73,51 @@ const LOCK_ABANDONED_MS = 120_000;
|
|
|
71
73
|
const SYNC_LOCK_MAX_WAIT_MS = 15_000;
|
|
72
74
|
const LOCK_WAIT_INTERVAL_MS = 15;
|
|
73
75
|
const lockSleepView = new Int32Array(new SharedArrayBuffer(4));
|
|
76
|
+
/**
|
|
77
|
+
* Windows refuses to replace a file another process holds open (antivirus
|
|
78
|
+
* scanners, an editor extension re-reading `auth.json`) with EPERM/EBUSY/EACCES
|
|
79
|
+
* even though the rename would succeed a few milliseconds later. Retry with a
|
|
80
|
+
* short back-off on that platform only; other platforms surface the error.
|
|
81
|
+
*/
|
|
82
|
+
const WIN32_RENAME_RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
83
|
+
const WIN32_RENAME_MAX_ATTEMPTS = 10;
|
|
84
|
+
function renameRetryDelayMs(attempt) {
|
|
85
|
+
return Math.min(320, 20 * 2 ** attempt);
|
|
86
|
+
}
|
|
87
|
+
function shouldRetryRename(error, attempt) {
|
|
88
|
+
if (process.platform !== 'win32')
|
|
89
|
+
return false;
|
|
90
|
+
if (attempt >= WIN32_RENAME_MAX_ATTEMPTS - 1)
|
|
91
|
+
return false;
|
|
92
|
+
const code = error.code ?? '';
|
|
93
|
+
return WIN32_RENAME_RETRY_CODES.has(code);
|
|
94
|
+
}
|
|
95
|
+
function renameSyncWithRetry(from, to) {
|
|
96
|
+
for (let attempt = 0;; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
fs.renameSync(from, to);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!shouldRetryRename(error, attempt))
|
|
103
|
+
throw error;
|
|
104
|
+
Atomics.wait(lockSleepView, 0, 0, renameRetryDelayMs(attempt));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function renameWithRetry(from, to) {
|
|
109
|
+
for (let attempt = 0;; attempt++) {
|
|
110
|
+
try {
|
|
111
|
+
await fs.promises.rename(from, to);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (!shouldRetryRename(error, attempt))
|
|
116
|
+
throw error;
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, renameRetryDelayMs(attempt)));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
74
121
|
async function syncDirectory(directory) {
|
|
75
122
|
let handle;
|
|
76
123
|
try {
|
|
@@ -112,7 +159,7 @@ async function atomicWriteJson(filePath, value) {
|
|
|
112
159
|
await handle.sync();
|
|
113
160
|
await handle.close();
|
|
114
161
|
handle = undefined;
|
|
115
|
-
await
|
|
162
|
+
await renameWithRetry(tempPath, filePath);
|
|
116
163
|
await syncDirectory(path.dirname(filePath));
|
|
117
164
|
}
|
|
118
165
|
catch (error) {
|
|
@@ -135,7 +182,7 @@ function atomicWriteFileSync(filePath, content, mode = 0o600) {
|
|
|
135
182
|
fs.fsyncSync(descriptor);
|
|
136
183
|
fs.closeSync(descriptor);
|
|
137
184
|
descriptor = undefined;
|
|
138
|
-
|
|
185
|
+
renameSyncWithRetry(tempPath, filePath);
|
|
139
186
|
syncDirectorySync(path.dirname(filePath));
|
|
140
187
|
}
|
|
141
188
|
catch (error) {
|