@sleds/cli 0.1.7 → 0.1.14
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 +5 -2
- package/dist/sleds.mjs +1529 -561
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -1,47 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __returnValue = (v) => v;
|
|
6
|
+
function __exportSetter(name, newValue) {
|
|
7
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
+
}
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
set: __exportSetter.bind(all, name)
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
20
|
|
|
6
|
-
// src/index.ts
|
|
7
|
-
import { realpathSync } from "fs";
|
|
8
|
-
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
|
|
11
|
-
// src/commands/bar.ts
|
|
12
|
-
import { execFileSync } from "node:child_process";
|
|
13
|
-
import {
|
|
14
|
-
chmodSync as chmodSync2,
|
|
15
|
-
existsSync as existsSync5,
|
|
16
|
-
lstatSync,
|
|
17
|
-
mkdirSync as mkdirSync5,
|
|
18
|
-
symlinkSync,
|
|
19
|
-
unlinkSync,
|
|
20
|
-
writeFileSync as writeFileSync5
|
|
21
|
-
} from "node:fs";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
-
import { join as join5, resolve as resolve3 } from "node:path";
|
|
24
|
-
|
|
25
|
-
// src/meter.ts
|
|
26
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27
|
-
import { join as join3 } from "node:path";
|
|
28
|
-
|
|
29
|
-
// src/config.ts
|
|
30
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
31
|
-
import { join as join2, resolve as resolve2 } from "node:path";
|
|
32
|
-
|
|
33
21
|
// src/settings.ts
|
|
34
22
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
35
23
|
import { dirname, join, resolve } from "node:path";
|
|
36
|
-
var BASE_URL_KEY = "ANTHROPIC_BASE_URL"
|
|
37
|
-
var AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN";
|
|
38
|
-
var claudeDir = (home, env = process.env) => {
|
|
24
|
+
var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN", claudeDir = (home, env = process.env) => {
|
|
39
25
|
const o = env.CLAUDE_CONFIG_DIR?.trim();
|
|
40
26
|
return o ? resolve(home, o) : join(home, ".claude");
|
|
41
|
-
}
|
|
42
|
-
var hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim());
|
|
43
|
-
var settingsPath = (home) => join(claudeDir(home), "settings.json");
|
|
44
|
-
var sledsCommand = (sub, home, env = process.env) => {
|
|
27
|
+
}, hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim()), settingsPath = (home) => join(claudeDir(home), "settings.json"), sledsCommand = (sub, home, env = process.env) => {
|
|
45
28
|
const parts = [];
|
|
46
29
|
const sh = env.SLEDS_HOME?.trim();
|
|
47
30
|
const cc = env.CLAUDE_CONFIG_DIR?.trim();
|
|
@@ -51,8 +34,7 @@ var sledsCommand = (sub, home, env = process.env) => {
|
|
|
51
34
|
parts.push(`CLAUDE_CONFIG_DIR=${shellWord(resolve(home, cc))}`);
|
|
52
35
|
parts.push(`sleds ${sub}`);
|
|
53
36
|
return parts.join(" ");
|
|
54
|
-
}
|
|
55
|
-
var sledsCommandsIn = (settings) => {
|
|
37
|
+
}, sledsCommandsIn = (settings) => {
|
|
56
38
|
const out = [];
|
|
57
39
|
const visit = (v) => {
|
|
58
40
|
if (Array.isArray(v)) {
|
|
@@ -70,74 +52,50 @@ var sledsCommandsIn = (settings) => {
|
|
|
70
52
|
visit(settings[STATUS_LINE_KEY]);
|
|
71
53
|
visit(settings.hooks);
|
|
72
54
|
return out;
|
|
73
|
-
}
|
|
74
|
-
var bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1];
|
|
75
|
-
var shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
76
|
-
var backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup");
|
|
77
|
-
var tokenPath = (home, env = process.env) => {
|
|
55
|
+
}, bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1], shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`, backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup"), tokenPath = (home, env = process.env) => {
|
|
78
56
|
const o = env.SLEDS_HOME?.trim();
|
|
79
57
|
return o ? join(resolve(home, o), ".sleds-token") : join(home, ".sleds-token");
|
|
80
|
-
}
|
|
81
|
-
var skillDir = (home) => join(claudeDir(home), "skills", "sled");
|
|
82
|
-
var skillPath = (home) => join(skillDir(home), "SKILL.md");
|
|
83
|
-
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
84
|
-
var envOf = (settings) => {
|
|
58
|
+
}, skillDir = (home) => join(claudeDir(home), "skills", "sled"), skillPath = (home) => join(skillDir(home), "SKILL.md"), isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v), envOf = (settings) => {
|
|
85
59
|
const env = settings.env;
|
|
86
60
|
return isObject(env) ? env : {};
|
|
87
|
-
}
|
|
88
|
-
var applyProxy = (settings, cfg) => ({
|
|
61
|
+
}, applyProxy = (settings, cfg) => ({
|
|
89
62
|
...settings,
|
|
90
63
|
env: { ...envOf(settings), [BASE_URL_KEY]: cfg.url, [AUTH_TOKEN_KEY]: cfg.token }
|
|
91
|
-
})
|
|
92
|
-
var removeProxy = (settings) => {
|
|
64
|
+
}), removeProxy = (settings) => {
|
|
93
65
|
if (!isObject(settings.env))
|
|
94
66
|
return { ...settings };
|
|
95
67
|
const { [BASE_URL_KEY]: _url, [AUTH_TOKEN_KEY]: _token, ...rest } = settings.env;
|
|
96
68
|
return { ...settings, env: rest };
|
|
97
|
-
}
|
|
98
|
-
var readProxy = (settings) => {
|
|
69
|
+
}, readProxy = (settings) => {
|
|
99
70
|
const env = envOf(settings);
|
|
100
71
|
const url = env[BASE_URL_KEY];
|
|
101
72
|
const token = env[AUTH_TOKEN_KEY];
|
|
102
73
|
if (typeof url !== "string" || typeof token !== "string")
|
|
103
74
|
return null;
|
|
104
75
|
return { url, token };
|
|
105
|
-
}
|
|
106
|
-
var STATUS_LINE_KEY = "statusLine";
|
|
107
|
-
var statusLineEntry = (command) => ({ type: "command", command });
|
|
108
|
-
var isSledsStatusLine = (value) => isObject(value) && value.type === "command" && typeof value.command === "string" && /\bstatusline\b/.test(value.command);
|
|
109
|
-
var applyStatusLine = (settings, command) => {
|
|
76
|
+
}, STATUS_LINE_KEY = "statusLine", statusLineEntry = (command) => ({ type: "command", command }), isSledsStatusLine = (value) => isObject(value) && value.type === "command" && typeof value.command === "string" && /\bstatusline\b/.test(value.command), applyStatusLine = (settings, command) => {
|
|
110
77
|
const current = settings[STATUS_LINE_KEY];
|
|
111
78
|
const prior = current === undefined || isSledsStatusLine(current) ? undefined : current;
|
|
112
79
|
return { settings: { ...settings, [STATUS_LINE_KEY]: statusLineEntry(command) }, prior };
|
|
113
|
-
}
|
|
114
|
-
var removeStatusLine = (settings, prior) => {
|
|
80
|
+
}, removeStatusLine = (settings, prior) => {
|
|
115
81
|
if (!isSledsStatusLine(settings[STATUS_LINE_KEY]))
|
|
116
82
|
return { ...settings };
|
|
117
83
|
const { [STATUS_LINE_KEY]: _ours, ...rest } = settings;
|
|
118
84
|
return prior === undefined ? rest : { ...rest, [STATUS_LINE_KEY]: prior };
|
|
119
|
-
}
|
|
120
|
-
var HOOKS_KEY = "hooks";
|
|
121
|
-
var NOTIFICATION_EVENT = "Notification";
|
|
122
|
-
var SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled";
|
|
123
|
-
var SEAT_EVENT_TIMEOUT_S = 10;
|
|
124
|
-
var isSledsSeatHook = (entry) => {
|
|
85
|
+
}, HOOKS_KEY = "hooks", NOTIFICATION_EVENT = "Notification", SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled", SEAT_EVENT_TIMEOUT_S = 10, isSledsSeatHook = (entry) => {
|
|
125
86
|
if (!isObject(entry) || !Array.isArray(entry.hooks))
|
|
126
87
|
return false;
|
|
127
88
|
return entry.hooks.some((h) => isObject(h) && h.type === "command" && typeof h.command === "string" && /\bseat-event\b/.test(h.command));
|
|
128
|
-
}
|
|
129
|
-
var seatHookEntry = (command) => ({
|
|
89
|
+
}, seatHookEntry = (command) => ({
|
|
130
90
|
matcher: SEAT_EVENT_MATCHER,
|
|
131
91
|
hooks: [{ type: "command", command, timeout: SEAT_EVENT_TIMEOUT_S }]
|
|
132
|
-
})
|
|
133
|
-
var notificationHooks = (settings) => {
|
|
92
|
+
}), notificationHooks = (settings) => {
|
|
134
93
|
const hooks = settings[HOOKS_KEY];
|
|
135
94
|
if (!isObject(hooks))
|
|
136
95
|
return [];
|
|
137
96
|
const list = hooks[NOTIFICATION_EVENT];
|
|
138
97
|
return Array.isArray(list) ? list : [];
|
|
139
|
-
}
|
|
140
|
-
var withNotificationHooks = (settings, list) => {
|
|
98
|
+
}, withNotificationHooks = (settings, list) => {
|
|
141
99
|
const hooks = isObject(settings[HOOKS_KEY]) ? settings[HOOKS_KEY] : {};
|
|
142
100
|
if (list.length === 0) {
|
|
143
101
|
const { [NOTIFICATION_EVENT]: _gone, ...restHooks } = hooks;
|
|
@@ -148,41 +106,31 @@ var withNotificationHooks = (settings, list) => {
|
|
|
148
106
|
return { ...settings, [HOOKS_KEY]: restHooks };
|
|
149
107
|
}
|
|
150
108
|
return { ...settings, [HOOKS_KEY]: { ...hooks, [NOTIFICATION_EVENT]: list } };
|
|
151
|
-
}
|
|
152
|
-
var applySeatHook = (settings, command) => {
|
|
109
|
+
}, applySeatHook = (settings, command) => {
|
|
153
110
|
const list = notificationHooks(settings);
|
|
154
111
|
const idx = list.findIndex(isSledsSeatHook);
|
|
155
112
|
const entry = seatHookEntry(command);
|
|
156
113
|
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
157
114
|
return withNotificationHooks(settings, next);
|
|
158
|
-
}
|
|
159
|
-
var removeSeatHook = (settings) => {
|
|
115
|
+
}, removeSeatHook = (settings) => {
|
|
160
116
|
const list = notificationHooks(settings);
|
|
161
117
|
if (!list.some(isSledsSeatHook))
|
|
162
118
|
return { ...settings };
|
|
163
119
|
return withNotificationHooks(settings, list.filter((e) => !isSledsSeatHook(e)));
|
|
164
|
-
}
|
|
165
|
-
var hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook);
|
|
166
|
-
var POST_TOOL_EVENT = "PostToolUse";
|
|
167
|
-
var SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit";
|
|
168
|
-
var SPEC_EVENT_TIMEOUT_S = 5;
|
|
169
|
-
var isSledsSpecHook = (entry) => {
|
|
120
|
+
}, hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook), POST_TOOL_EVENT = "PostToolUse", SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit", SPEC_EVENT_TIMEOUT_S = 5, isSledsSpecHook = (entry) => {
|
|
170
121
|
if (!isObject(entry) || !Array.isArray(entry.hooks))
|
|
171
122
|
return false;
|
|
172
123
|
return entry.hooks.some((h) => isObject(h) && h.type === "command" && typeof h.command === "string" && /\bspec-event\b/.test(h.command));
|
|
173
|
-
}
|
|
174
|
-
var specHookEntry = (command) => ({
|
|
124
|
+
}, specHookEntry = (command) => ({
|
|
175
125
|
matcher: SPEC_EVENT_MATCHER,
|
|
176
126
|
hooks: [{ type: "command", command, timeout: SPEC_EVENT_TIMEOUT_S }]
|
|
177
|
-
})
|
|
178
|
-
var eventHooks = (settings, event) => {
|
|
127
|
+
}), eventHooks = (settings, event) => {
|
|
179
128
|
const hooks = settings[HOOKS_KEY];
|
|
180
129
|
if (!isObject(hooks))
|
|
181
130
|
return [];
|
|
182
131
|
const list = hooks[event];
|
|
183
132
|
return Array.isArray(list) ? list : [];
|
|
184
|
-
}
|
|
185
|
-
var withEventHooks = (settings, event, list) => {
|
|
133
|
+
}, withEventHooks = (settings, event, list) => {
|
|
186
134
|
const hooks = isObject(settings[HOOKS_KEY]) ? settings[HOOKS_KEY] : {};
|
|
187
135
|
if (list.length === 0) {
|
|
188
136
|
const { [event]: _gone, ...restHooks } = hooks;
|
|
@@ -193,27 +141,19 @@ var withEventHooks = (settings, event, list) => {
|
|
|
193
141
|
return { ...settings, [HOOKS_KEY]: restHooks };
|
|
194
142
|
}
|
|
195
143
|
return { ...settings, [HOOKS_KEY]: { ...hooks, [event]: list } };
|
|
196
|
-
}
|
|
197
|
-
var applySpecHook = (settings, command) => {
|
|
144
|
+
}, applySpecHook = (settings, command) => {
|
|
198
145
|
const list = eventHooks(settings, POST_TOOL_EVENT);
|
|
199
146
|
const idx = list.findIndex(isSledsSpecHook);
|
|
200
147
|
const entry = specHookEntry(command);
|
|
201
148
|
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
202
149
|
return withEventHooks(settings, POST_TOOL_EVENT, next);
|
|
203
|
-
}
|
|
204
|
-
var removeSpecHook = (settings) => {
|
|
150
|
+
}, removeSpecHook = (settings) => {
|
|
205
151
|
const list = eventHooks(settings, POST_TOOL_EVENT);
|
|
206
152
|
if (!list.some(isSledsSpecHook))
|
|
207
153
|
return { ...settings };
|
|
208
154
|
return withEventHooks(settings, POST_TOOL_EVENT, list.filter((e) => !isSledsSpecHook(e)));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
var isSeatToken = (token) => /^sled_[A-Za-z0-9_-]+$/.test(token);
|
|
212
|
-
var maskToken = (token) => token.length <= 9 ? `${token.slice(0, 5)}…` : `${token.slice(0, 9)}…`;
|
|
213
|
-
var serialize = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
214
|
-
`;
|
|
215
|
-
var hasClaudeCode = (home) => existsSync(claudeDir(home));
|
|
216
|
-
var readSettings = (home) => {
|
|
155
|
+
}, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook), isSeatToken = (token) => /^sled_[A-Za-z0-9_-]+$/.test(token), maskToken = (token) => token.length <= 9 ? `${token.slice(0, 5)}…` : `${token.slice(0, 9)}…`, serialize = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
156
|
+
`, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
|
|
217
157
|
const p = settingsPath(home);
|
|
218
158
|
if (!existsSync(p))
|
|
219
159
|
return {};
|
|
@@ -221,44 +161,39 @@ var readSettings = (home) => {
|
|
|
221
161
|
if (!isObject(parsed))
|
|
222
162
|
throw new Error(`${p} is not a JSON object`);
|
|
223
163
|
return parsed;
|
|
224
|
-
}
|
|
225
|
-
var writeSettings = (home, settings) => {
|
|
164
|
+
}, writeSettings = (home, settings) => {
|
|
226
165
|
mkdirSync(claudeDir(home), { recursive: true });
|
|
227
166
|
const target = settingsPath(home);
|
|
228
167
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
229
168
|
writeFileSync(tmp, serialize(settings));
|
|
230
169
|
renameSync(tmp, target);
|
|
231
|
-
}
|
|
232
|
-
var backupSettings = (home) => {
|
|
170
|
+
}, backupSettings = (home) => {
|
|
233
171
|
const src = settingsPath(home);
|
|
234
172
|
const dst = backupPath(home);
|
|
235
173
|
if (!existsSync(src) || existsSync(dst))
|
|
236
174
|
return false;
|
|
237
175
|
writeFileSync(dst, readFileSync(src));
|
|
238
176
|
return true;
|
|
239
|
-
}
|
|
240
|
-
var readTokenFile = (home) => {
|
|
177
|
+
}, readTokenFile = (home) => {
|
|
241
178
|
const p = tokenPath(home);
|
|
242
179
|
if (!existsSync(p))
|
|
243
180
|
return null;
|
|
244
181
|
const t = readFileSync(p, "utf8").trim();
|
|
245
182
|
return t.length > 0 ? t : null;
|
|
246
|
-
}
|
|
247
|
-
var writeTokenFile = (home, token) => {
|
|
183
|
+
}, writeTokenFile = (home, token) => {
|
|
248
184
|
mkdirSync(dirname(tokenPath(home)), { recursive: true, mode: 448 });
|
|
249
185
|
writeFileSync(tokenPath(home), `${token}
|
|
250
186
|
`, { mode: 384 });
|
|
251
187
|
};
|
|
188
|
+
var init_settings = () => {};
|
|
252
189
|
|
|
253
190
|
// src/config.ts
|
|
191
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
192
|
+
import { join as join2, resolve as resolve2 } from "node:path";
|
|
254
193
|
var configDir = (home, env = process.env) => {
|
|
255
194
|
const o = env.SLEDS_HOME?.trim();
|
|
256
195
|
return o ? resolve2(home, o) : join2(home, ".config", "sleds");
|
|
257
|
-
}
|
|
258
|
-
var configPath = (home) => join2(configDir(home), "config.json");
|
|
259
|
-
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
260
|
-
var optString = (v) => typeof v === "string" ? v : undefined;
|
|
261
|
-
var parsePriorEnv = (v) => {
|
|
196
|
+
}, configPath = (home) => join2(configDir(home), "config.json"), isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), optString = (v) => typeof v === "string" ? v : undefined, parsePriorEnv = (v) => {
|
|
262
197
|
if (!isObject2(v))
|
|
263
198
|
return;
|
|
264
199
|
const out = {};
|
|
@@ -271,9 +206,7 @@ var parsePriorEnv = (v) => {
|
|
|
271
206
|
if (typeof v.hadEnv === "boolean")
|
|
272
207
|
out.hadEnv = v.hadEnv;
|
|
273
208
|
return out;
|
|
274
|
-
}
|
|
275
|
-
var parseSchedule = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined;
|
|
276
|
-
var parseConfig = (raw) => {
|
|
209
|
+
}, parseSchedule = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined, parseConfig = (raw) => {
|
|
277
210
|
if (!isObject2(raw))
|
|
278
211
|
return {};
|
|
279
212
|
const cfg = {};
|
|
@@ -305,15 +238,58 @@ var parseConfig = (raw) => {
|
|
|
305
238
|
cfg.schedule = schedule;
|
|
306
239
|
if (raw.priorStatusLine !== undefined)
|
|
307
240
|
cfg.priorStatusLine = raw.priorStatusLine;
|
|
241
|
+
if (raw.wiring === "seat-first" || raw.wiring === "proxy-always")
|
|
242
|
+
cfg.wiring = raw.wiring;
|
|
243
|
+
const org = parseOrgInfo(raw.org);
|
|
244
|
+
if (org)
|
|
245
|
+
cfg.org = org;
|
|
246
|
+
const musher = parseMusher(raw.musher);
|
|
247
|
+
if (musher)
|
|
248
|
+
cfg.musher = musher;
|
|
308
249
|
return cfg;
|
|
309
|
-
}
|
|
310
|
-
|
|
250
|
+
}, parseMusher = (raw) => {
|
|
251
|
+
if (!isObject2(raw))
|
|
252
|
+
return;
|
|
253
|
+
const out = {};
|
|
254
|
+
const pm = optString(raw.permissionMode);
|
|
255
|
+
const cp = optString(raw.claudePath);
|
|
256
|
+
if (pm)
|
|
257
|
+
out.permissionMode = pm;
|
|
258
|
+
if (cp)
|
|
259
|
+
out.claudePath = cp;
|
|
260
|
+
if (typeof raw.timeoutMinutes === "number" && raw.timeoutMinutes > 0)
|
|
261
|
+
out.timeoutMinutes = raw.timeoutMinutes;
|
|
262
|
+
if (raw.agent === "launchd" || raw.agent === "none")
|
|
263
|
+
out.agent = raw.agent;
|
|
264
|
+
return out;
|
|
265
|
+
}, parseOrgInfo = (raw) => {
|
|
266
|
+
if (!isObject2(raw) || !isObject2(raw.capabilities))
|
|
267
|
+
return;
|
|
268
|
+
const name = optString(raw.name);
|
|
269
|
+
const slug = optString(raw.slug);
|
|
270
|
+
const plan = raw.plan;
|
|
271
|
+
const c = raw.capabilities;
|
|
272
|
+
const src = c.upstreamSource;
|
|
273
|
+
if (name === undefined || slug === undefined || plan !== "team" && plan !== "enterprise" && plan !== "api" && plan !== "individual" || src !== "own" && src !== "shared" && src !== "none")
|
|
274
|
+
return;
|
|
275
|
+
return {
|
|
276
|
+
name,
|
|
277
|
+
slug,
|
|
278
|
+
plan,
|
|
279
|
+
capabilities: {
|
|
280
|
+
upstreamKey: c.upstreamKey === true,
|
|
281
|
+
adminKey: c.adminKey === true,
|
|
282
|
+
seats: c.seats !== false,
|
|
283
|
+
upstreamSource: src
|
|
284
|
+
},
|
|
285
|
+
mode: raw.mode === "proxy-always" ? "proxy-always" : "seat-first"
|
|
286
|
+
};
|
|
287
|
+
}, readConfig = (home) => {
|
|
311
288
|
const p = configPath(home);
|
|
312
289
|
if (!existsSync2(p))
|
|
313
290
|
return {};
|
|
314
291
|
return parseConfig(JSON.parse(readFileSync2(p, "utf8")));
|
|
315
|
-
}
|
|
316
|
-
var writeConfig = (home, cfg) => {
|
|
292
|
+
}, writeConfig = (home, cfg) => {
|
|
317
293
|
const dir = configDir(home);
|
|
318
294
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
319
295
|
const target = configPath(home);
|
|
@@ -323,14 +299,14 @@ var writeConfig = (home, cfg) => {
|
|
|
323
299
|
renameSync2(tmp, target);
|
|
324
300
|
chmodSync(target, 384);
|
|
325
301
|
};
|
|
302
|
+
var init_config = __esm(() => {
|
|
303
|
+
init_settings();
|
|
304
|
+
});
|
|
326
305
|
|
|
327
306
|
// src/meter.ts
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v)
|
|
331
|
-
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
332
|
-
var str = (v) => typeof v === "string" ? v : undefined;
|
|
333
|
-
var parseMeter = (raw) => {
|
|
307
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
308
|
+
import { join as join3 } from "node:path";
|
|
309
|
+
var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, str = (v) => typeof v === "string" ? v : undefined, parseMeter = (raw) => {
|
|
334
310
|
if (!isObject3(raw))
|
|
335
311
|
return null;
|
|
336
312
|
const seat = isObject3(raw.seat) ? str(raw.seat.email) : undefined;
|
|
@@ -385,18 +361,15 @@ var parseMeter = (raw) => {
|
|
|
385
361
|
if (acc !== undefined)
|
|
386
362
|
out.estimateAccuracyPct = acc;
|
|
387
363
|
return out;
|
|
388
|
-
}
|
|
389
|
-
var usd = (n) => {
|
|
364
|
+
}, usd = (n) => {
|
|
390
365
|
const abs = Math.abs(n);
|
|
391
366
|
if (abs >= 1000)
|
|
392
367
|
return `${n < 0 ? "-" : ""}$${Math.round(abs).toLocaleString("en-US")}`;
|
|
393
368
|
return `${n < 0 ? "-" : ""}$${abs.toFixed(2)}`;
|
|
394
|
-
}
|
|
395
|
-
var usdRange = (low, high) => {
|
|
369
|
+
}, usdRange = (low, high) => {
|
|
396
370
|
const d = high >= 10 ? 0 : 2;
|
|
397
371
|
return `$${low.toFixed(d)}–${high.toFixed(d)}`;
|
|
398
|
-
}
|
|
399
|
-
var shortDuration = (ms) => {
|
|
372
|
+
}, shortDuration = (ms) => {
|
|
400
373
|
const total = Math.max(0, Math.round(ms / 60000));
|
|
401
374
|
const d = Math.floor(total / (24 * 60));
|
|
402
375
|
const h = Math.floor(total % (24 * 60) / 60);
|
|
@@ -406,18 +379,7 @@ var shortDuration = (ms) => {
|
|
|
406
379
|
if (h > 0)
|
|
407
380
|
return `${h}h${String(m).padStart(2, "0")}m`;
|
|
408
381
|
return `${m}m`;
|
|
409
|
-
}
|
|
410
|
-
var KIND_WORDS = {
|
|
411
|
-
rate_limit: "rate limit hit",
|
|
412
|
-
weekly_cap: "usage limit reached",
|
|
413
|
-
budget: "budget exhausted",
|
|
414
|
-
provider_down: "provider unavailable"
|
|
415
|
-
};
|
|
416
|
-
var kindInWords = (kind) => KIND_WORDS[kind] ?? kind.replace(/_/g, " ");
|
|
417
|
-
var SEP = " · ";
|
|
418
|
-
var PREFIX = "sleds ";
|
|
419
|
-
var OFFLINE_LINE = `${PREFIX}offline`;
|
|
420
|
-
var jobParts = (m) => {
|
|
382
|
+
}, KIND_WORDS, kindInWords = (kind) => KIND_WORDS[kind] ?? kind.replace(/_/g, " "), SEP = " · ", PREFIX = "sleds ", OFFLINE_LINE, jobParts = (m) => {
|
|
421
383
|
const parts = [];
|
|
422
384
|
const [low, high] = m.jobs.queuedEstimateUsd;
|
|
423
385
|
if (m.jobs.queued > 0) {
|
|
@@ -429,8 +391,7 @@ var jobParts = (m) => {
|
|
|
429
391
|
if (m.jobs.deliveredToday > 0)
|
|
430
392
|
parts.push(`${m.jobs.deliveredToday} delivered`);
|
|
431
393
|
return parts;
|
|
432
|
-
}
|
|
433
|
-
var formatMeterLine = (m, now, seatFragment = null) => {
|
|
394
|
+
}, formatMeterLine = (m, now, seatFragment = null) => {
|
|
434
395
|
if (!m)
|
|
435
396
|
return seatFragment ? `${OFFLINE_LINE}${SEP}${seatFragment}` : OFFLINE_LINE;
|
|
436
397
|
const parts = [`${usd(m.today.spentUsd)} today`];
|
|
@@ -445,10 +406,7 @@ var formatMeterLine = (m, now, seatFragment = null) => {
|
|
|
445
406
|
parts.push(`allowance ${usd(m.allowance.remainingUsd)}`);
|
|
446
407
|
parts.push(...jobParts(m));
|
|
447
408
|
return `${PREFIX}${parts.join(SEP)}`;
|
|
448
|
-
}
|
|
449
|
-
var row = (label, value) => `${label.padEnd(12)}${value}`;
|
|
450
|
-
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
451
|
-
var formatMeter = (m, now) => {
|
|
409
|
+
}, row = (label, value) => `${label.padEnd(12)}${value}`, plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`, formatMeter = (m, now) => {
|
|
452
410
|
const lines = [row("Seat:", m.seat.email)];
|
|
453
411
|
lines.push(row("Built today:", `${usd(m.today.spentUsd)}${SEP}${plural(m.today.requests, "request")}`));
|
|
454
412
|
lines.push(row("Month:", m.month.ceilingUsd !== undefined ? `${usd(m.month.spentUsd)} of ${usd(m.month.ceilingUsd)} ceiling` : usd(m.month.spentUsd)));
|
|
@@ -472,9 +430,7 @@ var formatMeter = (m, now) => {
|
|
|
472
430
|
if (m.estimateAccuracyPct !== undefined)
|
|
473
431
|
lines.push(row("Estimates:", `${Math.round(m.estimateAccuracyPct)}% landed in band`));
|
|
474
432
|
return lines;
|
|
475
|
-
}
|
|
476
|
-
var meterCachePath = (home) => join3(configDir(home), "meter.json");
|
|
477
|
-
var readMeterCache = (home, now) => {
|
|
433
|
+
}, meterCachePath = (home) => join3(configDir(home), "meter.json"), readMeterCache = (home, now) => {
|
|
478
434
|
const p = meterCachePath(home);
|
|
479
435
|
if (!existsSync3(p))
|
|
480
436
|
return null;
|
|
@@ -489,8 +445,7 @@ var readMeterCache = (home, now) => {
|
|
|
489
445
|
} catch {
|
|
490
446
|
return null;
|
|
491
447
|
}
|
|
492
|
-
}
|
|
493
|
-
var writeMeterCache = (home, meter, now) => {
|
|
448
|
+
}, writeMeterCache = (home, meter, now) => {
|
|
494
449
|
const dir = configDir(home);
|
|
495
450
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
496
451
|
const target = meterCachePath(home);
|
|
@@ -499,8 +454,7 @@ var writeMeterCache = (home, meter, now) => {
|
|
|
499
454
|
writeFileSync3(tmp, `${JSON.stringify(file)}
|
|
500
455
|
`, { mode: 384 });
|
|
501
456
|
renameSync3(tmp, target);
|
|
502
|
-
}
|
|
503
|
-
var fetchMeter = async (o) => {
|
|
457
|
+
}, fetchMeter = async (o) => {
|
|
504
458
|
const fetchFn = o.fetchFn ?? fetch;
|
|
505
459
|
try {
|
|
506
460
|
const res = await fetchFn(`${o.proxyUrl}/proxy/meter`, {
|
|
@@ -513,16 +467,14 @@ var fetchMeter = async (o) => {
|
|
|
513
467
|
} catch {
|
|
514
468
|
return null;
|
|
515
469
|
}
|
|
516
|
-
}
|
|
517
|
-
var meterSource = (home) => {
|
|
470
|
+
}, meterSource = (home) => {
|
|
518
471
|
const cfg = readConfig(home);
|
|
519
472
|
const settings = existsSync3(settingsPath(home)) ? readSettings(home) : {};
|
|
520
473
|
const inSettings = readProxy(settings);
|
|
521
474
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
522
475
|
const token = inSettings?.token ?? cfg.token;
|
|
523
476
|
return proxyUrl && token ? { proxyUrl, token } : null;
|
|
524
|
-
}
|
|
525
|
-
var loadMeter = async (o) => {
|
|
477
|
+
}, loadMeter = async (o) => {
|
|
526
478
|
const now = o.now ?? Date.now();
|
|
527
479
|
const cached = readMeterCache(o.home, now);
|
|
528
480
|
if (cached)
|
|
@@ -538,99 +490,548 @@ var loadMeter = async (o) => {
|
|
|
538
490
|
}
|
|
539
491
|
return fresh;
|
|
540
492
|
};
|
|
493
|
+
var init_meter = __esm(() => {
|
|
494
|
+
init_config();
|
|
495
|
+
init_settings();
|
|
496
|
+
KIND_WORDS = {
|
|
497
|
+
rate_limit: "rate limit hit",
|
|
498
|
+
weekly_cap: "usage limit reached",
|
|
499
|
+
budget: "budget exhausted",
|
|
500
|
+
provider_down: "provider unavailable"
|
|
501
|
+
};
|
|
502
|
+
OFFLINE_LINE = `${PREFIX}offline`;
|
|
503
|
+
});
|
|
541
504
|
|
|
542
|
-
// src/
|
|
543
|
-
var
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
505
|
+
// src/seat.ts
|
|
506
|
+
var exports_seat = {};
|
|
507
|
+
__export(exports_seat, {
|
|
508
|
+
SEAT_POST_MIN_DELTA_PCT: () => SEAT_POST_MIN_DELTA_PCT2,
|
|
509
|
+
SEAT_POST_MIN_INTERVAL_MS: () => SEAT_POST_MIN_INTERVAL_MS2,
|
|
510
|
+
SEAT_POST_TIMEOUT_MS: () => SEAT_POST_TIMEOUT_MS2,
|
|
511
|
+
SEAT_WALL_LINE: () => SEAT_WALL_LINE2,
|
|
512
|
+
SEAT_WALL_LINE_NO_KEY: () => SEAT_WALL_LINE_NO_KEY2,
|
|
513
|
+
STDIN_TIMEOUT_MS: () => STDIN_TIMEOUT_MS2,
|
|
514
|
+
bindingResetAt: () => bindingResetAt2,
|
|
515
|
+
formatSeatFragment: () => formatSeatFragment2,
|
|
516
|
+
markPosted: () => markPosted2,
|
|
517
|
+
parseResetsAt: () => parseResetsAt2,
|
|
518
|
+
parseSeatState: () => parseSeatState2,
|
|
519
|
+
parseSeatWindow: () => parseSeatWindow2,
|
|
520
|
+
postSeatWall: () => postSeatWall2,
|
|
521
|
+
postSeatWindow: () => postSeatWindow2,
|
|
522
|
+
readSeatState: () => readSeatState2,
|
|
523
|
+
readStdin: () => readStdin2,
|
|
524
|
+
seatSessionFromStdin: () => seatSessionFromStdin2,
|
|
525
|
+
seatStatePath: () => seatStatePath2,
|
|
526
|
+
seatWallDue: () => seatWallDue2,
|
|
527
|
+
seatWallLine: () => seatWallLine2,
|
|
528
|
+
seatWindowBody: () => seatWindowBody2,
|
|
529
|
+
seatWindowFromStdin: () => seatWindowFromStdin2,
|
|
530
|
+
shouldPostSeat: () => shouldPostSeat2,
|
|
531
|
+
writeSeatState: () => writeSeatState2
|
|
532
|
+
});
|
|
533
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync5, writeFileSync as writeFileSync10 } from "node:fs";
|
|
534
|
+
import { join as join10 } from "node:path";
|
|
535
|
+
var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_MS2 = 1500, STDIN_TIMEOUT_MS2 = 300, isObject9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num5 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, EPOCH_MS_THRESHOLD2 = 100000000000, parseResetsAt2 = (v) => {
|
|
536
|
+
const n = num5(v);
|
|
537
|
+
if (n !== undefined) {
|
|
538
|
+
if (n <= 0)
|
|
539
|
+
return;
|
|
540
|
+
return n >= EPOCH_MS_THRESHOLD2 ? Math.round(n) : Math.round(n * 1000);
|
|
541
|
+
}
|
|
542
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
543
|
+
const parsed = Date.parse(v);
|
|
544
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}, parseBar2 = (v) => {
|
|
548
|
+
if (!isObject9(v))
|
|
549
|
+
return;
|
|
550
|
+
const usedPct = num5(v.used_percentage) ?? num5(v.usedPct);
|
|
551
|
+
const resetsAt = parseResetsAt2(v.resets_at ?? v.resetsAt);
|
|
552
|
+
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
553
|
+
return;
|
|
554
|
+
return { usedPct, resetsAt };
|
|
555
|
+
}, parseSeatWindow2 = (raw, capturedAt) => {
|
|
556
|
+
if (!isObject9(raw))
|
|
552
557
|
return null;
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
558
|
+
const limits = isObject9(raw.rate_limits) ? raw.rate_limits : raw;
|
|
559
|
+
const fiveHour = parseBar2(limits.five_hour ?? limits.fiveHour);
|
|
560
|
+
const sevenDay = parseBar2(limits.seven_day ?? limits.sevenDay);
|
|
561
|
+
const spendLimit = parseBar2(limits.spend_limit ?? limits.spendLimit);
|
|
562
|
+
const models = {};
|
|
563
|
+
if (isObject9(limits.models))
|
|
564
|
+
for (const [k, v] of Object.entries(limits.models)) {
|
|
565
|
+
const bar = parseBar2(v);
|
|
566
|
+
if (bar)
|
|
567
|
+
models[k] = bar;
|
|
568
|
+
}
|
|
569
|
+
const usedCreditsUsd = num5(limits.usedCreditsUsd);
|
|
570
|
+
const hasModels = Object.keys(models).length > 0;
|
|
571
|
+
if (!fiveHour && !sevenDay && !spendLimit && !hasModels && usedCreditsUsd === undefined)
|
|
572
|
+
return null;
|
|
573
|
+
const out = { capturedAt };
|
|
574
|
+
if (fiveHour)
|
|
575
|
+
out.fiveHour = fiveHour;
|
|
576
|
+
if (sevenDay)
|
|
577
|
+
out.sevenDay = sevenDay;
|
|
578
|
+
if (spendLimit)
|
|
579
|
+
out.spendLimit = spendLimit;
|
|
580
|
+
if (hasModels)
|
|
581
|
+
out.models = models;
|
|
582
|
+
if (usedCreditsUsd !== undefined)
|
|
583
|
+
out.usedCreditsUsd = usedCreditsUsd;
|
|
584
|
+
return out;
|
|
585
|
+
}, seatWindowFromStdin2 = (text, capturedAt) => {
|
|
586
|
+
const trimmed = text.trim();
|
|
587
|
+
if (!trimmed)
|
|
588
|
+
return null;
|
|
589
|
+
try {
|
|
590
|
+
return parseSeatWindow2(JSON.parse(trimmed), capturedAt);
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}, formatSeatFragment2 = (w) => {
|
|
557
595
|
if (!w)
|
|
558
|
-
return
|
|
559
|
-
const
|
|
596
|
+
return null;
|
|
597
|
+
const parts = [];
|
|
560
598
|
if (w.sevenDay)
|
|
561
|
-
|
|
599
|
+
parts.push(`wk ${Math.round(w.sevenDay.usedPct)}%`);
|
|
562
600
|
if (w.fiveHour)
|
|
563
|
-
|
|
601
|
+
parts.push(`5h ${Math.round(w.fiveHour.usedPct)}%`);
|
|
564
602
|
if (w.spendLimit)
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
603
|
+
parts.push(`spend ${Math.round(w.spendLimit.usedPct)}%`);
|
|
604
|
+
if (parts.length === 0)
|
|
605
|
+
return null;
|
|
606
|
+
return `seat ${parts.join(SEP)}`;
|
|
607
|
+
}, seatStatePath2 = (home) => join10(configDir(home), "seat.json"), parseWindowStored2 = (v) => {
|
|
608
|
+
if (!isObject9(v))
|
|
609
|
+
return;
|
|
610
|
+
const capturedAt = num5(v.capturedAt);
|
|
611
|
+
if (capturedAt === undefined)
|
|
612
|
+
return;
|
|
613
|
+
return parseSeatWindow2(v, capturedAt) ?? undefined;
|
|
614
|
+
}, parseSeatState2 = (raw) => {
|
|
615
|
+
if (!isObject9(raw))
|
|
616
|
+
return {};
|
|
617
|
+
const out = {};
|
|
618
|
+
const window = parseWindowStored2(raw.window);
|
|
619
|
+
if (window)
|
|
620
|
+
out.window = window;
|
|
621
|
+
const lastPostedAt = num5(raw.lastPostedAt);
|
|
622
|
+
if (lastPostedAt !== undefined)
|
|
623
|
+
out.lastPostedAt = lastPostedAt;
|
|
624
|
+
if (isObject9(raw.lastPosted)) {
|
|
625
|
+
const lp = {};
|
|
626
|
+
const f = num5(raw.lastPosted.fiveHour);
|
|
627
|
+
const s = num5(raw.lastPosted.sevenDay);
|
|
628
|
+
const sp = num5(raw.lastPosted.spendLimit);
|
|
629
|
+
if (f !== undefined)
|
|
630
|
+
lp.fiveHour = f;
|
|
631
|
+
if (s !== undefined)
|
|
632
|
+
lp.sevenDay = s;
|
|
633
|
+
if (sp !== undefined)
|
|
634
|
+
lp.spendLimit = sp;
|
|
635
|
+
out.lastPosted = lp;
|
|
570
636
|
}
|
|
637
|
+
const lastWallResetsAt = num5(raw.lastWallResetsAt);
|
|
638
|
+
if (lastWallResetsAt !== undefined)
|
|
639
|
+
out.lastWallResetsAt = lastWallResetsAt;
|
|
640
|
+
const session = parseSeatSession2(raw.session);
|
|
641
|
+
if (session)
|
|
642
|
+
out.session = session;
|
|
643
|
+
return out;
|
|
644
|
+
}, parseSeatSession2 = (v) => {
|
|
645
|
+
if (!isObject9(v) || typeof v.id !== "string" || !v.id)
|
|
646
|
+
return;
|
|
647
|
+
const at = num5(v.at);
|
|
648
|
+
if (at === undefined)
|
|
649
|
+
return;
|
|
650
|
+
const out = { id: v.id, at };
|
|
651
|
+
if (typeof v.cwd === "string" && v.cwd)
|
|
652
|
+
out.cwd = v.cwd;
|
|
653
|
+
if (typeof v.model === "string" && v.model)
|
|
654
|
+
out.model = v.model;
|
|
571
655
|
return out;
|
|
656
|
+
}, seatSessionFromStdin2 = (text, now) => {
|
|
657
|
+
const trimmed = text.trim();
|
|
658
|
+
if (!trimmed)
|
|
659
|
+
return null;
|
|
660
|
+
try {
|
|
661
|
+
const raw = JSON.parse(trimmed);
|
|
662
|
+
if (!isObject9(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
663
|
+
return null;
|
|
664
|
+
const out = { id: raw.session_id, at: now };
|
|
665
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
666
|
+
out.cwd = raw.cwd;
|
|
667
|
+
const model = isObject9(raw.model) ? raw.model.id : raw.model;
|
|
668
|
+
if (typeof model === "string" && model)
|
|
669
|
+
out.model = model;
|
|
670
|
+
return out;
|
|
671
|
+
} catch {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}, bindingResetAt2 = (w, now) => {
|
|
675
|
+
if (!w)
|
|
676
|
+
return;
|
|
677
|
+
const bars = [
|
|
678
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
679
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
680
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
681
|
+
...Object.values(w.models ?? {})
|
|
682
|
+
].filter((b) => b.resetsAt > now);
|
|
683
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
684
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
685
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
686
|
+
}, readSeatState2 = (home) => {
|
|
687
|
+
const p = seatStatePath2(home);
|
|
688
|
+
if (!existsSync13(p))
|
|
689
|
+
return {};
|
|
690
|
+
try {
|
|
691
|
+
return parseSeatState2(JSON.parse(readFileSync10(p, "utf8")));
|
|
692
|
+
} catch {
|
|
693
|
+
return {};
|
|
694
|
+
}
|
|
695
|
+
}, writeSeatState2 = (home, state) => {
|
|
696
|
+
const dir = configDir(home);
|
|
697
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
698
|
+
const target = seatStatePath2(home);
|
|
699
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
700
|
+
writeFileSync10(tmp, `${JSON.stringify(state)}
|
|
701
|
+
`, { mode: 384 });
|
|
702
|
+
renameSync5(tmp, target);
|
|
703
|
+
}, pctOf2 = (b) => b?.usedPct, shouldPostSeat2 = (state, next, now) => {
|
|
704
|
+
if (state.lastPostedAt === undefined || !state.lastPosted)
|
|
705
|
+
return true;
|
|
706
|
+
if (now - state.lastPostedAt >= SEAT_POST_MIN_INTERVAL_MS2)
|
|
707
|
+
return true;
|
|
708
|
+
const pairs = [
|
|
709
|
+
[state.lastPosted.fiveHour, pctOf2(next.fiveHour)],
|
|
710
|
+
[state.lastPosted.sevenDay, pctOf2(next.sevenDay)],
|
|
711
|
+
[state.lastPosted.spendLimit, pctOf2(next.spendLimit)]
|
|
712
|
+
];
|
|
713
|
+
for (const [prev, cur] of pairs) {
|
|
714
|
+
if (prev === undefined && cur === undefined)
|
|
715
|
+
continue;
|
|
716
|
+
if (prev === undefined || cur === undefined)
|
|
717
|
+
return true;
|
|
718
|
+
if (Math.abs(cur - prev) >= SEAT_POST_MIN_DELTA_PCT2)
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
}, markPosted2 = (state, w, now) => {
|
|
723
|
+
const lastPosted = {};
|
|
724
|
+
if (w.fiveHour)
|
|
725
|
+
lastPosted.fiveHour = w.fiveHour.usedPct;
|
|
726
|
+
if (w.sevenDay)
|
|
727
|
+
lastPosted.sevenDay = w.sevenDay.usedPct;
|
|
728
|
+
if (w.spendLimit)
|
|
729
|
+
lastPosted.spendLimit = w.spendLimit.usedPct;
|
|
730
|
+
return { ...state, window: w, lastPostedAt: now, lastPosted };
|
|
731
|
+
}, seatWallDue2 = (state, w) => {
|
|
732
|
+
if (!w?.sevenDay || w.sevenDay.usedPct < 100)
|
|
733
|
+
return false;
|
|
734
|
+
return state.lastWallResetsAt === undefined || state.lastWallResetsAt < w.sevenDay.resetsAt;
|
|
735
|
+
}, seatWindowBody2 = (w) => ({
|
|
736
|
+
capturedAt: w.capturedAt,
|
|
737
|
+
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
738
|
+
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
739
|
+
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
740
|
+
}), postJson2 = async (o, path, body) => {
|
|
741
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: { authorization: `Bearer ${o.token}`, "content-type": "application/json" },
|
|
746
|
+
body: JSON.stringify(body),
|
|
747
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? SEAT_POST_TIMEOUT_MS2)
|
|
748
|
+
});
|
|
749
|
+
return res.ok;
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}, postSeatWindow2 = (o, w) => postJson2(o, "/proxy/seat", seatWindowBody2(w)), postSeatWall2 = (o, input) => postJson2(o, "/proxy/seat-wall", input), SEAT_WALL_LINE2 = "Seat limit hit. sleds on --until-reset to continue on the org key, or sleds defer to resume here at the reset.", SEAT_WALL_LINE_NO_KEY2 = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.", seatWallLine2 = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE2 : SEAT_WALL_LINE_NO_KEY2, readStdin2 = (timeoutMs, stdin = process.stdin) => new Promise((resolve4) => {
|
|
754
|
+
if (stdin.isTTY) {
|
|
755
|
+
resolve4("");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
let data = "";
|
|
759
|
+
let settled = false;
|
|
760
|
+
const finish = () => {
|
|
761
|
+
if (settled)
|
|
762
|
+
return;
|
|
763
|
+
settled = true;
|
|
764
|
+
clearTimeout(timer);
|
|
765
|
+
resolve4(data);
|
|
766
|
+
};
|
|
767
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
768
|
+
stdin.setEncoding("utf8");
|
|
769
|
+
stdin.on("data", (chunk) => {
|
|
770
|
+
data += chunk;
|
|
771
|
+
});
|
|
772
|
+
stdin.on("end", finish);
|
|
773
|
+
stdin.on("error", finish);
|
|
774
|
+
stdin.resume();
|
|
775
|
+
});
|
|
776
|
+
var init_seat = __esm(() => {
|
|
777
|
+
init_config();
|
|
778
|
+
init_meter();
|
|
779
|
+
SEAT_POST_MIN_INTERVAL_MS2 = 5 * 60000;
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// src/index.ts
|
|
783
|
+
import { realpathSync } from "fs";
|
|
784
|
+
import { homedir, platform as platform2 } from "os";
|
|
785
|
+
import { fileURLToPath } from "url";
|
|
786
|
+
|
|
787
|
+
// src/commands/defer.ts
|
|
788
|
+
init_config();
|
|
789
|
+
init_meter();
|
|
790
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
791
|
+
import { hostname } from "node:os";
|
|
792
|
+
|
|
793
|
+
// src/musher.ts
|
|
794
|
+
import { spawnSync } from "node:child_process";
|
|
795
|
+
import { createPublicKey, verify as cryptoVerify } from "node:crypto";
|
|
796
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
797
|
+
var MUSHER_LABEL = "ai.sleds.musher";
|
|
798
|
+
var MUSHER_INTERVAL_S = 300;
|
|
799
|
+
var DEFAULT_PERMISSION_MODE = "acceptEdits";
|
|
800
|
+
var DEFAULT_TIMEOUT_MINUTES = 90;
|
|
801
|
+
var SUMMARY_MAX = 2000;
|
|
802
|
+
var musherPlistPath = (home) => join4(home, "Library", "LaunchAgents", `${MUSHER_LABEL}.plist`);
|
|
803
|
+
var musherLogPath = (configDir2) => join4(configDir2, "musher.log");
|
|
804
|
+
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
805
|
+
var musherPath = (home, execPath, currentPath = "") => {
|
|
806
|
+
const parts = [
|
|
807
|
+
dirname2(execPath),
|
|
808
|
+
join4(home, ".sleds", "bin"),
|
|
809
|
+
join4(home, ".local", "bin"),
|
|
810
|
+
join4(home, ".bun", "bin"),
|
|
811
|
+
"/opt/homebrew/bin",
|
|
812
|
+
"/usr/local/bin",
|
|
813
|
+
"/usr/bin",
|
|
814
|
+
"/bin",
|
|
815
|
+
...currentPath.split(":").filter(Boolean)
|
|
816
|
+
];
|
|
817
|
+
return [...new Set(parts)].join(":");
|
|
572
818
|
};
|
|
573
|
-
var
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
819
|
+
var buildMusherPlist = (o) => {
|
|
820
|
+
const args = [...o.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
|
|
821
|
+
`);
|
|
822
|
+
const env = Object.entries(o.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
823
|
+
<string>${xmlEscape(v)}</string>`).join(`
|
|
824
|
+
`);
|
|
825
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
826
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
827
|
+
<plist version="1.0">
|
|
828
|
+
<dict>
|
|
829
|
+
<key>Label</key>
|
|
830
|
+
<string>${MUSHER_LABEL}</string>
|
|
831
|
+
<key>ProgramArguments</key>
|
|
832
|
+
<array>
|
|
833
|
+
${args}
|
|
834
|
+
</array>
|
|
835
|
+
<key>EnvironmentVariables</key>
|
|
836
|
+
<dict>
|
|
837
|
+
${env}
|
|
838
|
+
</dict>
|
|
839
|
+
<key>StartInterval</key>
|
|
840
|
+
<integer>${MUSHER_INTERVAL_S}</integer>
|
|
841
|
+
<key>RunAtLoad</key>
|
|
842
|
+
<true/>
|
|
843
|
+
<key>StandardOutPath</key>
|
|
844
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
845
|
+
<key>StandardErrorPath</key>
|
|
846
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
847
|
+
</dict>
|
|
848
|
+
</plist>
|
|
849
|
+
`;
|
|
850
|
+
};
|
|
851
|
+
var RESUME_PROMPT = [
|
|
852
|
+
"SLEDS musher: the usage limit that stopped this session has reset, and the seat's owner",
|
|
853
|
+
"scheduled this unattended run to continue it. Pick up exactly where the session left off:",
|
|
854
|
+
"finish the task that was in flight, run the checks you would normally run, follow the repo's",
|
|
855
|
+
"own conventions for branches and commits, and do not start unrelated work. If you are blocked",
|
|
856
|
+
"on a decision only the owner can make, stop there and say so. Finish with a summary under",
|
|
857
|
+
"200 words: what was done, what remains, anything the owner must decide."
|
|
858
|
+
].join(" ");
|
|
859
|
+
var NO_SESSION_PROMPT = [
|
|
860
|
+
"SLEDS musher: a deferred job started on this seat, but no Claude Code session was recorded",
|
|
861
|
+
"to resume. Do not guess at work. Reply with one line saying so, and stop."
|
|
862
|
+
].join(" ");
|
|
863
|
+
var claudeArgs = (o) => [
|
|
864
|
+
"-p",
|
|
865
|
+
o.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
|
|
866
|
+
...o.sessionId ? ["--resume", o.sessionId] : [],
|
|
867
|
+
...o.model ? ["--model", o.model] : [],
|
|
868
|
+
"--output-format",
|
|
869
|
+
"json",
|
|
870
|
+
"--permission-mode",
|
|
871
|
+
o.permissionMode
|
|
872
|
+
];
|
|
873
|
+
var parseClaudeOutput = (stdout, exitCode) => {
|
|
874
|
+
const lines = stdout.split(`
|
|
875
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
876
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
877
|
+
const line = lines[i] ?? "";
|
|
878
|
+
if (!line.startsWith("{"))
|
|
879
|
+
continue;
|
|
880
|
+
try {
|
|
881
|
+
const j = JSON.parse(line);
|
|
882
|
+
if (j.type !== "result" && typeof j.result !== "string")
|
|
883
|
+
continue;
|
|
884
|
+
const isError = j.is_error === true || typeof j.subtype === "string" && j.subtype !== "success";
|
|
885
|
+
const text = typeof j.result === "string" ? j.result : "";
|
|
886
|
+
const models = j.modelUsage;
|
|
887
|
+
const model = typeof j.model === "string" ? j.model : models && typeof models === "object" ? Object.keys(models)[0] : undefined;
|
|
888
|
+
return {
|
|
889
|
+
ok: !isError && exitCode === 0,
|
|
890
|
+
summary: (text || (isError ? "Claude Code reported an error" : "")).slice(0, SUMMARY_MAX),
|
|
891
|
+
...typeof j.session_id === "string" ? { sessionId: j.session_id } : {},
|
|
892
|
+
...model ? { model } : {},
|
|
893
|
+
...typeof j.duration_ms === "number" ? { durationMs: j.duration_ms } : {},
|
|
894
|
+
...typeof j.total_cost_usd === "number" ? { costUsd: j.total_cost_usd } : {}
|
|
895
|
+
};
|
|
896
|
+
} catch {}
|
|
897
|
+
}
|
|
898
|
+
const tail = lines.slice(-5).join(" ").slice(0, SUMMARY_MAX);
|
|
899
|
+
return { ok: false, summary: tail || `claude exited ${exitCode ?? "without a code"}` };
|
|
900
|
+
};
|
|
901
|
+
var defaultExecCapture = (cmd, args, o) => {
|
|
902
|
+
const r = spawnSync(cmd, [...args], {
|
|
903
|
+
cwd: o.cwd,
|
|
904
|
+
encoding: "utf8",
|
|
905
|
+
timeout: o.timeoutMs,
|
|
906
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
907
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
908
|
+
env: { ...process.env, SLEDS_MUSHER: "1" }
|
|
909
|
+
});
|
|
910
|
+
return {
|
|
911
|
+
stdout: r.stdout ?? "",
|
|
912
|
+
stderr: r.stderr ?? "",
|
|
913
|
+
status: r.status,
|
|
914
|
+
...r.error ? { error: r.error.message } : {}
|
|
915
|
+
};
|
|
916
|
+
};
|
|
917
|
+
var musherClient = (o) => {
|
|
918
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
919
|
+
const headers = { authorization: `Bearer ${o.token}`, "content-type": "application/json" };
|
|
920
|
+
const call = async (path, body) => {
|
|
921
|
+
try {
|
|
922
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
923
|
+
method: body === undefined ? "GET" : "POST",
|
|
924
|
+
headers,
|
|
925
|
+
...body === undefined ? {} : { body: JSON.stringify(body) },
|
|
926
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? 1e4)
|
|
927
|
+
});
|
|
928
|
+
if (!res.ok)
|
|
929
|
+
return null;
|
|
930
|
+
return await res.json();
|
|
931
|
+
} catch {
|
|
932
|
+
return null;
|
|
600
933
|
}
|
|
601
|
-
|
|
602
|
-
|
|
934
|
+
};
|
|
935
|
+
return {
|
|
936
|
+
list: () => call("/proxy/musher"),
|
|
937
|
+
start: (jobId, local) => call("/proxy/musher/start", { jobId, ...local }),
|
|
938
|
+
finish: async (input) => await call("/proxy/musher/finish", input) !== null,
|
|
939
|
+
defer: (input) => call("/proxy/defer", input),
|
|
940
|
+
attestation: (jobId) => call(`/proxy/attest/${encodeURIComponent(jobId)}`)
|
|
941
|
+
};
|
|
942
|
+
};
|
|
943
|
+
var gitHead = (cwd, exec) => {
|
|
944
|
+
if (!cwd)
|
|
945
|
+
return null;
|
|
946
|
+
const r = exec("git", ["rev-parse", "HEAD"], { cwd, timeoutMs: 5000 });
|
|
947
|
+
const sha = r.stdout.trim();
|
|
948
|
+
return r.status === 0 && /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
949
|
+
};
|
|
950
|
+
var gitNewCommits = (cwd, before, after, exec) => {
|
|
951
|
+
if (!cwd || !after || after === before)
|
|
952
|
+
return [];
|
|
953
|
+
if (!before)
|
|
954
|
+
return [after];
|
|
955
|
+
const r = exec("git", ["rev-list", `${before}..${after}`], { cwd, timeoutMs: 5000 });
|
|
956
|
+
if (r.status !== 0)
|
|
957
|
+
return [after];
|
|
958
|
+
return r.stdout.split(`
|
|
959
|
+
`).map((l) => l.trim()).filter((l) => /^[0-9a-f]{40}$/.test(l)).slice(0, 40);
|
|
960
|
+
};
|
|
961
|
+
var collectOutputs = (commits, summary) => {
|
|
962
|
+
const out = commits.map((c) => `git:${c}`);
|
|
963
|
+
for (const m of summary.matchAll(/https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+/g))
|
|
964
|
+
if (!out.includes(`pr:${m[0]}`))
|
|
965
|
+
out.push(`pr:${m[0]}`);
|
|
966
|
+
return out.slice(0, 50);
|
|
967
|
+
};
|
|
968
|
+
var runJob = async (o) => {
|
|
969
|
+
const log = o.log ?? (() => {});
|
|
970
|
+
const sessionId = o.job.claudeSessionId ?? o.fallbackSession?.id ?? null;
|
|
971
|
+
const cwd = o.job.cwd ?? o.fallbackSession?.cwd ?? undefined;
|
|
972
|
+
const started = await o.client.start(o.job.jobId, {
|
|
973
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
974
|
+
...cwd ? { cwd } : {},
|
|
975
|
+
host: o.host
|
|
976
|
+
});
|
|
977
|
+
if (!started)
|
|
978
|
+
return { kind: "skipped", reason: "control plane unreachable" };
|
|
979
|
+
if (!started.ok)
|
|
980
|
+
return { kind: "skipped", reason: started.reason };
|
|
981
|
+
const model = o.job.model ?? started.model ?? o.fallbackSession?.model ?? null;
|
|
982
|
+
const args = claudeArgs({ sessionId, model, permissionMode: o.permissionMode });
|
|
983
|
+
log(`musher: running job ${o.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
|
|
984
|
+
const exec = o.exec ?? defaultExecCapture;
|
|
985
|
+
const headBefore = gitHead(cwd, exec);
|
|
986
|
+
const t0 = Date.now();
|
|
987
|
+
const r = exec(o.claudePath, args, { cwd, timeoutMs: o.timeoutMs });
|
|
988
|
+
const durationMs = Date.now() - t0;
|
|
989
|
+
const result = r.error ? { ok: false, summary: `could not run ${o.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
|
|
990
|
+
if (!r.error && r.status === null)
|
|
991
|
+
result.summary = `timed out after ${Math.round(o.timeoutMs / 60000)} min. ${result.summary}`.trim();
|
|
992
|
+
const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
|
|
993
|
+
await o.client.finish({
|
|
994
|
+
jobId: o.job.jobId,
|
|
995
|
+
runId: started.runId,
|
|
996
|
+
ok: result.ok,
|
|
997
|
+
summary: result.summary,
|
|
998
|
+
...outputs.length ? { outputs } : {},
|
|
999
|
+
...result.model ?? model ? { model: result.model ?? model } : {},
|
|
1000
|
+
durationMs: result.durationMs ?? durationMs,
|
|
1001
|
+
...result.sessionId ?? sessionId ? { claudeSessionId: result.sessionId ?? sessionId } : {}
|
|
1002
|
+
});
|
|
1003
|
+
log(`musher: job ${o.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
|
|
1004
|
+
return { kind: "ran", ok: result.ok, summary: result.summary, durationMs, outputs };
|
|
1005
|
+
};
|
|
1006
|
+
var verifyAttestation = (payload, sig, publicKeyB64) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const pub = createPublicKey({
|
|
1009
|
+
key: Buffer.from(publicKeyB64, "base64"),
|
|
1010
|
+
format: "der",
|
|
1011
|
+
type: "spki"
|
|
1012
|
+
});
|
|
1013
|
+
const raw = Buffer.from(sig.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
1014
|
+
return cryptoVerify(null, Buffer.from(payload, "utf8"), pub, raw);
|
|
1015
|
+
} catch {
|
|
1016
|
+
return false;
|
|
603
1017
|
}
|
|
604
|
-
lines.push("---");
|
|
605
|
-
lines.push(`Open dashboard | href=${DASHBOARD_URL}`);
|
|
606
|
-
lines.push("Refresh | refresh=true");
|
|
607
|
-
return lines;
|
|
608
1018
|
};
|
|
609
|
-
var
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
`if [ -x ${shellQuote(nodePath)} ] && [ -f ${shellQuote(cliPath)} ]; then`,
|
|
620
|
-
` exec ${shellQuote(nodePath)} ${shellQuote(cliPath)} bar`,
|
|
621
|
-
"fi",
|
|
622
|
-
"if command -v sleds >/dev/null 2>&1; then exec sleds bar; fi",
|
|
623
|
-
'echo "sleds not found"',
|
|
624
|
-
'echo "---"',
|
|
625
|
-
'echo "Run \\`npx @sleds/cli bar --install\\` again. | font=Menlo size=12"',
|
|
626
|
-
""
|
|
627
|
-
].join(`
|
|
628
|
-
`);
|
|
629
|
-
var shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1019
|
+
var parsePayload = (payload) => {
|
|
1020
|
+
const out = {};
|
|
1021
|
+
for (const line of payload.split(`
|
|
1022
|
+
`).slice(1)) {
|
|
1023
|
+
const eq = line.indexOf("=");
|
|
1024
|
+
if (eq > 0)
|
|
1025
|
+
out[line.slice(0, eq)] = line.slice(eq + 1);
|
|
1026
|
+
}
|
|
1027
|
+
return out;
|
|
1028
|
+
};
|
|
630
1029
|
|
|
631
1030
|
// src/seat.ts
|
|
1031
|
+
init_config();
|
|
1032
|
+
init_meter();
|
|
632
1033
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
633
|
-
import { join as
|
|
1034
|
+
import { join as join5 } from "node:path";
|
|
634
1035
|
var SEAT_POST_MIN_DELTA_PCT = 1;
|
|
635
1036
|
var SEAT_POST_MIN_INTERVAL_MS = 5 * 60000;
|
|
636
1037
|
var SEAT_POST_TIMEOUT_MS = 1500;
|
|
@@ -715,7 +1116,7 @@ var formatSeatFragment = (w) => {
|
|
|
715
1116
|
return null;
|
|
716
1117
|
return `seat ${parts.join(SEP)}`;
|
|
717
1118
|
};
|
|
718
|
-
var seatStatePath = (home) =>
|
|
1119
|
+
var seatStatePath = (home) => join5(configDir(home), "seat.json");
|
|
719
1120
|
var parseWindowStored = (v) => {
|
|
720
1121
|
if (!isObject4(v))
|
|
721
1122
|
return;
|
|
@@ -750,8 +1151,56 @@ var parseSeatState = (raw) => {
|
|
|
750
1151
|
const lastWallResetsAt = num2(raw.lastWallResetsAt);
|
|
751
1152
|
if (lastWallResetsAt !== undefined)
|
|
752
1153
|
out.lastWallResetsAt = lastWallResetsAt;
|
|
1154
|
+
const session = parseSeatSession(raw.session);
|
|
1155
|
+
if (session)
|
|
1156
|
+
out.session = session;
|
|
1157
|
+
return out;
|
|
1158
|
+
};
|
|
1159
|
+
var parseSeatSession = (v) => {
|
|
1160
|
+
if (!isObject4(v) || typeof v.id !== "string" || !v.id)
|
|
1161
|
+
return;
|
|
1162
|
+
const at = num2(v.at);
|
|
1163
|
+
if (at === undefined)
|
|
1164
|
+
return;
|
|
1165
|
+
const out = { id: v.id, at };
|
|
1166
|
+
if (typeof v.cwd === "string" && v.cwd)
|
|
1167
|
+
out.cwd = v.cwd;
|
|
1168
|
+
if (typeof v.model === "string" && v.model)
|
|
1169
|
+
out.model = v.model;
|
|
753
1170
|
return out;
|
|
754
1171
|
};
|
|
1172
|
+
var seatSessionFromStdin = (text, now) => {
|
|
1173
|
+
const trimmed = text.trim();
|
|
1174
|
+
if (!trimmed)
|
|
1175
|
+
return null;
|
|
1176
|
+
try {
|
|
1177
|
+
const raw = JSON.parse(trimmed);
|
|
1178
|
+
if (!isObject4(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
1179
|
+
return null;
|
|
1180
|
+
const out = { id: raw.session_id, at: now };
|
|
1181
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
1182
|
+
out.cwd = raw.cwd;
|
|
1183
|
+
const model = isObject4(raw.model) ? raw.model.id : raw.model;
|
|
1184
|
+
if (typeof model === "string" && model)
|
|
1185
|
+
out.model = model;
|
|
1186
|
+
return out;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var bindingResetAt = (w, now) => {
|
|
1192
|
+
if (!w)
|
|
1193
|
+
return;
|
|
1194
|
+
const bars = [
|
|
1195
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
1196
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
1197
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
1198
|
+
...Object.values(w.models ?? {})
|
|
1199
|
+
].filter((b) => b.resetsAt > now);
|
|
1200
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
1201
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
1202
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
1203
|
+
};
|
|
755
1204
|
var readSeatState = (home) => {
|
|
756
1205
|
const p = seatStatePath(home);
|
|
757
1206
|
if (!existsSync4(p))
|
|
@@ -827,167 +1276,37 @@ var postJson = async (o, path, body) => {
|
|
|
827
1276
|
return false;
|
|
828
1277
|
}
|
|
829
1278
|
};
|
|
830
|
-
var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
|
|
831
|
-
var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", input);
|
|
832
|
-
var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the org key.";
|
|
833
|
-
var
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
let settled = false;
|
|
840
|
-
const finish = () => {
|
|
841
|
-
if (settled)
|
|
842
|
-
return;
|
|
843
|
-
settled = true;
|
|
844
|
-
clearTimeout(timer);
|
|
845
|
-
resolve3(data);
|
|
846
|
-
};
|
|
847
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
848
|
-
stdin.setEncoding("utf8");
|
|
849
|
-
stdin.on("data", (chunk) => {
|
|
850
|
-
data += chunk;
|
|
851
|
-
});
|
|
852
|
-
stdin.on("end", finish);
|
|
853
|
-
stdin.on("error", finish);
|
|
854
|
-
stdin.resume();
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
// src/commands/bar.ts
|
|
858
|
-
function parseInstallArgs(argv, env) {
|
|
859
|
-
const flag = (name2) => {
|
|
860
|
-
const i = argv.indexOf(`--${name2}`);
|
|
861
|
-
const v = i >= 0 ? argv[i + 1] : undefined;
|
|
862
|
-
return v && !v.startsWith("--") ? v : undefined;
|
|
863
|
-
};
|
|
864
|
-
const sledsHome = flag("home") ?? (env.SLEDS_HOME?.trim() || undefined);
|
|
865
|
-
const claudeConfigDir = flag("claude-config") ?? (env.CLAUDE_CONFIG_DIR?.trim() || undefined);
|
|
866
|
-
const name = flag("name");
|
|
867
|
-
return {
|
|
868
|
-
...sledsHome ? { sledsHome } : {},
|
|
869
|
-
...claudeConfigDir ? { claudeConfigDir } : {},
|
|
870
|
-
...name ? { name } : {}
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
var PLUGIN_NAME = "sleds.1m.sh";
|
|
874
|
-
var pluginName = (name) => name ? `sleds-${name}.1m.sh` : PLUGIN_NAME;
|
|
875
|
-
var bar = async (o) => {
|
|
876
|
-
const log = o.log ?? console.log;
|
|
877
|
-
const argv = o.argv ?? [];
|
|
878
|
-
if (argv.includes("--install"))
|
|
879
|
-
return install(o, log);
|
|
880
|
-
if (argv.includes("--uninstall"))
|
|
881
|
-
return uninstall(o, log);
|
|
882
|
-
const now = (o.now ?? Date.now)();
|
|
883
|
-
const seat = freshSeat(readSeatState(o.home).window, now);
|
|
884
|
-
if (!meterSource(o.home)) {
|
|
885
|
-
log("sleds not set up");
|
|
886
|
-
log("---");
|
|
887
|
-
log("Run `sleds init` first | font=Menlo size=12");
|
|
888
|
-
return 0;
|
|
889
|
-
}
|
|
890
|
-
const m = await loadMeter({ home: o.home, now, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs });
|
|
891
|
-
for (const line of formatBar(m, seat, now))
|
|
892
|
-
log(line);
|
|
893
|
-
return 0;
|
|
894
|
-
};
|
|
895
|
-
function swiftBarPluginDir() {
|
|
896
|
-
try {
|
|
897
|
-
const out = execFileSync("defaults", ["read", "com.ameba.SwiftBar", "PluginDirectory"], {
|
|
898
|
-
encoding: "utf8",
|
|
899
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
900
|
-
}).trim();
|
|
901
|
-
return out ? out.replace(/^~(?=$|\/)/, homedir()) : null;
|
|
902
|
-
} catch {
|
|
903
|
-
return null;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
var pluginPath = (home, name) => join5(configDir(home, {}), "swiftbar", pluginName(name));
|
|
907
|
-
function install(o, log) {
|
|
908
|
-
const opts = parseInstallArgs(o.argv ?? [], o.env ?? process.env);
|
|
909
|
-
const script = pluginPath(o.home, opts.name);
|
|
910
|
-
mkdirSync5(join5(script, ".."), { recursive: true, mode: 448 });
|
|
911
|
-
writeFileSync5(script, pluginScript(o.nodePath ?? process.execPath, o.cliPath ?? process.argv[1] ?? "", {
|
|
912
|
-
...opts.sledsHome ? { sledsHome: resolve3(o.home, opts.sledsHome) } : {},
|
|
913
|
-
...opts.claudeConfigDir ? { claudeConfigDir: resolve3(o.home, opts.claudeConfigDir) } : {}
|
|
914
|
-
}), {
|
|
915
|
-
mode: 493
|
|
916
|
-
});
|
|
917
|
-
chmodSync2(script, 493);
|
|
918
|
-
log(`wrote ${script}`);
|
|
919
|
-
if (opts.sledsHome || opts.claudeConfigDir)
|
|
920
|
-
log(`tracks the seat at ${[opts.sledsHome && `SLEDS_HOME=${opts.sledsHome}`, opts.claudeConfigDir && `CLAUDE_CONFIG_DIR=${opts.claudeConfigDir}`].filter(Boolean).join(" ")}`);
|
|
921
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
922
|
-
if (!dir) {
|
|
923
|
-
log("SwiftBar is not set up yet: `brew install --cask swiftbar`, launch it once and pick a plugin");
|
|
924
|
-
log("folder, then run `sleds bar --install` again — or symlink the file above into that folder.");
|
|
925
|
-
return 0;
|
|
926
|
-
}
|
|
927
|
-
const link = join5(dir, pluginName(opts.name));
|
|
928
|
-
try {
|
|
929
|
-
mkdirSync5(dir, { recursive: true });
|
|
930
|
-
if (existsSync5(link) || isDanglingLink(link))
|
|
931
|
-
unlinkSync(link);
|
|
932
|
-
symlinkSync(script, link);
|
|
933
|
-
log(`linked ${link}`);
|
|
934
|
-
log("SwiftBar picks it up within a minute; the item reads `sleds $… · wk …%`.");
|
|
935
|
-
return 0;
|
|
936
|
-
} catch (e) {
|
|
937
|
-
log(`could not link into ${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
938
|
-
log(`symlink it yourself: ln -sf ${script} ${link}`);
|
|
939
|
-
return 1;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
function uninstall(o, log) {
|
|
943
|
-
const { name } = parseInstallArgs(o.argv ?? [], {});
|
|
944
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
945
|
-
const link = dir ? join5(dir, pluginName(name)) : null;
|
|
946
|
-
if (link && (existsSync5(link) || isDanglingLink(link))) {
|
|
947
|
-
unlinkSync(link);
|
|
948
|
-
log(`removed ${link}`);
|
|
949
|
-
} else
|
|
950
|
-
log("no SwiftBar plugin link to remove");
|
|
951
|
-
const script = pluginPath(o.home, name);
|
|
952
|
-
if (existsSync5(script)) {
|
|
953
|
-
unlinkSync(script);
|
|
954
|
-
log(`removed ${script}`);
|
|
955
|
-
}
|
|
956
|
-
return 0;
|
|
957
|
-
}
|
|
958
|
-
var isDanglingLink = (p) => {
|
|
959
|
-
try {
|
|
960
|
-
return lstatSync(p).isSymbolicLink();
|
|
961
|
-
} catch {
|
|
962
|
-
return false;
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
// src/commands/doctor.ts
|
|
967
|
-
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
968
|
-
import { existsSync as existsSync6, lstatSync as lstatSync2, readFileSync as readFileSync5, readdirSync, readlinkSync } from "node:fs";
|
|
969
|
-
import { join as join7, resolve as resolve5 } from "node:path";
|
|
970
|
-
|
|
971
|
-
// src/commands/bar.ts
|
|
972
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
973
|
-
import { homedir as homedir2 } from "node:os";
|
|
974
|
-
import { join as join6, resolve as resolve4 } from "node:path";
|
|
975
|
-
var PLUGIN_NAME2 = "sleds.1m.sh";
|
|
976
|
-
var pluginName2 = (name) => name ? `sleds-${name}.1m.sh` : PLUGIN_NAME2;
|
|
977
|
-
function swiftBarPluginDir2() {
|
|
978
|
-
try {
|
|
979
|
-
const out = execFileSync2("defaults", ["read", "com.ameba.SwiftBar", "PluginDirectory"], {
|
|
980
|
-
encoding: "utf8",
|
|
981
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
982
|
-
}).trim();
|
|
983
|
-
return out ? out.replace(/^~(?=$|\/)/, homedir2()) : null;
|
|
984
|
-
} catch {
|
|
985
|
-
return null;
|
|
1279
|
+
var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
|
|
1280
|
+
var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", input);
|
|
1281
|
+
var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the org key, or sleds defer to resume here at the reset.";
|
|
1282
|
+
var SEAT_WALL_LINE_NO_KEY = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.";
|
|
1283
|
+
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
1284
|
+
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
|
|
1285
|
+
if (stdin.isTTY) {
|
|
1286
|
+
resolve3("");
|
|
1287
|
+
return;
|
|
986
1288
|
}
|
|
987
|
-
|
|
988
|
-
|
|
1289
|
+
let data = "";
|
|
1290
|
+
let settled = false;
|
|
1291
|
+
const finish = () => {
|
|
1292
|
+
if (settled)
|
|
1293
|
+
return;
|
|
1294
|
+
settled = true;
|
|
1295
|
+
clearTimeout(timer);
|
|
1296
|
+
resolve3(data);
|
|
1297
|
+
};
|
|
1298
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
1299
|
+
stdin.setEncoding("utf8");
|
|
1300
|
+
stdin.on("data", (chunk) => {
|
|
1301
|
+
data += chunk;
|
|
1302
|
+
});
|
|
1303
|
+
stdin.on("end", finish);
|
|
1304
|
+
stdin.on("error", finish);
|
|
1305
|
+
stdin.resume();
|
|
1306
|
+
});
|
|
989
1307
|
|
|
990
1308
|
// src/switch.ts
|
|
1309
|
+
init_settings();
|
|
991
1310
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
992
1311
|
var envOf2 = (settings) => isObject5(settings.env) ? settings.env : {};
|
|
993
1312
|
var capturePriorEnv = (settings) => {
|
|
@@ -1062,7 +1381,141 @@ var formatDuration = (ms) => {
|
|
|
1062
1381
|
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
1063
1382
|
};
|
|
1064
1383
|
|
|
1384
|
+
// src/commands/defer.ts
|
|
1385
|
+
var parseAt = (v, now) => {
|
|
1386
|
+
const rel = /^\+(\d+)([hm])$/.exec(v.trim());
|
|
1387
|
+
if (rel) {
|
|
1388
|
+
const n = Number.parseInt(rel[1] ?? "0", 10);
|
|
1389
|
+
return now + n * (rel[2] === "h" ? 3600000 : 60000);
|
|
1390
|
+
}
|
|
1391
|
+
const t = Date.parse(v);
|
|
1392
|
+
return Number.isFinite(t) ? t : undefined;
|
|
1393
|
+
};
|
|
1394
|
+
var parseFlags = (argv) => {
|
|
1395
|
+
const flags = {};
|
|
1396
|
+
for (let i = 0;i < argv.length; i++) {
|
|
1397
|
+
const a = argv[i] ?? "";
|
|
1398
|
+
if (!a.startsWith("--"))
|
|
1399
|
+
continue;
|
|
1400
|
+
const eq = a.indexOf("=");
|
|
1401
|
+
if (eq > 0)
|
|
1402
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
1403
|
+
else {
|
|
1404
|
+
const next = argv[i + 1];
|
|
1405
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
1406
|
+
flags[a.slice(2)] = next;
|
|
1407
|
+
i++;
|
|
1408
|
+
} else
|
|
1409
|
+
flags[a.slice(2)] = "true";
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return flags;
|
|
1413
|
+
};
|
|
1414
|
+
var defer = async (o) => {
|
|
1415
|
+
const log = o.log ?? console.log;
|
|
1416
|
+
const error = o.error ?? console.error;
|
|
1417
|
+
const now = (o.now ?? Date.now)();
|
|
1418
|
+
const flags = parseFlags(o.argv);
|
|
1419
|
+
const client = o.client ?? (() => {
|
|
1420
|
+
const src = meterSource(o.home);
|
|
1421
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
1422
|
+
})();
|
|
1423
|
+
if (!client) {
|
|
1424
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
1425
|
+
return 1;
|
|
1426
|
+
}
|
|
1427
|
+
const state = readSeatState(o.home);
|
|
1428
|
+
const piped = o.stdin ? seatSessionFromStdin(o.stdin, now) : null;
|
|
1429
|
+
const remembered = piped ?? state.session;
|
|
1430
|
+
const sessionId = flags.session ?? remembered?.id;
|
|
1431
|
+
const cwd = flags.cwd ?? remembered?.cwd;
|
|
1432
|
+
const model = flags.model ?? remembered?.model;
|
|
1433
|
+
let resetAt;
|
|
1434
|
+
if (flags.at) {
|
|
1435
|
+
resetAt = parseAt(flags.at, now);
|
|
1436
|
+
if (resetAt === undefined) {
|
|
1437
|
+
error(`--at takes +2h, +90m or an ISO timestamp (got ${flags.at})`);
|
|
1438
|
+
return 1;
|
|
1439
|
+
}
|
|
1440
|
+
} else
|
|
1441
|
+
resetAt = bindingResetAt(state.window, now);
|
|
1442
|
+
if (!sessionId)
|
|
1443
|
+
log("No Claude Code session remembered on this seat yet; the job will say so when it starts. Pass --session <id> to pick one.");
|
|
1444
|
+
const r = await client.defer({
|
|
1445
|
+
...resetAt !== undefined ? { resetAt } : {},
|
|
1446
|
+
...model ? { model } : {},
|
|
1447
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
1448
|
+
...cwd ? { cwd } : {},
|
|
1449
|
+
host: o.host ?? hostname()
|
|
1450
|
+
});
|
|
1451
|
+
if (!r) {
|
|
1452
|
+
error("Could not reach the SLEDS proxy; nothing was scheduled.");
|
|
1453
|
+
return 1;
|
|
1454
|
+
}
|
|
1455
|
+
const when = r.scheduledAt <= now + 60000 ? "as soon as the musher ticks (within 5 min)" : `${formatLocal(r.scheduledAt)} (in ${formatDuration(r.scheduledAt - now)})`;
|
|
1456
|
+
log(`Deferred. This Mac resumes ${sessionId ? `session ${sessionId.slice(0, 8)}…` : "the work"}${model ? ` on ${model}` : ""} ${when}.`);
|
|
1457
|
+
log(` job ${r.jobId}${cwd ? ` · ${cwd}` : ""} · runs on your seat, no key · sleds musher --status to watch`);
|
|
1458
|
+
const cfg = readConfig(o.home);
|
|
1459
|
+
if (process.platform === "darwin" && !existsSync5(musherPlistPath(o.home)) && cfg.musher?.agent !== "launchd")
|
|
1460
|
+
log(` The musher agent is not installed on this Mac: run \`sleds musher --install\` once, or \`sleds run ${r.jobId}\` by hand at the reset.`);
|
|
1461
|
+
log(" Keep this Mac awake at that time (System Settings → Battery → prevent sleeping on power adapter).");
|
|
1462
|
+
return 0;
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// src/commands/doctor.ts
|
|
1466
|
+
init_config();
|
|
1467
|
+
init_meter();
|
|
1468
|
+
import { execFileSync } from "node:child_process";
|
|
1469
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync } from "node:fs";
|
|
1470
|
+
import { resolve as resolve3 } from "node:path";
|
|
1471
|
+
init_settings();
|
|
1472
|
+
|
|
1473
|
+
// src/commands/musher.ts
|
|
1474
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1475
|
+
init_config();
|
|
1476
|
+
init_meter();
|
|
1477
|
+
var defaultWhich = (cmd) => {
|
|
1478
|
+
const path = process.env.PATH ?? "";
|
|
1479
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
1480
|
+
const p = `${dir}/${cmd}`;
|
|
1481
|
+
if (existsSync6(p))
|
|
1482
|
+
return p;
|
|
1483
|
+
}
|
|
1484
|
+
return null;
|
|
1485
|
+
};
|
|
1486
|
+
var findClaude = (home, configured, which) => {
|
|
1487
|
+
if (configured && existsSync6(configured))
|
|
1488
|
+
return configured;
|
|
1489
|
+
const found = which("claude");
|
|
1490
|
+
if (found)
|
|
1491
|
+
return found;
|
|
1492
|
+
for (const p of [
|
|
1493
|
+
`${home}/.local/bin/claude`,
|
|
1494
|
+
"/opt/homebrew/bin/claude",
|
|
1495
|
+
"/usr/local/bin/claude",
|
|
1496
|
+
`${home}/.npm-global/bin/claude`
|
|
1497
|
+
])
|
|
1498
|
+
if (existsSync6(p))
|
|
1499
|
+
return p;
|
|
1500
|
+
return null;
|
|
1501
|
+
};
|
|
1502
|
+
var musherAgentState = (home, env = process.env) => {
|
|
1503
|
+
const plist = musherPlistPath(home);
|
|
1504
|
+
if (!existsSync6(plist))
|
|
1505
|
+
return { installed: false, thisSeat: false };
|
|
1506
|
+
try {
|
|
1507
|
+
const text = readFileSync5(plist, "utf8");
|
|
1508
|
+
const want = env.SLEDS_HOME?.trim();
|
|
1509
|
+
const has = /<key>SLEDS_HOME<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1];
|
|
1510
|
+
return { installed: true, thisSeat: (want ?? undefined) === (has ?? undefined) };
|
|
1511
|
+
} catch {
|
|
1512
|
+
return { installed: true, thisSeat: false };
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1065
1516
|
// src/commands/status.ts
|
|
1517
|
+
init_config();
|
|
1518
|
+
init_settings();
|
|
1066
1519
|
var checkHealth = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
1067
1520
|
try {
|
|
1068
1521
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -1080,16 +1533,6 @@ function nodeOk(version, min = NODE_MIN) {
|
|
|
1080
1533
|
const [x, y, z] = parse(min);
|
|
1081
1534
|
return (a ?? 0) !== (x ?? 0) ? (a ?? 0) > (x ?? 0) : (b ?? 0) !== (y ?? 0) ? (b ?? 0) > (y ?? 0) : (c ?? 0) >= (z ?? 0);
|
|
1082
1535
|
}
|
|
1083
|
-
function pluginEnv(body) {
|
|
1084
|
-
const out = {};
|
|
1085
|
-
const home = /^export SLEDS_HOME='([^']*)'/m.exec(body);
|
|
1086
|
-
const cfg = /^export CLAUDE_CONFIG_DIR='([^']*)'/m.exec(body);
|
|
1087
|
-
if (home?.[1])
|
|
1088
|
-
out.sledsHome = home[1];
|
|
1089
|
-
if (cfg?.[1])
|
|
1090
|
-
out.claudeConfigDir = cfg[1];
|
|
1091
|
-
return out;
|
|
1092
|
-
}
|
|
1093
1536
|
var MARK = { ok: "ok ", warn: "warn", fail: "FAIL" };
|
|
1094
1537
|
function formatDoctor(checks) {
|
|
1095
1538
|
const lines = checks.map((c) => `${MARK[c.level]} ${c.name.padEnd(16)}${c.detail}`);
|
|
@@ -1105,9 +1548,9 @@ function formatDoctor(checks) {
|
|
|
1105
1548
|
lines.push(fails === 0 && warns === 0 ? "All good." : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`);
|
|
1106
1549
|
return lines;
|
|
1107
1550
|
}
|
|
1108
|
-
function
|
|
1551
|
+
function defaultWhich2(cmd) {
|
|
1109
1552
|
try {
|
|
1110
|
-
return
|
|
1553
|
+
return execFileSync("which", ["-a", cmd], {
|
|
1111
1554
|
encoding: "utf8",
|
|
1112
1555
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1113
1556
|
}).split(`
|
|
@@ -1116,16 +1559,6 @@ function defaultWhich(cmd) {
|
|
|
1116
1559
|
return [];
|
|
1117
1560
|
}
|
|
1118
1561
|
}
|
|
1119
|
-
function defaultLaunchctl() {
|
|
1120
|
-
try {
|
|
1121
|
-
return execFileSync3("launchctl", ["list"], {
|
|
1122
|
-
encoding: "utf8",
|
|
1123
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1124
|
-
});
|
|
1125
|
-
} catch {
|
|
1126
|
-
return "";
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
1562
|
async function runDoctor(o) {
|
|
1130
1563
|
const env = o.env ?? process.env;
|
|
1131
1564
|
const checks = [];
|
|
@@ -1136,7 +1569,7 @@ async function runDoctor(o) {
|
|
|
1136
1569
|
].filter(Boolean);
|
|
1137
1570
|
const cfgDir = configDir(home, env);
|
|
1138
1571
|
const cfg = readConfig(home);
|
|
1139
|
-
const cfgExists =
|
|
1572
|
+
const cfgExists = existsSync7(configPath(home));
|
|
1140
1573
|
if (!cfgExists) {
|
|
1141
1574
|
checks.push({
|
|
1142
1575
|
level: "fail",
|
|
@@ -1152,7 +1585,7 @@ async function runDoctor(o) {
|
|
|
1152
1585
|
});
|
|
1153
1586
|
}
|
|
1154
1587
|
const sPath = settingsPath(home);
|
|
1155
|
-
const settings =
|
|
1588
|
+
const settings = existsSync7(sPath) ? readSettings(home) : null;
|
|
1156
1589
|
const pointed = settings ? readProxy(settings) : null;
|
|
1157
1590
|
if (!settings) {
|
|
1158
1591
|
checks.push({
|
|
@@ -1196,7 +1629,7 @@ async function runDoctor(o) {
|
|
|
1196
1629
|
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
|
|
1197
1630
|
fix: seat2 && spec ? undefined : "sleds hooks"
|
|
1198
1631
|
});
|
|
1199
|
-
const want = env.SLEDS_HOME?.trim() ?
|
|
1632
|
+
const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
|
|
1200
1633
|
const cmds = sledsCommandsIn(settings);
|
|
1201
1634
|
const wrong = cmds.filter((c) => {
|
|
1202
1635
|
const got = bakedHome(c);
|
|
@@ -1210,7 +1643,7 @@ async function runDoctor(o) {
|
|
|
1210
1643
|
fix: wrong.length === 0 ? undefined : `${overrides.length ? `${overrides.join(" ")} ` : ""}sleds hooks (re-writes the status line + hooks with this seat baked in)`
|
|
1211
1644
|
});
|
|
1212
1645
|
}
|
|
1213
|
-
const skill =
|
|
1646
|
+
const skill = existsSync7(skillPath(home));
|
|
1214
1647
|
checks.push({
|
|
1215
1648
|
level: skill ? "ok" : "warn",
|
|
1216
1649
|
name: "skill",
|
|
@@ -1225,10 +1658,10 @@ async function runDoctor(o) {
|
|
|
1225
1658
|
detail: `${nodeVersion}${nodeOk(nodeVersion) ? "" : ` — seat-sync needs ≥ ${NODE_MIN} (node:sqlite)`}`,
|
|
1226
1659
|
fix: nodeOk(nodeVersion) ? undefined : `brew install node@22 && echo 'export PATH="/opt/homebrew/opt/node@22/bin:$PATH"' >> ~/.zshrc`
|
|
1227
1660
|
});
|
|
1228
|
-
const which = (o.which ??
|
|
1661
|
+
const which = (o.which ?? defaultWhich2)("sleds");
|
|
1229
1662
|
const stale = which.filter((p) => {
|
|
1230
1663
|
try {
|
|
1231
|
-
const target =
|
|
1664
|
+
const target = lstatSync(p).isSymbolicLink() ? readlinkSync(p) : p;
|
|
1232
1665
|
return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p);
|
|
1233
1666
|
} catch {
|
|
1234
1667
|
return false;
|
|
@@ -1243,49 +1676,64 @@ async function runDoctor(o) {
|
|
|
1243
1676
|
});
|
|
1244
1677
|
else if (which.length > 0)
|
|
1245
1678
|
checks.push({ level: "ok", name: "path", detail: which[0] ?? "" });
|
|
1246
|
-
const pluginBody = existsSync6(pluginPath2(home)) ? readFileSync5(pluginPath2(home), "utf8") : null;
|
|
1247
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir2)();
|
|
1248
|
-
if (pluginBody) {
|
|
1249
|
-
const baked = pluginEnv(pluginBody);
|
|
1250
|
-
const linked = dir ? existsSync6(join7(dir, PLUGIN_NAME2)) : false;
|
|
1251
|
-
const seatDesc = baked.sledsHome || baked.claudeConfigDir ? `seat at ${[baked.sledsHome && `SLEDS_HOME=${baked.sledsHome}`, baked.claudeConfigDir && `CLAUDE_CONFIG_DIR=${baked.claudeConfigDir}`].filter(Boolean).join(" ")}` : "default seat";
|
|
1252
|
-
checks.push({
|
|
1253
|
-
level: linked ? "ok" : "warn",
|
|
1254
|
-
name: "menu bar",
|
|
1255
|
-
detail: linked ? `${PLUGIN_NAME2} linked · ${seatDesc}` : `plugin written but not linked into SwiftBar${dir ? ` (${dir})` : " (SwiftBar not set up)"}`,
|
|
1256
|
-
fix: linked ? undefined : "sleds bar --install"
|
|
1257
|
-
});
|
|
1258
|
-
} else {
|
|
1259
|
-
checks.push({
|
|
1260
|
-
level: "warn",
|
|
1261
|
-
name: "menu bar",
|
|
1262
|
-
detail: "no SwiftBar plugin",
|
|
1263
|
-
fix: "sleds bar --install"
|
|
1264
|
-
});
|
|
1265
|
-
}
|
|
1266
|
-
const agents = join7(home, "Library", "LaunchAgents");
|
|
1267
|
-
const plists = existsSync6(agents) ? readdirSync(agents).filter((f) => f.startsWith("ai.sleds.") && f.endsWith(".plist")) : [];
|
|
1268
|
-
const loaded = (o.launchctlList ?? defaultLaunchctl)();
|
|
1269
1679
|
const seat = readSeatState(home);
|
|
1270
1680
|
const age = seat.window ? Math.round((Date.now() - seat.window.capturedAt) / 60000) : null;
|
|
1271
|
-
|
|
1272
|
-
|
|
1681
|
+
checks.push({
|
|
1682
|
+
level: age === null ? "warn" : "ok",
|
|
1683
|
+
name: "seat quota",
|
|
1684
|
+
detail: age === null ? "no report yet — run one Claude Code turn on this seat" : `last report ${age < 1 ? "just now" : `${age}m ago`} via the status line${seat.window?.sevenDay ? ` · wk ${Math.round(seat.window.sevenDay.usedPct)}%` : ""}`,
|
|
1685
|
+
fix: age === null ? "open claude, say hi, exit — the status line posts the seat's quota" : undefined
|
|
1686
|
+
});
|
|
1687
|
+
if ((o.platform ?? process.platform) === "darwin") {
|
|
1688
|
+
const agent = musherAgentState(home, env);
|
|
1689
|
+
checks.push(!agent.installed ? {
|
|
1273
1690
|
level: "warn",
|
|
1274
|
-
name: "
|
|
1275
|
-
detail: "
|
|
1276
|
-
fix: "sleds
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1691
|
+
name: "musher",
|
|
1692
|
+
detail: "agent not installed — `sleds defer` jobs would wait for `sleds run`",
|
|
1693
|
+
fix: "sleds musher --install"
|
|
1694
|
+
} : !agent.thisSeat ? {
|
|
1695
|
+
level: "warn",
|
|
1696
|
+
name: "musher",
|
|
1697
|
+
detail: "agent installed for a different seat (SLEDS_HOME differs)",
|
|
1698
|
+
fix: "sleds musher --install"
|
|
1699
|
+
} : { level: "ok", name: "musher", detail: `launchd agent ${MUSHER_LABEL}, every 5 min` });
|
|
1700
|
+
const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o.which ?? defaultWhich2)(c)[0] ?? null);
|
|
1701
|
+
checks.push(claude ? { level: "ok", name: "claude cli", detail: claude } : {
|
|
1702
|
+
level: "warn",
|
|
1703
|
+
name: "claude cli",
|
|
1704
|
+
detail: "`claude` not on PATH — the musher cannot run Claude Code",
|
|
1705
|
+
fix: 'sleds musher --claude "$(which claude)"'
|
|
1285
1706
|
});
|
|
1707
|
+
const sleep = (o.pmset ?? defaultPmset)();
|
|
1708
|
+
if (sleep !== null)
|
|
1709
|
+
checks.push(sleep.acSleepMinutes === 0 ? {
|
|
1710
|
+
level: "ok",
|
|
1711
|
+
name: "awake",
|
|
1712
|
+
detail: "does not sleep on power — deferred jobs can start"
|
|
1713
|
+
} : {
|
|
1714
|
+
level: "warn",
|
|
1715
|
+
name: "awake",
|
|
1716
|
+
detail: `sleeps after ${sleep.acSleepMinutes} min on power — a deferred job needs the Mac awake at the reset`,
|
|
1717
|
+
fix: "System Settings → Battery → Options → Prevent automatic sleeping on power adapter (or: sudo pmset -c sleep 0)"
|
|
1718
|
+
});
|
|
1286
1719
|
}
|
|
1287
1720
|
return checks;
|
|
1288
1721
|
}
|
|
1722
|
+
function parsePmset(text) {
|
|
1723
|
+
const ac = text.split(/AC Power:/i)[1];
|
|
1724
|
+
const block = ac ?? text;
|
|
1725
|
+
const m = /^\s*sleep\s+(\d+)/m.exec(block);
|
|
1726
|
+
if (!m)
|
|
1727
|
+
return null;
|
|
1728
|
+
return { acSleepMinutes: Number.parseInt(m[1] ?? "0", 10) };
|
|
1729
|
+
}
|
|
1730
|
+
var defaultPmset = () => {
|
|
1731
|
+
try {
|
|
1732
|
+
return parsePmset(execFileSync("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1289
1737
|
var doctor = async (o) => {
|
|
1290
1738
|
const log = o.log ?? console.log;
|
|
1291
1739
|
const checks = await runDoctor(o);
|
|
@@ -1295,6 +1743,7 @@ var doctor = async (o) => {
|
|
|
1295
1743
|
};
|
|
1296
1744
|
|
|
1297
1745
|
// src/commands/hooks.ts
|
|
1746
|
+
init_settings();
|
|
1298
1747
|
var hooks = (o) => {
|
|
1299
1748
|
const log = o.log ?? console.log;
|
|
1300
1749
|
const error = o.error ?? console.error;
|
|
@@ -1317,6 +1766,8 @@ var hooks = (o) => {
|
|
|
1317
1766
|
};
|
|
1318
1767
|
|
|
1319
1768
|
// src/commands/init.ts
|
|
1769
|
+
init_config();
|
|
1770
|
+
init_settings();
|
|
1320
1771
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1321
1772
|
import { createInterface } from "node:readline";
|
|
1322
1773
|
|
|
@@ -1350,8 +1801,13 @@ When the proxy is active it answers a walled request with its options. The user
|
|
|
1350
1801
|
/sled 3 run overnight at half price
|
|
1351
1802
|
/sled 4 switch to a cheaper model for the rest of the session (offered when one saves ≥15%)
|
|
1352
1803
|
/sled 5 hand off to an idle coworker, or park for the team (offered on a team of two or more)
|
|
1804
|
+
/sled 6 defer to reset: the job runs itself when the seat resets — same model, done in minutes, full price (offered when the wall has a reset and the org has a key)
|
|
1353
1805
|
\`\`\`
|
|
1354
1806
|
|
|
1807
|
+
No key (Team plan, seat-first)? The musher still runs: \`sleds defer\` in a terminal schedules this Mac to resume the current session itself at the reset — \`claude --resume\` on the seat's own quota, the model pinned. \`sleds musher --status\` lists what is waiting; \`sleds run <jobId>\` runs one now; the Mac must be awake at the reset (\`sleds doctor\` checks). When the user says "defer this" or "pick it up at the reset" and the org has no key, tell them to run \`sleds defer\`.
|
|
1808
|
+
|
|
1809
|
+
A hand-off carries a brief (the packet): what, note, cost so far, how to pick it up. The \`handoffs\` MCP tool and the dashboard's Jobs page show it; read it before taking one.
|
|
1810
|
+
|
|
1355
1811
|
## Window reset
|
|
1356
1812
|
|
|
1357
1813
|
When the user mentions their window has reset, or the deadline shown by \`sleds status\` has passed:
|
|
@@ -1364,9 +1820,9 @@ then restart \`claude\`. Settings go back to exactly what they were.
|
|
|
1364
1820
|
|
|
1365
1821
|
## Check
|
|
1366
1822
|
|
|
1367
|
-
\`sleds status\` — on/off, since, reverts at, proxy health, wall mode.
|
|
1823
|
+
\`sleds status\` — on/off, since, reverts at, proxy health, wall mode, and the org's shape (\`Org:\` / \`Wiring:\`). An org with no platform key runs seat-first and its walls offer hold (\`/sled 2\`), \`sleds defer\` (this Mac resumes at the reset) and hand-off (\`/sled 5\`) — continue, overnight and switch need a key an admin adds on the dashboard.
|
|
1368
1824
|
|
|
1369
|
-
The status line's \`seat wk 52% · 5h 12%\` is the user's own Pro/Max window as Claude Code reports it (weekly, then 5-hour); \`window\` is the org key's headroom through the proxy. At \`wk 100%\` the answer is \`sleds on --until-reset
|
|
1825
|
+
The status line's \`seat wk 52% · 5h 12%\` is the user's own Pro/Max window as Claude Code reports it (weekly, then 5-hour); \`window\` is the org key's headroom through the proxy. At \`wk 100%\` the answer is \`sleds on --until-reset\` (org key) or \`sleds defer\` (this Mac resumes at the reset, no key).
|
|
1370
1826
|
|
|
1371
1827
|
## /sled — estimate the task, offer overnight
|
|
1372
1828
|
|
|
@@ -1441,6 +1897,7 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
|
|
|
1441
1897
|
`;
|
|
1442
1898
|
|
|
1443
1899
|
// src/commands/hooks.ts
|
|
1900
|
+
init_settings();
|
|
1444
1901
|
var hooks2 = (o) => {
|
|
1445
1902
|
const log = o.log ?? console.log;
|
|
1446
1903
|
const error = o.error ?? console.error;
|
|
@@ -1464,24 +1921,35 @@ var hooks2 = (o) => {
|
|
|
1464
1921
|
|
|
1465
1922
|
// src/commands/init.ts
|
|
1466
1923
|
var DEFAULT_PROXY_URL = "http://localhost:8787";
|
|
1467
|
-
var promptStdin = (question) => new Promise((
|
|
1924
|
+
var promptStdin = (question) => new Promise((resolve4) => {
|
|
1468
1925
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1469
1926
|
rl.question(question, (answer) => {
|
|
1470
1927
|
rl.close();
|
|
1471
|
-
|
|
1928
|
+
resolve4(answer.trim());
|
|
1472
1929
|
});
|
|
1473
1930
|
});
|
|
1474
1931
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
|
|
1932
|
+
return signupRequest(proxyUrl, { email, apiKey: credential }, fetchFn);
|
|
1933
|
+
}
|
|
1934
|
+
async function signupTeam(proxyUrl, email, team, credential, fetchFn = fetch) {
|
|
1935
|
+
return signupRequest(proxyUrl, { email, team, ...credential ? { apiKey: credential } : {} }, fetchFn);
|
|
1936
|
+
}
|
|
1937
|
+
async function signupRequest(proxyUrl, payload, fetchFn) {
|
|
1475
1938
|
try {
|
|
1476
1939
|
const res = await fetchFn(`${proxyUrl}/signup`, {
|
|
1477
1940
|
method: "POST",
|
|
1478
1941
|
headers: { "content-type": "application/json" },
|
|
1479
|
-
body: JSON.stringify(
|
|
1942
|
+
body: JSON.stringify(payload),
|
|
1480
1943
|
signal: AbortSignal.timeout(20000)
|
|
1481
1944
|
});
|
|
1482
1945
|
const body = await res.json().catch(() => ({}));
|
|
1483
1946
|
if (res.ok && typeof body.token === "string")
|
|
1484
|
-
return {
|
|
1947
|
+
return {
|
|
1948
|
+
ok: true,
|
|
1949
|
+
token: body.token,
|
|
1950
|
+
created: body.created === true,
|
|
1951
|
+
verifyPending: body.verifyPending === true
|
|
1952
|
+
};
|
|
1485
1953
|
return {
|
|
1486
1954
|
ok: false,
|
|
1487
1955
|
reason: typeof body.reason === "string" ? body.reason : `http_${res.status}`,
|
|
@@ -1497,6 +1965,9 @@ var SIGNUP_REASONS = {
|
|
|
1497
1965
|
key_rejected: "Anthropic did not accept that API key. Check it at console.anthropic.com → API keys.",
|
|
1498
1966
|
rate_limited: "too many signups from this network in the last hour. Try again later.",
|
|
1499
1967
|
not_configured: "the proxy is not set up for self-serve signup yet.",
|
|
1968
|
+
team: "the team needs a name (2–80 characters).",
|
|
1969
|
+
personal_email: "a team needs a work email — that domain is a personal mailbox. Sign up as an individual instead, or use your company address.",
|
|
1970
|
+
org_exists: "someone at your company already set up SLEDS. Ask them for a seat (Members → invite, Admin → Mint a seat) and run `sleds init --token …`.",
|
|
1500
1971
|
unavailable: "the proxy could not reach its control plane. Try again in a moment.",
|
|
1501
1972
|
unreachable: "could not reach the proxy."
|
|
1502
1973
|
};
|
|
@@ -1522,6 +1993,20 @@ function normalizeProxyUrl(raw) {
|
|
|
1522
1993
|
return null;
|
|
1523
1994
|
}
|
|
1524
1995
|
}
|
|
1996
|
+
async function fetchOrgInfo(url, token, fetchFn = fetch) {
|
|
1997
|
+
try {
|
|
1998
|
+
const res = await fetchFn(`${url}/proxy/meter`, {
|
|
1999
|
+
headers: { authorization: `Bearer ${token}` },
|
|
2000
|
+
signal: AbortSignal.timeout(1e4)
|
|
2001
|
+
});
|
|
2002
|
+
if (!res.ok)
|
|
2003
|
+
return null;
|
|
2004
|
+
const body = await res.json();
|
|
2005
|
+
return body.org?.capabilities ? body.org : null;
|
|
2006
|
+
} catch {
|
|
2007
|
+
return null;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1525
2010
|
async function probeProxy(url, fetchFn = fetch, opts = {}) {
|
|
1526
2011
|
const tries = opts.tries ?? 2;
|
|
1527
2012
|
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
@@ -1550,21 +2035,45 @@ var resolveToken = async (o) => {
|
|
|
1550
2035
|
if (typed)
|
|
1551
2036
|
return { token: typed, source: "prompt" };
|
|
1552
2037
|
}
|
|
1553
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2038
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1554
2039
|
if (!url)
|
|
1555
2040
|
return null;
|
|
1556
|
-
const
|
|
2041
|
+
const log = o.log ?? console.log;
|
|
2042
|
+
const err = o.error ?? console.error;
|
|
2043
|
+
let team = o.team?.trim() || null;
|
|
2044
|
+
if (team === null && !o.email) {
|
|
2045
|
+
const which = (await o.prompt("Set up SLEDS for (1) just you, or (2) your team? [1/2]: ")).trim();
|
|
2046
|
+
if (which === "2") {
|
|
2047
|
+
team = (await o.prompt("Team name (e.g. your company): ")).trim();
|
|
2048
|
+
if (!team)
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
const email = (o.email ?? await o.prompt(team ? "Your work email: " : "Your email: ")).trim();
|
|
1557
2053
|
if (!email.includes("@"))
|
|
1558
2054
|
return null;
|
|
2055
|
+
if (team) {
|
|
2056
|
+
const credential2 = (await o.prompt("Your organization's Anthropic API key, if you have one (sk-ant-…; press enter to skip — a Team plan without one still works, walls offer hold and hand-off): ")).trim();
|
|
2057
|
+
const r2 = await signupTeam(url, email, team, credential2 || null, o.fetchFn);
|
|
2058
|
+
if (!r2.ok) {
|
|
2059
|
+
err(signupFailureText(r2));
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
log(`Created ${team} with you as owner (${email}).${credential2 ? " Your platform key is stored encrypted as the org's upstream key." : " No platform key yet — add one on the dashboard (Admin → Keys) to unlock continue / overnight / switch at a wall."}`);
|
|
2063
|
+
if (r2.verifyPending)
|
|
2064
|
+
log(`Check ${email} for a verification link and click it — inviting members unlocks then (an unverified team is removed after 48 h; your own seat works meanwhile).`);
|
|
2065
|
+
log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
|
|
2066
|
+
return { token: r2.token, source: "signup" };
|
|
2067
|
+
}
|
|
1559
2068
|
const credential = (await o.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
|
|
1560
2069
|
if (!credential)
|
|
1561
2070
|
return null;
|
|
1562
2071
|
const r = await signupIndividual(url, email, credential, o.fetchFn);
|
|
1563
2072
|
if (!r.ok) {
|
|
1564
|
-
(
|
|
2073
|
+
err(signupFailureText(r));
|
|
1565
2074
|
return null;
|
|
1566
2075
|
}
|
|
1567
|
-
|
|
2076
|
+
log(r.created ? `Created your individual seat (${email}). Your key is stored encrypted and is the only key this seat spends.` : `Re-issued your individual seat (${email}); the previous token is revoked.`);
|
|
1568
2077
|
return { token: r.token, source: "signup" };
|
|
1569
2078
|
};
|
|
1570
2079
|
var init = async (o) => {
|
|
@@ -1584,7 +2093,7 @@ var init = async (o) => {
|
|
|
1584
2093
|
error("Invalid seat token: expected something like sled_xxxxxxxx.");
|
|
1585
2094
|
return 2;
|
|
1586
2095
|
}
|
|
1587
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2096
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1588
2097
|
if (!url) {
|
|
1589
2098
|
error(`Invalid proxy URL: ${JSON.stringify(o.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
|
|
1590
2099
|
return 2;
|
|
@@ -1594,12 +2103,19 @@ var init = async (o) => {
|
|
|
1594
2103
|
return 2;
|
|
1595
2104
|
}
|
|
1596
2105
|
const wrote = [];
|
|
2106
|
+
const org = await fetchOrgInfo(url, resolved.token, o.fetchFn);
|
|
2107
|
+
const wiring = o.proxyAlways ? "proxy-always" : o.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
|
|
2108
|
+
if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
|
|
2109
|
+
error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
|
|
2110
|
+
}
|
|
2111
|
+
const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
|
|
1597
2112
|
if (backupSettings(o.home))
|
|
1598
2113
|
wrote.push(`backed up settings to ${backupPath(o.home)}`);
|
|
1599
|
-
const
|
|
1600
|
-
const
|
|
2114
|
+
const current = readSettings(o.home);
|
|
2115
|
+
const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
|
|
2116
|
+
const statusLine = applyStatusLine(wired, o.statusLineCommand ?? sledsCommand("statusline", o.home, o.env));
|
|
1601
2117
|
writeSettings(o.home, statusLine.settings);
|
|
1602
|
-
wrote.push(`set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)}`);
|
|
2118
|
+
wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)} (everything through the proxy)` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o.home)}); \`sleds on\` switches to the proxy at a wall`);
|
|
1603
2119
|
wrote.push(statusLine.prior === undefined ? `set statusLine in ${settingsPath(o.home)}` : `set statusLine in ${settingsPath(o.home)} (previous one kept for uninstall)`);
|
|
1604
2120
|
hooks2({
|
|
1605
2121
|
home: o.home,
|
|
@@ -1619,24 +2135,47 @@ var init = async (o) => {
|
|
|
1619
2135
|
...prior,
|
|
1620
2136
|
token: resolved.token,
|
|
1621
2137
|
proxyUrl: url,
|
|
2138
|
+
wiring: finalWiring,
|
|
2139
|
+
...org ? { org } : {},
|
|
1622
2140
|
...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {}
|
|
1623
2141
|
});
|
|
1624
2142
|
wrote.push(`saved token and proxy URL to ${configPath(o.home)} (0600)`);
|
|
1625
2143
|
mkdirSync6(skillDir(o.home), { recursive: true });
|
|
1626
2144
|
writeFileSync6(skillPath(o.home), SKILL_MD);
|
|
1627
2145
|
wrote.push(`installed skill at ${skillPath(o.home)}`);
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2146
|
+
if (o.installMusher) {
|
|
2147
|
+
const r = o.installMusher();
|
|
2148
|
+
if (r)
|
|
2149
|
+
wrote.push(r);
|
|
2150
|
+
}
|
|
2151
|
+
const orgLine = org ? `${org.name} · ${org.plan}${org.capabilities.upstreamKey ? ` · ${org.capabilities.upstreamSource === "own" ? "org key" : "shared key"}` : " · no platform key"}` : "org unknown (proxy did not answer /proxy/meter)";
|
|
2152
|
+
if (finalWiring === "proxy-always") {
|
|
2153
|
+
log(`Claude Code now runs through SLEDS on the org key. (${orgLine})`);
|
|
2154
|
+
log("");
|
|
2155
|
+
for (const w of wrote)
|
|
2156
|
+
log(` ${w}`);
|
|
2157
|
+
log("");
|
|
2158
|
+
log(`Every request goes to ${url} and is metered to this seat. Run \`sleds status\` to check.`);
|
|
2159
|
+
log("The status line shows your meter: spend today, seat limits, window, allowance, queued jobs.");
|
|
2160
|
+
log("Prefer your seat first? `sleds init --seat-first` leaves Claude Code on it and switches only at a wall.");
|
|
2161
|
+
} else {
|
|
2162
|
+
log(`SLEDS is watching your seat. Nothing changes until a wall. (${orgLine})`);
|
|
2163
|
+
log("");
|
|
2164
|
+
for (const w of wrote)
|
|
2165
|
+
log(` ${w}`);
|
|
2166
|
+
log("");
|
|
2167
|
+
log("Claude Code keeps running on your seat; the status line reports its quota to SLEDS on every turn.");
|
|
2168
|
+
if (org && !org.capabilities.upstreamKey) {
|
|
2169
|
+
log("This org has no platform key, so at a wall the choices are hold until reset, `sleds defer` (this Mac resumes the session itself at the reset), or hand off to a teammate. An admin can add a key on the dashboard to unlock continue / overnight / switch.");
|
|
2170
|
+
} else {
|
|
2171
|
+
log("At a wall: `sleds on --until-reset +5h` (or /sled 1) switches to the org key through the proxy until the reset; `sleds off` puts you back.");
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
1636
2174
|
return 0;
|
|
1637
2175
|
};
|
|
1638
2176
|
|
|
1639
2177
|
// src/mcp.ts
|
|
2178
|
+
init_meter();
|
|
1640
2179
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
1641
2180
|
var SERVER_INFO = { name: "sleds", version: "0.0.1" };
|
|
1642
2181
|
var MCP_TIMEOUT_MS = 5000;
|
|
@@ -1992,8 +2531,17 @@ async function handoffsTool(deps) {
|
|
|
1992
2531
|
if (handoffs.length === 0)
|
|
1993
2532
|
return { text: "No open hand-offs on your team. Park one with the handoff tool." };
|
|
1994
2533
|
const now = (deps.now ?? Date.now)();
|
|
1995
|
-
|
|
1996
|
-
|
|
2534
|
+
const packets = handoffs.map((h) => str2(h.packet)).filter((p) => Boolean(p)).join(`
|
|
2535
|
+
|
|
2536
|
+
---
|
|
2537
|
+
|
|
2538
|
+
`);
|
|
2539
|
+
return {
|
|
2540
|
+
text: [handoffLines(handoffs, now).slice(1).join(`
|
|
2541
|
+
`), packets ? `
|
|
2542
|
+
${packets}` : ""].join(`
|
|
2543
|
+
`)
|
|
2544
|
+
};
|
|
1997
2545
|
}
|
|
1998
2546
|
async function handoffTool(deps, args) {
|
|
1999
2547
|
if (!meterSource(deps.home))
|
|
@@ -2161,6 +2709,8 @@ var mcp = async (o) => {
|
|
|
2161
2709
|
};
|
|
2162
2710
|
|
|
2163
2711
|
// src/commands/meter.ts
|
|
2712
|
+
init_config();
|
|
2713
|
+
init_meter();
|
|
2164
2714
|
var meter = async (o) => {
|
|
2165
2715
|
const log = o.log ?? console.log;
|
|
2166
2716
|
const now = (o.now ?? Date.now)();
|
|
@@ -2179,14 +2729,232 @@ var meter = async (o) => {
|
|
|
2179
2729
|
return 0;
|
|
2180
2730
|
};
|
|
2181
2731
|
|
|
2732
|
+
// src/commands/musher.ts
|
|
2733
|
+
init_config();
|
|
2734
|
+
init_meter();
|
|
2735
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2736
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2737
|
+
import { hostname as hostname2 } from "node:os";
|
|
2738
|
+
import { dirname as dirname3 } from "node:path";
|
|
2739
|
+
var defaultWhich3 = (cmd) => {
|
|
2740
|
+
const path = process.env.PATH ?? "";
|
|
2741
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
2742
|
+
const p = `${dir}/${cmd}`;
|
|
2743
|
+
if (existsSync8(p))
|
|
2744
|
+
return p;
|
|
2745
|
+
}
|
|
2746
|
+
return null;
|
|
2747
|
+
};
|
|
2748
|
+
var findClaude2 = (home, configured, which) => {
|
|
2749
|
+
if (configured && existsSync8(configured))
|
|
2750
|
+
return configured;
|
|
2751
|
+
const found = which("claude");
|
|
2752
|
+
if (found)
|
|
2753
|
+
return found;
|
|
2754
|
+
for (const p of [
|
|
2755
|
+
`${home}/.local/bin/claude`,
|
|
2756
|
+
"/opt/homebrew/bin/claude",
|
|
2757
|
+
"/usr/local/bin/claude",
|
|
2758
|
+
`${home}/.npm-global/bin/claude`
|
|
2759
|
+
])
|
|
2760
|
+
if (existsSync8(p))
|
|
2761
|
+
return p;
|
|
2762
|
+
return null;
|
|
2763
|
+
};
|
|
2764
|
+
var musherInstall = (o) => {
|
|
2765
|
+
const log = o.log ?? console.log;
|
|
2766
|
+
const error = o.error ?? console.error;
|
|
2767
|
+
const platform = o.platform ?? process.platform;
|
|
2768
|
+
if (platform !== "darwin") {
|
|
2769
|
+
error(`sleds musher --install needs launchd (macOS). On ${platform}, run \`sleds musher tick\` from cron every 5 minutes.`);
|
|
2770
|
+
return 1;
|
|
2771
|
+
}
|
|
2772
|
+
const env = o.env ?? process.env;
|
|
2773
|
+
const plist = musherPlistPath(o.home);
|
|
2774
|
+
const dir = configDir(o.home, env);
|
|
2775
|
+
mkdirSync7(dirname3(plist), { recursive: true });
|
|
2776
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2777
|
+
const execPath = o.sledsArgv[0] ?? process.execPath;
|
|
2778
|
+
const agentEnv = {
|
|
2779
|
+
PATH: musherPath(o.home, execPath, env.PATH ?? ""),
|
|
2780
|
+
HOME: o.home,
|
|
2781
|
+
...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
|
|
2782
|
+
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
2783
|
+
};
|
|
2784
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2785
|
+
if (existsSync8(plist))
|
|
2786
|
+
exec("launchctl", ["unload", plist]);
|
|
2787
|
+
writeFileSync7(plist, buildMusherPlist({ sledsArgv: o.sledsArgv, env: agentEnv, logPath: musherLogPath(dir) }));
|
|
2788
|
+
const r = exec("launchctl", ["load", plist]);
|
|
2789
|
+
if (!r.ok) {
|
|
2790
|
+
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
2791
|
+
return 1;
|
|
2792
|
+
}
|
|
2793
|
+
const cfg = readConfig(o.home);
|
|
2794
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
|
|
2795
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2796
|
+
log(`musher installed: launchd agent ${MUSHER_LABEL}, every ${5} min · log ${musherLogPath(dir)}`);
|
|
2797
|
+
if (!claude)
|
|
2798
|
+
log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
|
|
2799
|
+
return 0;
|
|
2800
|
+
};
|
|
2801
|
+
var musherUninstall = (o) => {
|
|
2802
|
+
const log = o.log ?? console.log;
|
|
2803
|
+
const plist = musherPlistPath(o.home);
|
|
2804
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2805
|
+
if (existsSync8(plist)) {
|
|
2806
|
+
exec("launchctl", ["unload", plist]);
|
|
2807
|
+
rmSync2(plist, { force: true });
|
|
2808
|
+
log(`musher removed: ${MUSHER_LABEL}`);
|
|
2809
|
+
} else
|
|
2810
|
+
log("musher was not installed.");
|
|
2811
|
+
const cfg = readConfig(o.home);
|
|
2812
|
+
if (cfg.musher)
|
|
2813
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
|
|
2814
|
+
return 0;
|
|
2815
|
+
};
|
|
2816
|
+
var formatMusherStatus = (list, now) => {
|
|
2817
|
+
const lines = [];
|
|
2818
|
+
for (const j of list.running)
|
|
2819
|
+
lines.push(`running ${j.jobId}${j.startedAt ? ` · since ${formatLocal(j.startedAt)} (${formatDuration(now - j.startedAt)})` : ""}`);
|
|
2820
|
+
for (const j of list.due)
|
|
2821
|
+
lines.push(`due now ${j.jobId}${j.model ? ` · ${j.model}` : ""} · was ${formatLocal(j.scheduledAt)}`);
|
|
2822
|
+
for (const j of list.upcoming)
|
|
2823
|
+
lines.push(`waiting ${j.jobId}${j.model ? ` · ${j.model}` : ""} · starts ${formatLocal(j.scheduledAt)} (in ${formatDuration(j.scheduledAt - now)})`);
|
|
2824
|
+
if (lines.length === 0)
|
|
2825
|
+
lines.push("nothing deferred on this seat.");
|
|
2826
|
+
return lines;
|
|
2827
|
+
};
|
|
2828
|
+
var clientFor = (o) => {
|
|
2829
|
+
if (o.client)
|
|
2830
|
+
return o.client;
|
|
2831
|
+
const src = meterSource(o.home);
|
|
2832
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
2833
|
+
};
|
|
2834
|
+
var musherStatus = async (o) => {
|
|
2835
|
+
const log = o.log ?? console.log;
|
|
2836
|
+
const now = (o.now ?? Date.now)();
|
|
2837
|
+
const plist = musherPlistPath(o.home);
|
|
2838
|
+
const cfg = readConfig(o.home);
|
|
2839
|
+
log(existsSync8(plist) ? `agent ${MUSHER_LABEL} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
2840
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2841
|
+
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
2842
|
+
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2843
|
+
const client = clientFor(o);
|
|
2844
|
+
if (!client) {
|
|
2845
|
+
log("seat not initialised (sleds init)");
|
|
2846
|
+
return 1;
|
|
2847
|
+
}
|
|
2848
|
+
const list = await client.list();
|
|
2849
|
+
if (!list) {
|
|
2850
|
+
log("proxy unreachable");
|
|
2851
|
+
return 1;
|
|
2852
|
+
}
|
|
2853
|
+
for (const line of formatMusherStatus(list, now))
|
|
2854
|
+
log(line);
|
|
2855
|
+
return 0;
|
|
2856
|
+
};
|
|
2857
|
+
var musherTick = async (o) => {
|
|
2858
|
+
const log = o.log ?? console.log;
|
|
2859
|
+
const client = clientFor(o);
|
|
2860
|
+
if (!client) {
|
|
2861
|
+
log("musher: seat not initialised; nothing to do");
|
|
2862
|
+
return 1;
|
|
2863
|
+
}
|
|
2864
|
+
const list = await client.list();
|
|
2865
|
+
if (!list) {
|
|
2866
|
+
log("musher: proxy unreachable; will try again next tick");
|
|
2867
|
+
return 0;
|
|
2868
|
+
}
|
|
2869
|
+
if (list.due.length === 0)
|
|
2870
|
+
return 0;
|
|
2871
|
+
const cfg = readConfig(o.home);
|
|
2872
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3) ?? "claude";
|
|
2873
|
+
const seat = readSeatState(o.home);
|
|
2874
|
+
for (const job of list.due) {
|
|
2875
|
+
const out = await runJob({
|
|
2876
|
+
job,
|
|
2877
|
+
client,
|
|
2878
|
+
fallbackSession: seat.session,
|
|
2879
|
+
host: o.host ?? hostname2(),
|
|
2880
|
+
claudePath: claude,
|
|
2881
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
2882
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
2883
|
+
exec: o.execCapture,
|
|
2884
|
+
log
|
|
2885
|
+
});
|
|
2886
|
+
if (out.kind === "skipped")
|
|
2887
|
+
log(`musher: job ${job.jobId} skipped — ${out.reason}`);
|
|
2888
|
+
}
|
|
2889
|
+
return 0;
|
|
2890
|
+
};
|
|
2891
|
+
var musherConfigure = (o, flags) => {
|
|
2892
|
+
const log = o.log ?? console.log;
|
|
2893
|
+
const cfg = readConfig(o.home);
|
|
2894
|
+
const musher = { ...cfg.musher ?? {} };
|
|
2895
|
+
if (flags.claude)
|
|
2896
|
+
musher.claudePath = flags.claude;
|
|
2897
|
+
if (flags.mode)
|
|
2898
|
+
musher.permissionMode = flags.mode;
|
|
2899
|
+
if (flags.timeout) {
|
|
2900
|
+
const n = Number.parseInt(flags.timeout, 10);
|
|
2901
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
2902
|
+
(o.error ?? console.error)("--timeout takes minutes, a positive number");
|
|
2903
|
+
return 1;
|
|
2904
|
+
}
|
|
2905
|
+
musher.timeoutMinutes = n;
|
|
2906
|
+
}
|
|
2907
|
+
writeConfig(o.home, { ...cfg, musher });
|
|
2908
|
+
log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2909
|
+
return 0;
|
|
2910
|
+
};
|
|
2911
|
+
var defaultExecSync = (cmd, args) => {
|
|
2912
|
+
const r = spawnSync2(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
2913
|
+
if (r.error)
|
|
2914
|
+
return { ok: false, stderr: r.error.message };
|
|
2915
|
+
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
2916
|
+
};
|
|
2917
|
+
var musher = async (o) => {
|
|
2918
|
+
const args = [...o.argv];
|
|
2919
|
+
const flags = {};
|
|
2920
|
+
const positional = [];
|
|
2921
|
+
for (let i = 0;i < args.length; i++) {
|
|
2922
|
+
const a = args[i] ?? "";
|
|
2923
|
+
if (a.startsWith("--")) {
|
|
2924
|
+
const next = args[i + 1];
|
|
2925
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
2926
|
+
flags[a.slice(2)] = next;
|
|
2927
|
+
i++;
|
|
2928
|
+
} else
|
|
2929
|
+
flags[a.slice(2)] = "true";
|
|
2930
|
+
} else
|
|
2931
|
+
positional.push(a);
|
|
2932
|
+
}
|
|
2933
|
+
if (flags.install)
|
|
2934
|
+
return musherInstall(o);
|
|
2935
|
+
if (flags.uninstall)
|
|
2936
|
+
return musherUninstall(o);
|
|
2937
|
+
if (flags.claude || flags.mode || flags.timeout)
|
|
2938
|
+
return musherConfigure(o, flags);
|
|
2939
|
+
if (positional[0] === "tick")
|
|
2940
|
+
return musherTick(o);
|
|
2941
|
+
if (flags.status || positional.length === 0)
|
|
2942
|
+
return musherStatus(o);
|
|
2943
|
+
(o.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
|
|
2944
|
+
return 1;
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
// src/commands/off.ts
|
|
2948
|
+
init_config();
|
|
2949
|
+
|
|
2182
2950
|
// src/schedule.ts
|
|
2183
|
-
import { existsSync as
|
|
2184
|
-
import { join as
|
|
2951
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
2952
|
+
import { join as join7 } from "node:path";
|
|
2185
2953
|
var REVERT_LABEL = "ai.sleds.revert";
|
|
2186
|
-
var plistPath = (home) =>
|
|
2187
|
-
var
|
|
2954
|
+
var plistPath = (home) => join7(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
|
|
2955
|
+
var xmlEscape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2188
2956
|
var buildLaunchdPlist = (sledsArgv, deadline) => {
|
|
2189
|
-
const args = [...sledsArgv, "off"].map((a) => ` <string>${
|
|
2957
|
+
const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
|
|
2190
2958
|
`);
|
|
2191
2959
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2192
2960
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2222,9 +2990,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2222
2990
|
if (d.platform === "darwin") {
|
|
2223
2991
|
const p = plistPath(d.home);
|
|
2224
2992
|
try {
|
|
2225
|
-
|
|
2993
|
+
mkdirSync8(join7(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
2226
2994
|
d.exec("launchctl", ["unload", p]);
|
|
2227
|
-
|
|
2995
|
+
writeFileSync8(p, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
2228
2996
|
const r = d.exec("launchctl", ["load", p]);
|
|
2229
2997
|
if (r.ok)
|
|
2230
2998
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
@@ -2251,9 +3019,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2251
3019
|
var cancelRevert = (d, kind) => {
|
|
2252
3020
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
2253
3021
|
const p = plistPath(d.home);
|
|
2254
|
-
if (
|
|
3022
|
+
if (existsSync9(p)) {
|
|
2255
3023
|
d.exec("launchctl", ["unload", p]);
|
|
2256
|
-
|
|
3024
|
+
rmSync3(p, { force: true });
|
|
2257
3025
|
}
|
|
2258
3026
|
}
|
|
2259
3027
|
if (kind === "systemd" || d.platform === "linux") {
|
|
@@ -2262,6 +3030,7 @@ var cancelRevert = (d, kind) => {
|
|
|
2262
3030
|
};
|
|
2263
3031
|
|
|
2264
3032
|
// src/commands/off.ts
|
|
3033
|
+
init_settings();
|
|
2265
3034
|
var off = (o) => {
|
|
2266
3035
|
const log = o.log ?? console.log;
|
|
2267
3036
|
const cfg = readConfig(o.home);
|
|
@@ -2289,7 +3058,13 @@ var off = (o) => {
|
|
|
2289
3058
|
return 0;
|
|
2290
3059
|
};
|
|
2291
3060
|
|
|
3061
|
+
// src/commands/on.ts
|
|
3062
|
+
init_config();
|
|
3063
|
+
init_settings();
|
|
3064
|
+
|
|
2292
3065
|
// src/commands/init.ts
|
|
3066
|
+
init_config();
|
|
3067
|
+
init_settings();
|
|
2293
3068
|
var DEFAULT_PROXY_URL2 = "http://localhost:8787";
|
|
2294
3069
|
|
|
2295
3070
|
// src/commands/on.ts
|
|
@@ -2312,6 +3087,10 @@ var on = (o) => {
|
|
|
2312
3087
|
error("Saved token is not a sled_ token. Run `sleds init --token <sled_…>`.");
|
|
2313
3088
|
return 2;
|
|
2314
3089
|
}
|
|
3090
|
+
if (cfg.org && !cfg.org.capabilities.upstreamKey) {
|
|
3091
|
+
error(`${cfg.org.name} has no platform key, so there is no org key to switch to. Hold until the reset, or hand off with /sled 5. An admin can add a key on the dashboard.`);
|
|
3092
|
+
return 2;
|
|
3093
|
+
}
|
|
2315
3094
|
const url = (o.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
|
|
2316
3095
|
let untilResetMs;
|
|
2317
3096
|
if (o.untilReset !== undefined) {
|
|
@@ -2365,7 +3144,70 @@ var on = (o) => {
|
|
|
2365
3144
|
return 0;
|
|
2366
3145
|
};
|
|
2367
3146
|
|
|
3147
|
+
// src/commands/run.ts
|
|
3148
|
+
init_config();
|
|
3149
|
+
init_meter();
|
|
3150
|
+
import { hostname as hostname3 } from "node:os";
|
|
3151
|
+
var run = async (o) => {
|
|
3152
|
+
const log = o.log ?? console.log;
|
|
3153
|
+
const error = o.error ?? console.error;
|
|
3154
|
+
if (!o.jobId) {
|
|
3155
|
+
error("usage: sleds run <jobId>");
|
|
3156
|
+
return 2;
|
|
3157
|
+
}
|
|
3158
|
+
const client = o.client ?? (() => {
|
|
3159
|
+
const src = meterSource(o.home);
|
|
3160
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
3161
|
+
})();
|
|
3162
|
+
if (!client) {
|
|
3163
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
3164
|
+
return 1;
|
|
3165
|
+
}
|
|
3166
|
+
const list = await client.list();
|
|
3167
|
+
if (!list) {
|
|
3168
|
+
error("Could not reach the SLEDS proxy.");
|
|
3169
|
+
return 1;
|
|
3170
|
+
}
|
|
3171
|
+
const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o.jobId);
|
|
3172
|
+
if (!job) {
|
|
3173
|
+
error(`Job ${o.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
|
|
3174
|
+
return 1;
|
|
3175
|
+
}
|
|
3176
|
+
const cfg = readConfig(o.home);
|
|
3177
|
+
const claude = findClaude(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich);
|
|
3178
|
+
if (!claude) {
|
|
3179
|
+
error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
|
|
3180
|
+
return 1;
|
|
3181
|
+
}
|
|
3182
|
+
const out = await runJob({
|
|
3183
|
+
job,
|
|
3184
|
+
client,
|
|
3185
|
+
fallbackSession: readSeatState(o.home).session,
|
|
3186
|
+
host: o.host ?? hostname3(),
|
|
3187
|
+
claudePath: claude,
|
|
3188
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
3189
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
3190
|
+
exec: o.execCapture,
|
|
3191
|
+
log
|
|
3192
|
+
});
|
|
3193
|
+
if (out.kind === "skipped") {
|
|
3194
|
+
error(`Not run: ${out.reason}`);
|
|
3195
|
+
return 1;
|
|
3196
|
+
}
|
|
3197
|
+
log(out.summary);
|
|
3198
|
+
if (out.ok) {
|
|
3199
|
+
const a = await client.attestation(job.jobId);
|
|
3200
|
+
if (a)
|
|
3201
|
+
log(a.attestation.trailer);
|
|
3202
|
+
else
|
|
3203
|
+
log(`(attestation pending — sleds verify ${job.jobId})`);
|
|
3204
|
+
}
|
|
3205
|
+
return out.ok ? 0 : 1;
|
|
3206
|
+
};
|
|
3207
|
+
|
|
2368
3208
|
// src/commands/seatEvent.ts
|
|
3209
|
+
init_config();
|
|
3210
|
+
init_meter();
|
|
2369
3211
|
var HOOK_STDIN_TIMEOUT_MS = 1000;
|
|
2370
3212
|
var NOTIFICATION_TITLE = "sleds";
|
|
2371
3213
|
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -2407,25 +3249,26 @@ var seatEvent = async (o) => {
|
|
|
2407
3249
|
if (week && week.resetsAt > now)
|
|
2408
3250
|
writeSeatState(o.home, { ...state, lastWallResetsAt: week.resetsAt });
|
|
2409
3251
|
} catch {}
|
|
2410
|
-
|
|
3252
|
+
const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
|
|
3253
|
+
log(line);
|
|
2411
3254
|
if ((o.platform ?? process.platform) === "darwin" && o.exec) {
|
|
2412
3255
|
try {
|
|
2413
|
-
o.exec("osascript", osascriptArgs(NOTIFICATION_TITLE,
|
|
3256
|
+
o.exec("osascript", osascriptArgs(NOTIFICATION_TITLE, line));
|
|
2414
3257
|
} catch {}
|
|
2415
3258
|
}
|
|
2416
3259
|
return 0;
|
|
2417
3260
|
};
|
|
2418
3261
|
|
|
2419
3262
|
// src/commands/seatSync.ts
|
|
2420
|
-
import { execFileSync as
|
|
2421
|
-
import { existsSync as
|
|
3263
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3264
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
2422
3265
|
import { platform } from "node:os";
|
|
2423
|
-
import { join as
|
|
3266
|
+
import { join as join9 } from "node:path";
|
|
2424
3267
|
|
|
2425
3268
|
// src/cookieDb.ts
|
|
2426
|
-
import { copyFileSync, rmSync as
|
|
3269
|
+
import { copyFileSync, rmSync as rmSync4 } from "node:fs";
|
|
2427
3270
|
import { tmpdir } from "node:os";
|
|
2428
|
-
import { join as
|
|
3271
|
+
import { join as join8 } from "node:path";
|
|
2429
3272
|
var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
|
|
2430
3273
|
var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
2431
3274
|
async function queryRows(dbPath) {
|
|
@@ -2454,18 +3297,21 @@ function nodeSqlite() {
|
|
|
2454
3297
|
return mod;
|
|
2455
3298
|
}
|
|
2456
3299
|
async function readCookieRows(dbPath) {
|
|
2457
|
-
const copy =
|
|
3300
|
+
const copy = join8(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
|
|
2458
3301
|
copyFileSync(dbPath, copy);
|
|
2459
3302
|
try {
|
|
2460
3303
|
const rows = await queryRows(copy);
|
|
2461
3304
|
return rows.map((r) => ({ name: r.name, encrypted_value: Buffer.from(r.ev, "hex") }));
|
|
2462
3305
|
} finally {
|
|
2463
3306
|
try {
|
|
2464
|
-
|
|
3307
|
+
rmSync4(copy, { force: true });
|
|
2465
3308
|
} catch {}
|
|
2466
3309
|
}
|
|
2467
3310
|
}
|
|
2468
3311
|
|
|
3312
|
+
// src/commands/seatSync.ts
|
|
3313
|
+
init_meter();
|
|
3314
|
+
|
|
2469
3315
|
// src/seatSync.ts
|
|
2470
3316
|
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
2471
3317
|
var BROWSERS = {
|
|
@@ -2611,13 +3457,13 @@ function parseUsageResponse(raw, now = Date.now()) {
|
|
|
2611
3457
|
const resetsAt = parseResetsAt(v.resets_at);
|
|
2612
3458
|
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
2613
3459
|
continue;
|
|
2614
|
-
const
|
|
3460
|
+
const bar = { usedPct, resetsAt };
|
|
2615
3461
|
if (k === "five_hour")
|
|
2616
|
-
out.fiveHour =
|
|
3462
|
+
out.fiveHour = bar;
|
|
2617
3463
|
else if (k === "seven_day")
|
|
2618
|
-
out.sevenDay =
|
|
3464
|
+
out.sevenDay = bar;
|
|
2619
3465
|
else
|
|
2620
|
-
out.models[k] =
|
|
3466
|
+
out.models[k] = bar;
|
|
2621
3467
|
}
|
|
2622
3468
|
const hasAny = out.fiveHour || out.sevenDay || Object.keys(out.models).length > 0 || out.usedCreditsUsd !== undefined;
|
|
2623
3469
|
return hasAny ? out : null;
|
|
@@ -2727,7 +3573,7 @@ function findFile(root, name) {
|
|
|
2727
3573
|
const stack = [root];
|
|
2728
3574
|
while (stack.length > 0) {
|
|
2729
3575
|
const dir = stack.pop();
|
|
2730
|
-
if (!dir || !
|
|
3576
|
+
if (!dir || !existsSync10(dir))
|
|
2731
3577
|
continue;
|
|
2732
3578
|
let entries;
|
|
2733
3579
|
try {
|
|
@@ -2736,7 +3582,7 @@ function findFile(root, name) {
|
|
|
2736
3582
|
continue;
|
|
2737
3583
|
}
|
|
2738
3584
|
for (const e of entries) {
|
|
2739
|
-
const p =
|
|
3585
|
+
const p = join9(dir, e.name);
|
|
2740
3586
|
if (e.isDirectory())
|
|
2741
3587
|
stack.push(p);
|
|
2742
3588
|
else if (e.name === name)
|
|
@@ -2747,8 +3593,8 @@ function findFile(root, name) {
|
|
|
2747
3593
|
}
|
|
2748
3594
|
function findCookieDb(home, source) {
|
|
2749
3595
|
const b = BROWSERS[source.browser];
|
|
2750
|
-
const root =
|
|
2751
|
-
if (!
|
|
3596
|
+
const root = join9(home, APP_SUPPORT, b.appDir);
|
|
3597
|
+
if (!existsSync10(root))
|
|
2752
3598
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
2753
3599
|
if (!b.profiled) {
|
|
2754
3600
|
const db2 = findFile(root, "Cookies");
|
|
@@ -2756,15 +3602,15 @@ function findCookieDb(home, source) {
|
|
|
2756
3602
|
throw new Error(`no cookie store under ${root}`);
|
|
2757
3603
|
return db2;
|
|
2758
3604
|
}
|
|
2759
|
-
const localStatePath =
|
|
2760
|
-
const localState =
|
|
3605
|
+
const localStatePath = join9(root, "Local State");
|
|
3606
|
+
const localState = existsSync10(localStatePath) ? readFileSync8(localStatePath, "utf8") : null;
|
|
2761
3607
|
const wanted = source.profile ?? "Default";
|
|
2762
3608
|
const { dir, ambiguous } = resolveProfileDir(localState, wanted);
|
|
2763
3609
|
if (ambiguous)
|
|
2764
3610
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
2765
3611
|
if (!dir)
|
|
2766
3612
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
2767
|
-
const db = cookieDbCandidates(
|
|
3613
|
+
const db = cookieDbCandidates(join9(root, dir)).find((p) => existsSync10(p));
|
|
2768
3614
|
if (!db)
|
|
2769
3615
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
2770
3616
|
return db;
|
|
@@ -2774,7 +3620,7 @@ async function readCookieRowsBun(home, source) {
|
|
|
2774
3620
|
}
|
|
2775
3621
|
function keychainKey(service) {
|
|
2776
3622
|
try {
|
|
2777
|
-
return
|
|
3623
|
+
return execFileSync2("security", ["find-generic-password", "-w", "-s", service], {
|
|
2778
3624
|
encoding: "utf8"
|
|
2779
3625
|
}).trim();
|
|
2780
3626
|
} catch {
|
|
@@ -2786,7 +3632,7 @@ function decryptJar(rows, safeStoragePw) {
|
|
|
2786
3632
|
}
|
|
2787
3633
|
var curlUsage = (org, cookie) => {
|
|
2788
3634
|
try {
|
|
2789
|
-
const out =
|
|
3635
|
+
const out = execFileSync2("curl", usageCurlArgs(org, cookie), {
|
|
2790
3636
|
encoding: "utf8",
|
|
2791
3637
|
maxBuffer: 8000000
|
|
2792
3638
|
});
|
|
@@ -2795,8 +3641,8 @@ var curlUsage = (org, cookie) => {
|
|
|
2795
3641
|
return { status: 0, body: "" };
|
|
2796
3642
|
}
|
|
2797
3643
|
};
|
|
2798
|
-
function fetchSeatUsage(jar, org, now,
|
|
2799
|
-
const { status, body } =
|
|
3644
|
+
function fetchSeatUsage(jar, org, now, run2) {
|
|
3645
|
+
const { status, body } = run2(org, cookieHeader(jar, org));
|
|
2800
3646
|
if (status !== 200)
|
|
2801
3647
|
return { usage: null, status };
|
|
2802
3648
|
try {
|
|
@@ -2807,7 +3653,7 @@ function fetchSeatUsage(jar, org, now, run) {
|
|
|
2807
3653
|
}
|
|
2808
3654
|
function launchAgentPlist(bun, script, home, source) {
|
|
2809
3655
|
const label = launchLabel(source);
|
|
2810
|
-
const log =
|
|
3656
|
+
const log = join9(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
|
|
2811
3657
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2812
3658
|
const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
|
|
2813
3659
|
`);
|
|
@@ -2829,32 +3675,32 @@ ${args}
|
|
|
2829
3675
|
`;
|
|
2830
3676
|
}
|
|
2831
3677
|
function plistPath2(home, source) {
|
|
2832
|
-
return
|
|
3678
|
+
return join9(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
|
|
2833
3679
|
}
|
|
2834
|
-
function
|
|
3680
|
+
function install(home, argv, source, log) {
|
|
2835
3681
|
const bun = argv[0] ?? process.execPath;
|
|
2836
3682
|
const script = argv[1] ?? "";
|
|
2837
3683
|
if (!script) {
|
|
2838
3684
|
log("cannot resolve the sleds script path to install the timer");
|
|
2839
3685
|
return 1;
|
|
2840
3686
|
}
|
|
2841
|
-
const dir =
|
|
2842
|
-
|
|
3687
|
+
const dir = join9(home, "Library", "LaunchAgents");
|
|
3688
|
+
mkdirSync9(dir, { recursive: true });
|
|
2843
3689
|
const path = plistPath2(home, source);
|
|
2844
|
-
|
|
3690
|
+
writeFileSync9(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
2845
3691
|
try {
|
|
2846
|
-
|
|
3692
|
+
execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
|
|
2847
3693
|
} catch {}
|
|
2848
|
-
|
|
3694
|
+
execFileSync2("launchctl", ["load", "-w", path]);
|
|
2849
3695
|
log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
|
|
2850
3696
|
return 0;
|
|
2851
3697
|
}
|
|
2852
|
-
function
|
|
3698
|
+
function uninstall(home, source, log) {
|
|
2853
3699
|
const path = plistPath2(home, source);
|
|
2854
3700
|
try {
|
|
2855
|
-
|
|
3701
|
+
execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
2856
3702
|
} catch {}
|
|
2857
|
-
log(
|
|
3703
|
+
log(existsSync10(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
2858
3704
|
return 0;
|
|
2859
3705
|
}
|
|
2860
3706
|
var seatSync = async (o) => {
|
|
@@ -2867,9 +3713,9 @@ var seatSync = async (o) => {
|
|
|
2867
3713
|
}
|
|
2868
3714
|
const { source } = parsed;
|
|
2869
3715
|
if (source.uninstall)
|
|
2870
|
-
return
|
|
3716
|
+
return uninstall(o.home, source, log);
|
|
2871
3717
|
if (source.install)
|
|
2872
|
-
return
|
|
3718
|
+
return install(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
2873
3719
|
if (platform() !== "darwin" && !o.readCookieRows) {
|
|
2874
3720
|
log("seat-sync is macOS-only for now.");
|
|
2875
3721
|
return 2;
|
|
@@ -2949,7 +3795,8 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
|
2949
3795
|
}
|
|
2950
3796
|
|
|
2951
3797
|
// src/commands/specEvent.ts
|
|
2952
|
-
|
|
3798
|
+
init_meter();
|
|
3799
|
+
import { readFileSync as readFileSync9, statSync } from "node:fs";
|
|
2953
3800
|
import { basename, isAbsolute, relative } from "node:path";
|
|
2954
3801
|
var HOOK_STDIN_TIMEOUT_MS2 = 1000;
|
|
2955
3802
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
@@ -3005,7 +3852,7 @@ function readFileOrNull(path) {
|
|
|
3005
3852
|
try {
|
|
3006
3853
|
if (statSync(path).size > 2000000)
|
|
3007
3854
|
return null;
|
|
3008
|
-
return
|
|
3855
|
+
return readFileSync9(path, "utf8");
|
|
3009
3856
|
} catch {
|
|
3010
3857
|
return null;
|
|
3011
3858
|
}
|
|
@@ -3048,7 +3895,9 @@ var specEvent = async (o) => {
|
|
|
3048
3895
|
};
|
|
3049
3896
|
|
|
3050
3897
|
// src/commands/status.ts
|
|
3051
|
-
|
|
3898
|
+
init_config();
|
|
3899
|
+
init_settings();
|
|
3900
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3052
3901
|
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
3053
3902
|
try {
|
|
3054
3903
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -3107,6 +3956,10 @@ var formatStatus = (s) => {
|
|
|
3107
3956
|
}
|
|
3108
3957
|
if (s.email)
|
|
3109
3958
|
lines.push(row2("Seat:", s.email));
|
|
3959
|
+
if (s.org)
|
|
3960
|
+
lines.push(row2("Org:", `${s.org.name} · ${s.org.plan} · ${s.org.upstream === "none" ? "no platform key (walls: hold or hand off)" : s.org.upstream === "own" ? "org key" : "shared key"}`));
|
|
3961
|
+
if (s.wiring)
|
|
3962
|
+
lines.push(row2("Wiring:", s.wiring === "seat-first" ? "seat-first — Claude Code on your seat; the proxy only after `sleds on`" : "proxy-always — every request through the proxy"));
|
|
3110
3963
|
if (s.mode === "off") {
|
|
3111
3964
|
lines.push(s.pointed ? "Claude Code is pointed at the proxy by settings.json (set by `sleds init`)." : "Claude Code is on your seat. Hit a limit? `sleds on --until-reset +5h`.");
|
|
3112
3965
|
}
|
|
@@ -3116,7 +3969,7 @@ var status = async (o) => {
|
|
|
3116
3969
|
const log = o.log ?? console.log;
|
|
3117
3970
|
const nowMs = (o.now ?? Date.now)();
|
|
3118
3971
|
const cfg = readConfig(o.home);
|
|
3119
|
-
const settings =
|
|
3972
|
+
const settings = existsSync11(settingsPath(o.home)) ? readSettings(o.home) : {};
|
|
3120
3973
|
const inSettings = readProxy(settings);
|
|
3121
3974
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
3122
3975
|
const token = inSettings?.token ?? cfg.token;
|
|
@@ -3138,6 +3991,14 @@ var status = async (o) => {
|
|
|
3138
3991
|
info.tokenMasked = maskToken(token);
|
|
3139
3992
|
if (cfg.email)
|
|
3140
3993
|
info.email = cfg.email;
|
|
3994
|
+
if (cfg.wiring)
|
|
3995
|
+
info.wiring = cfg.wiring;
|
|
3996
|
+
if (cfg.org)
|
|
3997
|
+
info.org = {
|
|
3998
|
+
name: cfg.org.name,
|
|
3999
|
+
plan: cfg.org.plan,
|
|
4000
|
+
upstream: cfg.org.capabilities.upstreamSource
|
|
4001
|
+
};
|
|
3141
4002
|
if (cfg.reason)
|
|
3142
4003
|
info.reason = cfg.reason;
|
|
3143
4004
|
if (o.tz)
|
|
@@ -3162,15 +4023,19 @@ var status = async (o) => {
|
|
|
3162
4023
|
};
|
|
3163
4024
|
|
|
3164
4025
|
// src/commands/statusline.ts
|
|
4026
|
+
init_meter();
|
|
3165
4027
|
var statusline = async (o) => {
|
|
3166
4028
|
const log = o.log ?? console.log;
|
|
3167
4029
|
const now = (o.now ?? Date.now)();
|
|
3168
4030
|
const input = o.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
|
|
3169
4031
|
const window = seatWindowFromStdin(input, now);
|
|
4032
|
+
const session = seatSessionFromStdin(input, now);
|
|
3170
4033
|
let posts = [];
|
|
3171
4034
|
try {
|
|
3172
4035
|
if (window)
|
|
3173
|
-
posts = rememberSeat(o, window, now);
|
|
4036
|
+
posts = rememberSeat(o, window, session, now);
|
|
4037
|
+
else if (session)
|
|
4038
|
+
writeSeatState(o.home, { ...readSeatState(o.home), session });
|
|
3174
4039
|
} catch {}
|
|
3175
4040
|
const seatFragment = formatSeatFragment(window);
|
|
3176
4041
|
let line;
|
|
@@ -3189,9 +4054,9 @@ var statusline = async (o) => {
|
|
|
3189
4054
|
await Promise.allSettled(posts);
|
|
3190
4055
|
return 0;
|
|
3191
4056
|
};
|
|
3192
|
-
var rememberSeat = (o, window, now) => {
|
|
4057
|
+
var rememberSeat = (o, window, session, now) => {
|
|
3193
4058
|
const prior = readSeatState(o.home);
|
|
3194
|
-
let state = { ...prior, window };
|
|
4059
|
+
let state = { ...prior, window, ...session ? { session } : {} };
|
|
3195
4060
|
const src = meterSource(o.home);
|
|
3196
4061
|
const posts = [];
|
|
3197
4062
|
if (src) {
|
|
@@ -3214,11 +4079,18 @@ var rememberSeat = (o, window, now) => {
|
|
|
3214
4079
|
};
|
|
3215
4080
|
|
|
3216
4081
|
// src/commands/uninstall.ts
|
|
3217
|
-
|
|
3218
|
-
|
|
4082
|
+
init_config();
|
|
4083
|
+
init_meter();
|
|
4084
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4085
|
+
import { existsSync as existsSync12, rmSync as rmSync5 } from "node:fs";
|
|
4086
|
+
init_settings();
|
|
4087
|
+
var defaultUnload = (plist) => {
|
|
4088
|
+
spawnSync3("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
4089
|
+
};
|
|
4090
|
+
var uninstall2 = (o) => {
|
|
3219
4091
|
const log = o.log ?? console.log;
|
|
3220
4092
|
const removed = [];
|
|
3221
|
-
if (
|
|
4093
|
+
if (existsSync12(settingsPath(o.home))) {
|
|
3222
4094
|
let settings = readSettings(o.home);
|
|
3223
4095
|
let changed = false;
|
|
3224
4096
|
if (hasEitherKey(settings)) {
|
|
@@ -3250,16 +4122,22 @@ var uninstall3 = (o) => {
|
|
|
3250
4122
|
writeSettings(o.home, settings);
|
|
3251
4123
|
}
|
|
3252
4124
|
for (const file of [meterCachePath(o.home), seatStatePath(o.home)]) {
|
|
3253
|
-
if (
|
|
3254
|
-
|
|
4125
|
+
if (existsSync12(file)) {
|
|
4126
|
+
rmSync5(file, { force: true });
|
|
3255
4127
|
removed.push(file);
|
|
3256
4128
|
}
|
|
3257
4129
|
}
|
|
3258
4130
|
const skill = skillDir(o.home);
|
|
3259
|
-
if (
|
|
3260
|
-
|
|
4131
|
+
if (existsSync12(skill)) {
|
|
4132
|
+
rmSync5(skill, { recursive: true, force: true });
|
|
3261
4133
|
removed.push(skill);
|
|
3262
4134
|
}
|
|
4135
|
+
const plist = musherPlistPath(o.home);
|
|
4136
|
+
if (existsSync12(plist)) {
|
|
4137
|
+
(o.unloadAgent ?? defaultUnload)(plist);
|
|
4138
|
+
rmSync5(plist, { force: true });
|
|
4139
|
+
removed.push(`launchd agent ${MUSHER_LABEL}`);
|
|
4140
|
+
}
|
|
3263
4141
|
if (removed.length === 0) {
|
|
3264
4142
|
log("Nothing to remove; SLEDS was not installed.");
|
|
3265
4143
|
} else {
|
|
@@ -3277,10 +4155,58 @@ var hasEitherKey = (settings) => {
|
|
|
3277
4155
|
return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
|
|
3278
4156
|
};
|
|
3279
4157
|
|
|
4158
|
+
// src/commands/verify.ts
|
|
4159
|
+
init_meter();
|
|
4160
|
+
var jobIdFromTarget = (target) => {
|
|
4161
|
+
const t = target.trim();
|
|
4162
|
+
const m = /SLEDS-Attest:.*\bjob=([^\s]+)/.exec(t);
|
|
4163
|
+
if (m?.[1])
|
|
4164
|
+
return m[1];
|
|
4165
|
+
return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
|
|
4166
|
+
};
|
|
4167
|
+
var verify = async (o) => {
|
|
4168
|
+
const log = o.log ?? console.log;
|
|
4169
|
+
const error = o.error ?? console.error;
|
|
4170
|
+
const jobId = o.target ? jobIdFromTarget(o.target) : null;
|
|
4171
|
+
if (!jobId) {
|
|
4172
|
+
error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
|
|
4173
|
+
return 2;
|
|
4174
|
+
}
|
|
4175
|
+
const client = o.client ?? (() => {
|
|
4176
|
+
const src = meterSource(o.home);
|
|
4177
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
4178
|
+
})();
|
|
4179
|
+
if (!client) {
|
|
4180
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
4181
|
+
return 1;
|
|
4182
|
+
}
|
|
4183
|
+
const a = await client.attestation(jobId);
|
|
4184
|
+
if (!a) {
|
|
4185
|
+
error(`No attestation for ${jobId} (not delivered, not signed, not your org, or the proxy is down).`);
|
|
4186
|
+
return 1;
|
|
4187
|
+
}
|
|
4188
|
+
if (!a.publicKey) {
|
|
4189
|
+
error(`Attestation found but its key ${a.attestation.keyId} is not on file — cannot verify.`);
|
|
4190
|
+
return 1;
|
|
4191
|
+
}
|
|
4192
|
+
const ok = verifyAttestation(a.attestation.payload, a.attestation.sig, a.publicKey);
|
|
4193
|
+
const f = parsePayload(a.attestation.payload);
|
|
4194
|
+
log(`${ok ? "VERIFIED" : "INVALID "} job ${jobId}`);
|
|
4195
|
+
log(` model ${f.model ?? a.model ?? "unknown"}`);
|
|
4196
|
+
log(` engine ${f.engine ?? a.engine}${f.seat ? ` · seat ${f.seat}` : ""}`);
|
|
4197
|
+
log(` finished ${f.finished ?? "?"} · signed ${formatLocal(a.attestation.signedAt)} · key ${a.attestation.keyId}`);
|
|
4198
|
+
if (a.outputs.length)
|
|
4199
|
+
log(` outputs ${a.outputs.join(" ")}`);
|
|
4200
|
+
log(` ${a.attestation.trailer}`);
|
|
4201
|
+
if (!ok)
|
|
4202
|
+
log(" The signature does not match the payload under the org's public key.");
|
|
4203
|
+
return ok ? 0 : 1;
|
|
4204
|
+
};
|
|
4205
|
+
|
|
3280
4206
|
// src/schedule.ts
|
|
3281
|
-
import { spawnSync } from "node:child_process";
|
|
4207
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3282
4208
|
var defaultExec = (cmd, args) => {
|
|
3283
|
-
const r =
|
|
4209
|
+
const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
3284
4210
|
if (r.error)
|
|
3285
4211
|
return { ok: false, stderr: r.error.message };
|
|
3286
4212
|
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
@@ -3294,21 +4220,33 @@ commands:
|
|
|
3294
4220
|
--token <sled_\u2026> seat token (else SLEDS_TOKEN, ~/.sleds-token, or prompt)
|
|
3295
4221
|
--url <proxy-url> proxy base URL (else SLEDS_PROXY_URL, default ${DEFAULT_PROXY_URL})
|
|
3296
4222
|
--no-probe skip the /healthz check of the proxy URL
|
|
4223
|
+
--seat-first keep Claude Code on your seat; the proxy only at a wall (default for seat plans)
|
|
4224
|
+
--proxy-always every request through the proxy on the org key (default for API-plan orgs)
|
|
3297
4225
|
--non-interactive never prompt; exit 2 if no token is available
|
|
3298
|
-
--signup no org? create
|
|
3299
|
-
|
|
4226
|
+
--signup no org? create one: (1) just you \u2014 your email + your own Anthropic
|
|
4227
|
+
API key (validated, stored encrypted, the only key it spends), or
|
|
4228
|
+
(2) your team \u2014 work email + team name, platform key optional
|
|
4229
|
+
--team "<name>" with --signup: create the team org (you become its owner)
|
|
4230
|
+
--email <you@work.com> with --signup: skip the email prompt
|
|
3300
4231
|
on Hit a limit on your seat? Switch Claude Code to the org key through the proxy.
|
|
3301
4232
|
--until-reset <when> revert at this time: +5h, +90m, or an ISO timestamp
|
|
3302
4233
|
--reason "<text>" note shown by \`sleds status\`
|
|
3303
4234
|
--no-schedule record the deadline but do not install a launchd/systemd revert
|
|
4235
|
+
defer Hit a limit? Resume this session on this Mac at the reset \u2014 runs itself on your
|
|
4236
|
+
seat, no key needed (the musher). Uses the last session the status line saw.
|
|
4237
|
+
--at +2h | <ISO> start then instead of the binding reset
|
|
4238
|
+
--session <id> --cwd <dir> --model <id> override what was remembered
|
|
4239
|
+
musher The agent that runs deferred jobs here: --install / --uninstall / --status,
|
|
4240
|
+
--claude <path>, --mode <permission-mode> (default acceptEdits), --timeout <min>.
|
|
4241
|
+
run <jobId> Run one deferred job on this Mac now (the manual musher).
|
|
4242
|
+
verify <jobId | SLEDS-Attest line> Check a finished job's attestation: which model built it,
|
|
4243
|
+
signed by the org's key, verified locally.
|
|
3304
4244
|
off Switch Claude Code back to your seat. Restores whatever \`on\` replaced.
|
|
3305
4245
|
uninstall Remove the proxy settings, skill and status line. Keeps ~/.sleds-token.
|
|
3306
4246
|
status Show the switch state, deadline, proxy health and wall mode.
|
|
3307
4247
|
doctor Check the whole install: which seat this shell is, Claude settings, proxy, hooks,
|
|
3308
|
-
Node version, stale links, the
|
|
4248
|
+
Node version, stale links, the seat's last quota report.
|
|
3309
4249
|
meter Show the pipeline: built today, queued with its band, delivered overnight.
|
|
3310
|
-
bar Print the menu-bar meter in SwiftBar format; --install links it into SwiftBar
|
|
3311
|
-
(--home/--claude-config pick the seat it tracks; --name <x> adds a second item).
|
|
3312
4250
|
mcp Run the MCP server on stdio: tools usage, estimate, overnight, jobs.
|
|
3313
4251
|
Register with \`claude mcp add sleds -- sleds mcp\` (Cowork sees the same server).
|
|
3314
4252
|
seat-sync Read the seat's subscription usage (weekly/5h limits, per-model, credits) from a
|
|
@@ -3359,17 +4297,24 @@ var stringFlag = (flags, name) => {
|
|
|
3359
4297
|
const v = flags[name];
|
|
3360
4298
|
return typeof v === "string" ? v : undefined;
|
|
3361
4299
|
};
|
|
4300
|
+
var hookStdin = async () => {
|
|
4301
|
+
if (process.stdin.isTTY)
|
|
4302
|
+
return;
|
|
4303
|
+
const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
|
|
4304
|
+
const text = await readStdin3(300);
|
|
4305
|
+
return text.trim() ? text : undefined;
|
|
4306
|
+
};
|
|
3362
4307
|
var sledsArgv = () => {
|
|
3363
4308
|
const script = process.argv[1];
|
|
3364
4309
|
return script ? [process.execPath, script] : [process.execPath];
|
|
3365
4310
|
};
|
|
3366
|
-
var
|
|
3367
|
-
var statusLineCommand = (argv) => [...argv, "statusline"].map(
|
|
3368
|
-
var seatEventCommand = (argv) => [...argv, "seat-event"].map(
|
|
3369
|
-
var specEventCommand = (argv) => [...argv, "spec-event"].map(
|
|
4311
|
+
var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
4312
|
+
var statusLineCommand = (argv) => [...argv, "statusline"].map(shellQuote).join(" ");
|
|
4313
|
+
var seatEventCommand = (argv) => [...argv, "seat-event"].map(shellQuote).join(" ");
|
|
4314
|
+
var specEventCommand = (argv) => [...argv, "spec-event"].map(shellQuote).join(" ");
|
|
3370
4315
|
var main = async (argv) => {
|
|
3371
4316
|
const { cmd, flags } = parseArgs(argv);
|
|
3372
|
-
const home =
|
|
4317
|
+
const home = homedir();
|
|
3373
4318
|
const scheduler = {
|
|
3374
4319
|
platform: platform2(),
|
|
3375
4320
|
home,
|
|
@@ -3384,7 +4329,24 @@ var main = async (argv) => {
|
|
|
3384
4329
|
url: stringFlag(flags, "url"),
|
|
3385
4330
|
nonInteractive: flags["non-interactive"] === true,
|
|
3386
4331
|
signup: flags.signup === true,
|
|
4332
|
+
team: typeof flags.team === "string" ? flags.team : undefined,
|
|
4333
|
+
email: typeof flags.email === "string" ? flags.email : undefined,
|
|
3387
4334
|
noProbe: flags["no-probe"] === true,
|
|
4335
|
+
proxyAlways: flags["proxy-always"] === true,
|
|
4336
|
+
seatFirst: flags["seat-first"] === true,
|
|
4337
|
+
installMusher: () => {
|
|
4338
|
+
if (platform2() !== "darwin")
|
|
4339
|
+
return null;
|
|
4340
|
+
const lines = [];
|
|
4341
|
+
const code = musherInstall({
|
|
4342
|
+
home,
|
|
4343
|
+
argv: [],
|
|
4344
|
+
sledsArgv: sledsArgv(),
|
|
4345
|
+
log: (l) => lines.push(l),
|
|
4346
|
+
error: (l) => lines.push(l)
|
|
4347
|
+
});
|
|
4348
|
+
return code === 0 ? lines[0] ?? "installed the musher agent" : `musher not installed: ${lines.join(" ")}`;
|
|
4349
|
+
},
|
|
3388
4350
|
prompt: promptStdin,
|
|
3389
4351
|
statusLineCommand: statusLineCommand(sledsArgv()),
|
|
3390
4352
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
@@ -3411,16 +4373,22 @@ var main = async (argv) => {
|
|
|
3411
4373
|
});
|
|
3412
4374
|
case "off":
|
|
3413
4375
|
return off({ home, scheduler });
|
|
4376
|
+
case "defer":
|
|
4377
|
+
return defer({ home, argv: process.argv.slice(3), stdin: await hookStdin() });
|
|
4378
|
+
case "musher":
|
|
4379
|
+
return musher({ home, argv: process.argv.slice(3), sledsArgv: sledsArgv() });
|
|
4380
|
+
case "run":
|
|
4381
|
+
return run({ home, jobId: process.argv[3] });
|
|
4382
|
+
case "verify":
|
|
4383
|
+
return verify({ home, target: process.argv.slice(3).join(" ") || undefined });
|
|
3414
4384
|
case "uninstall":
|
|
3415
|
-
return
|
|
4385
|
+
return uninstall2({ home });
|
|
3416
4386
|
case "doctor":
|
|
3417
4387
|
return doctor({ home });
|
|
3418
4388
|
case "status":
|
|
3419
4389
|
return status({ home });
|
|
3420
4390
|
case "meter":
|
|
3421
4391
|
return meter({ home });
|
|
3422
|
-
case "bar":
|
|
3423
|
-
return bar({ home, argv: process.argv.slice(3) });
|
|
3424
4392
|
case "mcp":
|
|
3425
4393
|
return mcp({ home });
|
|
3426
4394
|
case "seat-sync":
|
|
@@ -3435,7 +4403,7 @@ var main = async (argv) => {
|
|
|
3435
4403
|
case "version":
|
|
3436
4404
|
case "--version":
|
|
3437
4405
|
case "-v":
|
|
3438
|
-
console.log(
|
|
4406
|
+
console.log("sleds 0.1.14");
|
|
3439
4407
|
return 0;
|
|
3440
4408
|
default:
|
|
3441
4409
|
console.error(`unknown command: ${cmd}
|