@sleds/cli 0.1.11 → 0.1.15
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 +4 -1
- package/dist/sleds.mjs +1423 -562
- 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) => withSeatEnv(`sleds ${sub}`, home, env), withSeatEnv = (command, 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();
|
|
@@ -49,10 +32,9 @@ var sledsCommand = (sub, home, env = process.env) => {
|
|
|
49
32
|
parts.push(`SLEDS_HOME=${shellWord(resolve(home, sh))}`);
|
|
50
33
|
if (cc)
|
|
51
34
|
parts.push(`CLAUDE_CONFIG_DIR=${shellWord(resolve(home, cc))}`);
|
|
52
|
-
parts.push(
|
|
35
|
+
parts.push(command);
|
|
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 = {};
|
|
@@ -310,9 +243,26 @@ var parseConfig = (raw) => {
|
|
|
310
243
|
const org = parseOrgInfo(raw.org);
|
|
311
244
|
if (org)
|
|
312
245
|
cfg.org = org;
|
|
246
|
+
const musher = parseMusher(raw.musher);
|
|
247
|
+
if (musher)
|
|
248
|
+
cfg.musher = musher;
|
|
313
249
|
return cfg;
|
|
314
|
-
}
|
|
315
|
-
|
|
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) => {
|
|
316
266
|
if (!isObject2(raw) || !isObject2(raw.capabilities))
|
|
317
267
|
return;
|
|
318
268
|
const name = optString(raw.name);
|
|
@@ -334,14 +284,12 @@ var parseOrgInfo = (raw) => {
|
|
|
334
284
|
},
|
|
335
285
|
mode: raw.mode === "proxy-always" ? "proxy-always" : "seat-first"
|
|
336
286
|
};
|
|
337
|
-
}
|
|
338
|
-
var readConfig = (home) => {
|
|
287
|
+
}, readConfig = (home) => {
|
|
339
288
|
const p = configPath(home);
|
|
340
289
|
if (!existsSync2(p))
|
|
341
290
|
return {};
|
|
342
291
|
return parseConfig(JSON.parse(readFileSync2(p, "utf8")));
|
|
343
|
-
}
|
|
344
|
-
var writeConfig = (home, cfg) => {
|
|
292
|
+
}, writeConfig = (home, cfg) => {
|
|
345
293
|
const dir = configDir(home);
|
|
346
294
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
347
295
|
const target = configPath(home);
|
|
@@ -351,14 +299,14 @@ var writeConfig = (home, cfg) => {
|
|
|
351
299
|
renameSync2(tmp, target);
|
|
352
300
|
chmodSync(target, 384);
|
|
353
301
|
};
|
|
302
|
+
var init_config = __esm(() => {
|
|
303
|
+
init_settings();
|
|
304
|
+
});
|
|
354
305
|
|
|
355
306
|
// src/meter.ts
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v)
|
|
359
|
-
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
360
|
-
var str = (v) => typeof v === "string" ? v : undefined;
|
|
361
|
-
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) => {
|
|
362
310
|
if (!isObject3(raw))
|
|
363
311
|
return null;
|
|
364
312
|
const seat = isObject3(raw.seat) ? str(raw.seat.email) : undefined;
|
|
@@ -413,18 +361,15 @@ var parseMeter = (raw) => {
|
|
|
413
361
|
if (acc !== undefined)
|
|
414
362
|
out.estimateAccuracyPct = acc;
|
|
415
363
|
return out;
|
|
416
|
-
}
|
|
417
|
-
var usd = (n) => {
|
|
364
|
+
}, usd = (n) => {
|
|
418
365
|
const abs = Math.abs(n);
|
|
419
366
|
if (abs >= 1000)
|
|
420
367
|
return `${n < 0 ? "-" : ""}$${Math.round(abs).toLocaleString("en-US")}`;
|
|
421
368
|
return `${n < 0 ? "-" : ""}$${abs.toFixed(2)}`;
|
|
422
|
-
}
|
|
423
|
-
var usdRange = (low, high) => {
|
|
369
|
+
}, usdRange = (low, high) => {
|
|
424
370
|
const d = high >= 10 ? 0 : 2;
|
|
425
371
|
return `$${low.toFixed(d)}–${high.toFixed(d)}`;
|
|
426
|
-
}
|
|
427
|
-
var shortDuration = (ms) => {
|
|
372
|
+
}, shortDuration = (ms) => {
|
|
428
373
|
const total = Math.max(0, Math.round(ms / 60000));
|
|
429
374
|
const d = Math.floor(total / (24 * 60));
|
|
430
375
|
const h = Math.floor(total % (24 * 60) / 60);
|
|
@@ -434,18 +379,7 @@ var shortDuration = (ms) => {
|
|
|
434
379
|
if (h > 0)
|
|
435
380
|
return `${h}h${String(m).padStart(2, "0")}m`;
|
|
436
381
|
return `${m}m`;
|
|
437
|
-
}
|
|
438
|
-
var KIND_WORDS = {
|
|
439
|
-
rate_limit: "rate limit hit",
|
|
440
|
-
weekly_cap: "usage limit reached",
|
|
441
|
-
budget: "budget exhausted",
|
|
442
|
-
provider_down: "provider unavailable"
|
|
443
|
-
};
|
|
444
|
-
var kindInWords = (kind) => KIND_WORDS[kind] ?? kind.replace(/_/g, " ");
|
|
445
|
-
var SEP = " · ";
|
|
446
|
-
var PREFIX = "sleds ";
|
|
447
|
-
var OFFLINE_LINE = `${PREFIX}offline`;
|
|
448
|
-
var jobParts = (m) => {
|
|
382
|
+
}, KIND_WORDS, kindInWords = (kind) => KIND_WORDS[kind] ?? kind.replace(/_/g, " "), SEP = " · ", PREFIX = "sleds ", OFFLINE_LINE, jobParts = (m) => {
|
|
449
383
|
const parts = [];
|
|
450
384
|
const [low, high] = m.jobs.queuedEstimateUsd;
|
|
451
385
|
if (m.jobs.queued > 0) {
|
|
@@ -457,8 +391,7 @@ var jobParts = (m) => {
|
|
|
457
391
|
if (m.jobs.deliveredToday > 0)
|
|
458
392
|
parts.push(`${m.jobs.deliveredToday} delivered`);
|
|
459
393
|
return parts;
|
|
460
|
-
}
|
|
461
|
-
var formatMeterLine = (m, now, seatFragment = null) => {
|
|
394
|
+
}, formatMeterLine = (m, now, seatFragment = null) => {
|
|
462
395
|
if (!m)
|
|
463
396
|
return seatFragment ? `${OFFLINE_LINE}${SEP}${seatFragment}` : OFFLINE_LINE;
|
|
464
397
|
const parts = [`${usd(m.today.spentUsd)} today`];
|
|
@@ -473,10 +406,7 @@ var formatMeterLine = (m, now, seatFragment = null) => {
|
|
|
473
406
|
parts.push(`allowance ${usd(m.allowance.remainingUsd)}`);
|
|
474
407
|
parts.push(...jobParts(m));
|
|
475
408
|
return `${PREFIX}${parts.join(SEP)}`;
|
|
476
|
-
}
|
|
477
|
-
var row = (label, value) => `${label.padEnd(12)}${value}`;
|
|
478
|
-
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
479
|
-
var formatMeter = (m, now) => {
|
|
409
|
+
}, row = (label, value) => `${label.padEnd(12)}${value}`, plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`, formatMeter = (m, now) => {
|
|
480
410
|
const lines = [row("Seat:", m.seat.email)];
|
|
481
411
|
lines.push(row("Built today:", `${usd(m.today.spentUsd)}${SEP}${plural(m.today.requests, "request")}`));
|
|
482
412
|
lines.push(row("Month:", m.month.ceilingUsd !== undefined ? `${usd(m.month.spentUsd)} of ${usd(m.month.ceilingUsd)} ceiling` : usd(m.month.spentUsd)));
|
|
@@ -500,9 +430,7 @@ var formatMeter = (m, now) => {
|
|
|
500
430
|
if (m.estimateAccuracyPct !== undefined)
|
|
501
431
|
lines.push(row("Estimates:", `${Math.round(m.estimateAccuracyPct)}% landed in band`));
|
|
502
432
|
return lines;
|
|
503
|
-
}
|
|
504
|
-
var meterCachePath = (home) => join3(configDir(home), "meter.json");
|
|
505
|
-
var readMeterCache = (home, now) => {
|
|
433
|
+
}, meterCachePath = (home) => join3(configDir(home), "meter.json"), readMeterCache = (home, now) => {
|
|
506
434
|
const p = meterCachePath(home);
|
|
507
435
|
if (!existsSync3(p))
|
|
508
436
|
return null;
|
|
@@ -517,8 +445,7 @@ var readMeterCache = (home, now) => {
|
|
|
517
445
|
} catch {
|
|
518
446
|
return null;
|
|
519
447
|
}
|
|
520
|
-
}
|
|
521
|
-
var writeMeterCache = (home, meter, now) => {
|
|
448
|
+
}, writeMeterCache = (home, meter, now) => {
|
|
522
449
|
const dir = configDir(home);
|
|
523
450
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
524
451
|
const target = meterCachePath(home);
|
|
@@ -527,8 +454,7 @@ var writeMeterCache = (home, meter, now) => {
|
|
|
527
454
|
writeFileSync3(tmp, `${JSON.stringify(file)}
|
|
528
455
|
`, { mode: 384 });
|
|
529
456
|
renameSync3(tmp, target);
|
|
530
|
-
}
|
|
531
|
-
var fetchMeter = async (o) => {
|
|
457
|
+
}, fetchMeter = async (o) => {
|
|
532
458
|
const fetchFn = o.fetchFn ?? fetch;
|
|
533
459
|
try {
|
|
534
460
|
const res = await fetchFn(`${o.proxyUrl}/proxy/meter`, {
|
|
@@ -541,16 +467,14 @@ var fetchMeter = async (o) => {
|
|
|
541
467
|
} catch {
|
|
542
468
|
return null;
|
|
543
469
|
}
|
|
544
|
-
}
|
|
545
|
-
var meterSource = (home) => {
|
|
470
|
+
}, meterSource = (home) => {
|
|
546
471
|
const cfg = readConfig(home);
|
|
547
472
|
const settings = existsSync3(settingsPath(home)) ? readSettings(home) : {};
|
|
548
473
|
const inSettings = readProxy(settings);
|
|
549
474
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
550
475
|
const token = inSettings?.token ?? cfg.token;
|
|
551
476
|
return proxyUrl && token ? { proxyUrl, token } : null;
|
|
552
|
-
}
|
|
553
|
-
var loadMeter = async (o) => {
|
|
477
|
+
}, loadMeter = async (o) => {
|
|
554
478
|
const now = o.now ?? Date.now();
|
|
555
479
|
const cached = readMeterCache(o.home, now);
|
|
556
480
|
if (cached)
|
|
@@ -566,99 +490,556 @@ var loadMeter = async (o) => {
|
|
|
566
490
|
}
|
|
567
491
|
return fresh;
|
|
568
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
|
+
});
|
|
569
504
|
|
|
570
|
-
// src/
|
|
571
|
-
var
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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 join11 } 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))
|
|
580
557
|
return null;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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) => {
|
|
585
595
|
if (!w)
|
|
586
|
-
return
|
|
587
|
-
const
|
|
596
|
+
return null;
|
|
597
|
+
const parts = [];
|
|
588
598
|
if (w.sevenDay)
|
|
589
|
-
|
|
599
|
+
parts.push(`wk ${Math.round(w.sevenDay.usedPct)}%`);
|
|
590
600
|
if (w.fiveHour)
|
|
591
|
-
|
|
601
|
+
parts.push(`5h ${Math.round(w.fiveHour.usedPct)}%`);
|
|
592
602
|
if (w.spendLimit)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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) => join11(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;
|
|
598
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;
|
|
599
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((resolve5) => {
|
|
754
|
+
if (stdin.isTTY) {
|
|
755
|
+
resolve5("");
|
|
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
|
+
resolve5(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 { basename, 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 musherLabel = (env = process.env) => {
|
|
803
|
+
const sh = env.SLEDS_HOME?.trim();
|
|
804
|
+
if (!sh)
|
|
805
|
+
return MUSHER_LABEL;
|
|
806
|
+
const tag = basename(sh).replace(/^\.+/, "").replace(/[^A-Za-z0-9_-]+/g, "-") || "seat";
|
|
807
|
+
return `${MUSHER_LABEL}.${tag}`;
|
|
600
808
|
};
|
|
601
|
-
var
|
|
602
|
-
var
|
|
603
|
-
var
|
|
604
|
-
var
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
809
|
+
var musherPlistPath = (home, env = process.env) => join4(home, "Library", "LaunchAgents", `${musherLabel(env)}.plist`);
|
|
810
|
+
var musherLogPath = (configDir2) => join4(configDir2, "musher.log");
|
|
811
|
+
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
812
|
+
var musherPath = (home, execPath, currentPath = "") => {
|
|
813
|
+
const parts = [
|
|
814
|
+
dirname2(execPath),
|
|
815
|
+
join4(home, ".sleds", "bin"),
|
|
816
|
+
join4(home, ".local", "bin"),
|
|
817
|
+
join4(home, ".bun", "bin"),
|
|
818
|
+
"/opt/homebrew/bin",
|
|
819
|
+
"/usr/local/bin",
|
|
820
|
+
"/usr/bin",
|
|
821
|
+
"/bin",
|
|
822
|
+
...currentPath.split(":").filter(Boolean)
|
|
823
|
+
];
|
|
824
|
+
return [...new Set(parts)].join(":");
|
|
825
|
+
};
|
|
826
|
+
var buildMusherPlist = (o) => {
|
|
827
|
+
const label = o.label ?? MUSHER_LABEL;
|
|
828
|
+
const args = [...o.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
|
|
829
|
+
`);
|
|
830
|
+
const env = Object.entries(o.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
831
|
+
<string>${xmlEscape(v)}</string>`).join(`
|
|
832
|
+
`);
|
|
833
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
834
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
835
|
+
<plist version="1.0">
|
|
836
|
+
<dict>
|
|
837
|
+
<key>Label</key>
|
|
838
|
+
<string>${label}</string>
|
|
839
|
+
<key>ProgramArguments</key>
|
|
840
|
+
<array>
|
|
841
|
+
${args}
|
|
842
|
+
</array>
|
|
843
|
+
<key>EnvironmentVariables</key>
|
|
844
|
+
<dict>
|
|
845
|
+
${env}
|
|
846
|
+
</dict>
|
|
847
|
+
<key>StartInterval</key>
|
|
848
|
+
<integer>${MUSHER_INTERVAL_S}</integer>
|
|
849
|
+
<key>RunAtLoad</key>
|
|
850
|
+
<true/>
|
|
851
|
+
<key>StandardOutPath</key>
|
|
852
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
853
|
+
<key>StandardErrorPath</key>
|
|
854
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
855
|
+
</dict>
|
|
856
|
+
</plist>
|
|
857
|
+
`;
|
|
858
|
+
};
|
|
859
|
+
var RESUME_PROMPT = [
|
|
860
|
+
"SLEDS musher: the usage limit that stopped this session has reset, and the seat's owner",
|
|
861
|
+
"scheduled this unattended run to continue it. Pick up exactly where the session left off:",
|
|
862
|
+
"finish the task that was in flight, run the checks you would normally run, follow the repo's",
|
|
863
|
+
"own conventions for branches and commits, and do not start unrelated work. If you are blocked",
|
|
864
|
+
"on a decision only the owner can make, stop there and say so. Finish with a summary under",
|
|
865
|
+
"200 words: what was done, what remains, anything the owner must decide."
|
|
866
|
+
].join(" ");
|
|
867
|
+
var NO_SESSION_PROMPT = [
|
|
868
|
+
"SLEDS musher: a deferred job started on this seat, but no Claude Code session was recorded",
|
|
869
|
+
"to resume. Do not guess at work. Reply with one line saying so, and stop."
|
|
870
|
+
].join(" ");
|
|
871
|
+
var claudeArgs = (o) => [
|
|
872
|
+
"-p",
|
|
873
|
+
o.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
|
|
874
|
+
...o.sessionId ? ["--resume", o.sessionId] : [],
|
|
875
|
+
...o.model ? ["--model", o.model] : [],
|
|
876
|
+
"--output-format",
|
|
877
|
+
"json",
|
|
878
|
+
"--permission-mode",
|
|
879
|
+
o.permissionMode
|
|
880
|
+
];
|
|
881
|
+
var parseClaudeOutput = (stdout, exitCode) => {
|
|
882
|
+
const lines = stdout.split(`
|
|
883
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
884
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
885
|
+
const line = lines[i] ?? "";
|
|
886
|
+
if (!line.startsWith("{"))
|
|
887
|
+
continue;
|
|
888
|
+
try {
|
|
889
|
+
const j = JSON.parse(line);
|
|
890
|
+
if (j.type !== "result" && typeof j.result !== "string")
|
|
891
|
+
continue;
|
|
892
|
+
const isError = j.is_error === true || typeof j.subtype === "string" && j.subtype !== "success";
|
|
893
|
+
const text = typeof j.result === "string" ? j.result : "";
|
|
894
|
+
const models = j.modelUsage;
|
|
895
|
+
const model = typeof j.model === "string" ? j.model : models && typeof models === "object" ? Object.keys(models)[0] : undefined;
|
|
896
|
+
return {
|
|
897
|
+
ok: !isError && exitCode === 0,
|
|
898
|
+
summary: (text || (isError ? "Claude Code reported an error" : "")).slice(0, SUMMARY_MAX),
|
|
899
|
+
...typeof j.session_id === "string" ? { sessionId: j.session_id } : {},
|
|
900
|
+
...model ? { model } : {},
|
|
901
|
+
...typeof j.duration_ms === "number" ? { durationMs: j.duration_ms } : {},
|
|
902
|
+
...typeof j.total_cost_usd === "number" ? { costUsd: j.total_cost_usd } : {}
|
|
903
|
+
};
|
|
904
|
+
} catch {}
|
|
905
|
+
}
|
|
906
|
+
const tail = lines.slice(-5).join(" ").slice(0, SUMMARY_MAX);
|
|
907
|
+
return { ok: false, summary: tail || `claude exited ${exitCode ?? "without a code"}` };
|
|
908
|
+
};
|
|
909
|
+
var defaultExecCapture = (cmd, args, o) => {
|
|
910
|
+
const r = spawnSync(cmd, [...args], {
|
|
911
|
+
cwd: o.cwd,
|
|
912
|
+
encoding: "utf8",
|
|
913
|
+
timeout: o.timeoutMs,
|
|
914
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
915
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
916
|
+
env: { ...process.env, SLEDS_MUSHER: "1" }
|
|
917
|
+
});
|
|
918
|
+
return {
|
|
919
|
+
stdout: r.stdout ?? "",
|
|
920
|
+
stderr: r.stderr ?? "",
|
|
921
|
+
status: r.status,
|
|
922
|
+
...r.error ? { error: r.error.message } : {}
|
|
923
|
+
};
|
|
924
|
+
};
|
|
925
|
+
var musherClient = (o) => {
|
|
926
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
927
|
+
const headers = { authorization: `Bearer ${o.token}`, "content-type": "application/json" };
|
|
928
|
+
const call = async (path, body) => {
|
|
929
|
+
try {
|
|
930
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
931
|
+
method: body === undefined ? "GET" : "POST",
|
|
932
|
+
headers,
|
|
933
|
+
...body === undefined ? {} : { body: JSON.stringify(body) },
|
|
934
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? 1e4)
|
|
935
|
+
});
|
|
936
|
+
if (!res.ok)
|
|
937
|
+
return null;
|
|
938
|
+
return await res.json();
|
|
939
|
+
} catch {
|
|
940
|
+
return null;
|
|
628
941
|
}
|
|
629
|
-
|
|
630
|
-
|
|
942
|
+
};
|
|
943
|
+
return {
|
|
944
|
+
list: () => call("/proxy/musher"),
|
|
945
|
+
start: (jobId, local) => call("/proxy/musher/start", { jobId, ...local }),
|
|
946
|
+
finish: async (input) => await call("/proxy/musher/finish", input) !== null,
|
|
947
|
+
defer: (input) => call("/proxy/defer", input),
|
|
948
|
+
attestation: (jobId) => call(`/proxy/attest/${encodeURIComponent(jobId)}`)
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
var gitHead = (cwd, exec) => {
|
|
952
|
+
if (!cwd)
|
|
953
|
+
return null;
|
|
954
|
+
const r = exec("git", ["rev-parse", "HEAD"], { cwd, timeoutMs: 5000 });
|
|
955
|
+
const sha = r.stdout.trim();
|
|
956
|
+
return r.status === 0 && /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
957
|
+
};
|
|
958
|
+
var gitNewCommits = (cwd, before, after, exec) => {
|
|
959
|
+
if (!cwd || !after || after === before)
|
|
960
|
+
return [];
|
|
961
|
+
if (!before)
|
|
962
|
+
return [after];
|
|
963
|
+
const r = exec("git", ["rev-list", `${before}..${after}`], { cwd, timeoutMs: 5000 });
|
|
964
|
+
if (r.status !== 0)
|
|
965
|
+
return [after];
|
|
966
|
+
return r.stdout.split(`
|
|
967
|
+
`).map((l) => l.trim()).filter((l) => /^[0-9a-f]{40}$/.test(l)).slice(0, 40);
|
|
968
|
+
};
|
|
969
|
+
var collectOutputs = (commits, summary) => {
|
|
970
|
+
const out = commits.map((c) => `git:${c}`);
|
|
971
|
+
for (const m of summary.matchAll(/https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+/g))
|
|
972
|
+
if (!out.includes(`pr:${m[0]}`))
|
|
973
|
+
out.push(`pr:${m[0]}`);
|
|
974
|
+
return out.slice(0, 50);
|
|
975
|
+
};
|
|
976
|
+
var runJob = async (o) => {
|
|
977
|
+
const log = o.log ?? (() => {});
|
|
978
|
+
const sessionId = o.job.claudeSessionId ?? o.fallbackSession?.id ?? null;
|
|
979
|
+
const cwd = o.job.cwd ?? o.fallbackSession?.cwd ?? undefined;
|
|
980
|
+
const started = await o.client.start(o.job.jobId, {
|
|
981
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
982
|
+
...cwd ? { cwd } : {},
|
|
983
|
+
host: o.host
|
|
984
|
+
});
|
|
985
|
+
if (!started)
|
|
986
|
+
return { kind: "skipped", reason: "control plane unreachable" };
|
|
987
|
+
if (!started.ok)
|
|
988
|
+
return { kind: "skipped", reason: started.reason };
|
|
989
|
+
const model = o.job.model ?? started.model ?? o.fallbackSession?.model ?? null;
|
|
990
|
+
const args = claudeArgs({ sessionId, model, permissionMode: o.permissionMode });
|
|
991
|
+
log(`musher: running job ${o.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
|
|
992
|
+
const exec = o.exec ?? defaultExecCapture;
|
|
993
|
+
const headBefore = gitHead(cwd, exec);
|
|
994
|
+
const t0 = Date.now();
|
|
995
|
+
const r = exec(o.claudePath, args, { cwd, timeoutMs: o.timeoutMs });
|
|
996
|
+
const durationMs = Date.now() - t0;
|
|
997
|
+
const result = r.error ? { ok: false, summary: `could not run ${o.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
|
|
998
|
+
if (!r.error && r.status === null)
|
|
999
|
+
result.summary = `timed out after ${Math.round(o.timeoutMs / 60000)} min. ${result.summary}`.trim();
|
|
1000
|
+
const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
|
|
1001
|
+
await o.client.finish({
|
|
1002
|
+
jobId: o.job.jobId,
|
|
1003
|
+
runId: started.runId,
|
|
1004
|
+
ok: result.ok,
|
|
1005
|
+
summary: result.summary,
|
|
1006
|
+
...outputs.length ? { outputs } : {},
|
|
1007
|
+
...result.model ?? model ? { model: result.model ?? model } : {},
|
|
1008
|
+
durationMs: result.durationMs ?? durationMs,
|
|
1009
|
+
...result.sessionId ?? sessionId ? { claudeSessionId: result.sessionId ?? sessionId } : {}
|
|
1010
|
+
});
|
|
1011
|
+
log(`musher: job ${o.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
|
|
1012
|
+
return { kind: "ran", ok: result.ok, summary: result.summary, durationMs, outputs };
|
|
1013
|
+
};
|
|
1014
|
+
var verifyAttestation = (payload, sig, publicKeyB64) => {
|
|
1015
|
+
try {
|
|
1016
|
+
const pub = createPublicKey({
|
|
1017
|
+
key: Buffer.from(publicKeyB64, "base64"),
|
|
1018
|
+
format: "der",
|
|
1019
|
+
type: "spki"
|
|
1020
|
+
});
|
|
1021
|
+
const raw = Buffer.from(sig.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
1022
|
+
return cryptoVerify(null, Buffer.from(payload, "utf8"), pub, raw);
|
|
1023
|
+
} catch {
|
|
1024
|
+
return false;
|
|
631
1025
|
}
|
|
632
|
-
lines.push("---");
|
|
633
|
-
lines.push(`Open dashboard | href=${DASHBOARD_URL}`);
|
|
634
|
-
lines.push("Refresh | refresh=true");
|
|
635
|
-
return lines;
|
|
636
1026
|
};
|
|
637
|
-
var
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
`if [ -x ${shellQuote(nodePath)} ] && [ -f ${shellQuote(cliPath)} ]; then`,
|
|
648
|
-
` exec ${shellQuote(nodePath)} ${shellQuote(cliPath)} bar`,
|
|
649
|
-
"fi",
|
|
650
|
-
"if command -v sleds >/dev/null 2>&1; then exec sleds bar; fi",
|
|
651
|
-
'echo "sleds not found"',
|
|
652
|
-
'echo "---"',
|
|
653
|
-
'echo "Run \\`npx @sleds/cli bar --install\\` again. | font=Menlo size=12"',
|
|
654
|
-
""
|
|
655
|
-
].join(`
|
|
656
|
-
`);
|
|
657
|
-
var shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1027
|
+
var parsePayload = (payload) => {
|
|
1028
|
+
const out = {};
|
|
1029
|
+
for (const line of payload.split(`
|
|
1030
|
+
`).slice(1)) {
|
|
1031
|
+
const eq = line.indexOf("=");
|
|
1032
|
+
if (eq > 0)
|
|
1033
|
+
out[line.slice(0, eq)] = line.slice(eq + 1);
|
|
1034
|
+
}
|
|
1035
|
+
return out;
|
|
1036
|
+
};
|
|
658
1037
|
|
|
659
1038
|
// src/seat.ts
|
|
1039
|
+
init_config();
|
|
1040
|
+
init_meter();
|
|
660
1041
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
661
|
-
import { join as
|
|
1042
|
+
import { join as join5 } from "node:path";
|
|
662
1043
|
var SEAT_POST_MIN_DELTA_PCT = 1;
|
|
663
1044
|
var SEAT_POST_MIN_INTERVAL_MS = 5 * 60000;
|
|
664
1045
|
var SEAT_POST_TIMEOUT_MS = 1500;
|
|
@@ -743,7 +1124,7 @@ var formatSeatFragment = (w) => {
|
|
|
743
1124
|
return null;
|
|
744
1125
|
return `seat ${parts.join(SEP)}`;
|
|
745
1126
|
};
|
|
746
|
-
var seatStatePath = (home) =>
|
|
1127
|
+
var seatStatePath = (home) => join5(configDir(home), "seat.json");
|
|
747
1128
|
var parseWindowStored = (v) => {
|
|
748
1129
|
if (!isObject4(v))
|
|
749
1130
|
return;
|
|
@@ -778,8 +1159,56 @@ var parseSeatState = (raw) => {
|
|
|
778
1159
|
const lastWallResetsAt = num2(raw.lastWallResetsAt);
|
|
779
1160
|
if (lastWallResetsAt !== undefined)
|
|
780
1161
|
out.lastWallResetsAt = lastWallResetsAt;
|
|
1162
|
+
const session = parseSeatSession(raw.session);
|
|
1163
|
+
if (session)
|
|
1164
|
+
out.session = session;
|
|
781
1165
|
return out;
|
|
782
1166
|
};
|
|
1167
|
+
var parseSeatSession = (v) => {
|
|
1168
|
+
if (!isObject4(v) || typeof v.id !== "string" || !v.id)
|
|
1169
|
+
return;
|
|
1170
|
+
const at = num2(v.at);
|
|
1171
|
+
if (at === undefined)
|
|
1172
|
+
return;
|
|
1173
|
+
const out = { id: v.id, at };
|
|
1174
|
+
if (typeof v.cwd === "string" && v.cwd)
|
|
1175
|
+
out.cwd = v.cwd;
|
|
1176
|
+
if (typeof v.model === "string" && v.model)
|
|
1177
|
+
out.model = v.model;
|
|
1178
|
+
return out;
|
|
1179
|
+
};
|
|
1180
|
+
var seatSessionFromStdin = (text, now) => {
|
|
1181
|
+
const trimmed = text.trim();
|
|
1182
|
+
if (!trimmed)
|
|
1183
|
+
return null;
|
|
1184
|
+
try {
|
|
1185
|
+
const raw = JSON.parse(trimmed);
|
|
1186
|
+
if (!isObject4(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
1187
|
+
return null;
|
|
1188
|
+
const out = { id: raw.session_id, at: now };
|
|
1189
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
1190
|
+
out.cwd = raw.cwd;
|
|
1191
|
+
const model = isObject4(raw.model) ? raw.model.id : raw.model;
|
|
1192
|
+
if (typeof model === "string" && model)
|
|
1193
|
+
out.model = model;
|
|
1194
|
+
return out;
|
|
1195
|
+
} catch {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
var bindingResetAt = (w, now) => {
|
|
1200
|
+
if (!w)
|
|
1201
|
+
return;
|
|
1202
|
+
const bars = [
|
|
1203
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
1204
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
1205
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
1206
|
+
...Object.values(w.models ?? {})
|
|
1207
|
+
].filter((b) => b.resetsAt > now);
|
|
1208
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
1209
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
1210
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
1211
|
+
};
|
|
783
1212
|
var readSeatState = (home) => {
|
|
784
1213
|
const p = seatStatePath(home);
|
|
785
1214
|
if (!existsSync4(p))
|
|
@@ -837,187 +1266,55 @@ var seatWallDue = (state, w) => {
|
|
|
837
1266
|
};
|
|
838
1267
|
var seatWindowBody = (w) => ({
|
|
839
1268
|
capturedAt: w.capturedAt,
|
|
840
|
-
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
841
|
-
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
842
|
-
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
843
|
-
});
|
|
844
|
-
var postJson = async (o, path, body) => {
|
|
845
|
-
const fetchFn = o.fetchFn ?? fetch;
|
|
846
|
-
try {
|
|
847
|
-
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
848
|
-
method: "POST",
|
|
849
|
-
headers: { authorization: `Bearer ${o.token}`, "content-type": "application/json" },
|
|
850
|
-
body: JSON.stringify(body),
|
|
851
|
-
signal: AbortSignal.timeout(o.timeoutMs ?? SEAT_POST_TIMEOUT_MS)
|
|
852
|
-
});
|
|
853
|
-
return res.ok;
|
|
854
|
-
} catch {
|
|
855
|
-
return false;
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
|
|
859
|
-
var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", input);
|
|
860
|
-
var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the org key.";
|
|
861
|
-
var SEAT_WALL_LINE_NO_KEY = "Seat limit hit. Hold until the reset, or /sled 5 to hand off — this org has no platform key to switch to.";
|
|
862
|
-
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
863
|
-
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
|
|
864
|
-
if (stdin.isTTY) {
|
|
865
|
-
resolve3("");
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
let data = "";
|
|
869
|
-
let settled = false;
|
|
870
|
-
const finish = () => {
|
|
871
|
-
if (settled)
|
|
872
|
-
return;
|
|
873
|
-
settled = true;
|
|
874
|
-
clearTimeout(timer);
|
|
875
|
-
resolve3(data);
|
|
876
|
-
};
|
|
877
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
878
|
-
stdin.setEncoding("utf8");
|
|
879
|
-
stdin.on("data", (chunk) => {
|
|
880
|
-
data += chunk;
|
|
881
|
-
});
|
|
882
|
-
stdin.on("end", finish);
|
|
883
|
-
stdin.on("error", finish);
|
|
884
|
-
stdin.resume();
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
// src/commands/bar.ts
|
|
888
|
-
function parseInstallArgs(argv, env) {
|
|
889
|
-
const flag = (name2) => {
|
|
890
|
-
const i = argv.indexOf(`--${name2}`);
|
|
891
|
-
const v = i >= 0 ? argv[i + 1] : undefined;
|
|
892
|
-
return v && !v.startsWith("--") ? v : undefined;
|
|
893
|
-
};
|
|
894
|
-
const sledsHome = flag("home") ?? (env.SLEDS_HOME?.trim() || undefined);
|
|
895
|
-
const claudeConfigDir = flag("claude-config") ?? (env.CLAUDE_CONFIG_DIR?.trim() || undefined);
|
|
896
|
-
const name = flag("name");
|
|
897
|
-
return {
|
|
898
|
-
...sledsHome ? { sledsHome } : {},
|
|
899
|
-
...claudeConfigDir ? { claudeConfigDir } : {},
|
|
900
|
-
...name ? { name } : {}
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
var PLUGIN_NAME = "sleds.1m.sh";
|
|
904
|
-
var pluginName = (name) => name ? `sleds-${name}.1m.sh` : PLUGIN_NAME;
|
|
905
|
-
var bar = async (o) => {
|
|
906
|
-
const log = o.log ?? console.log;
|
|
907
|
-
const argv = o.argv ?? [];
|
|
908
|
-
if (argv.includes("--install"))
|
|
909
|
-
return install(o, log);
|
|
910
|
-
if (argv.includes("--uninstall"))
|
|
911
|
-
return uninstall(o, log);
|
|
912
|
-
const now = (o.now ?? Date.now)();
|
|
913
|
-
const seat = freshSeat(readSeatState(o.home).window, now);
|
|
914
|
-
if (!meterSource(o.home)) {
|
|
915
|
-
log("sleds not set up");
|
|
916
|
-
log("---");
|
|
917
|
-
log("Run `sleds init` first | font=Menlo size=12");
|
|
918
|
-
return 0;
|
|
919
|
-
}
|
|
920
|
-
const m = await loadMeter({ home: o.home, now, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs });
|
|
921
|
-
for (const line of formatBar(m, seat, now))
|
|
922
|
-
log(line);
|
|
923
|
-
return 0;
|
|
924
|
-
};
|
|
925
|
-
function swiftBarPluginDir() {
|
|
926
|
-
try {
|
|
927
|
-
const out = execFileSync("defaults", ["read", "com.ameba.SwiftBar", "PluginDirectory"], {
|
|
928
|
-
encoding: "utf8",
|
|
929
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
930
|
-
}).trim();
|
|
931
|
-
return out ? out.replace(/^~(?=$|\/)/, homedir()) : null;
|
|
932
|
-
} catch {
|
|
933
|
-
return null;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
var pluginPath = (home, name) => join5(configDir(home, {}), "swiftbar", pluginName(name));
|
|
937
|
-
function install(o, log) {
|
|
938
|
-
const opts = parseInstallArgs(o.argv ?? [], o.env ?? process.env);
|
|
939
|
-
const script = pluginPath(o.home, opts.name);
|
|
940
|
-
mkdirSync5(join5(script, ".."), { recursive: true, mode: 448 });
|
|
941
|
-
writeFileSync5(script, pluginScript(o.nodePath ?? process.execPath, o.cliPath ?? process.argv[1] ?? "", {
|
|
942
|
-
...opts.sledsHome ? { sledsHome: resolve3(o.home, opts.sledsHome) } : {},
|
|
943
|
-
...opts.claudeConfigDir ? { claudeConfigDir: resolve3(o.home, opts.claudeConfigDir) } : {}
|
|
944
|
-
}), {
|
|
945
|
-
mode: 493
|
|
946
|
-
});
|
|
947
|
-
chmodSync2(script, 493);
|
|
948
|
-
log(`wrote ${script}`);
|
|
949
|
-
if (opts.sledsHome || opts.claudeConfigDir)
|
|
950
|
-
log(`tracks the seat at ${[opts.sledsHome && `SLEDS_HOME=${opts.sledsHome}`, opts.claudeConfigDir && `CLAUDE_CONFIG_DIR=${opts.claudeConfigDir}`].filter(Boolean).join(" ")}`);
|
|
951
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
952
|
-
if (!dir) {
|
|
953
|
-
log("SwiftBar is not set up yet: `brew install --cask swiftbar`, launch it once and pick a plugin");
|
|
954
|
-
log("folder, then run `sleds bar --install` again — or symlink the file above into that folder.");
|
|
955
|
-
return 0;
|
|
956
|
-
}
|
|
957
|
-
const link = join5(dir, pluginName(opts.name));
|
|
958
|
-
try {
|
|
959
|
-
mkdirSync5(dir, { recursive: true });
|
|
960
|
-
if (existsSync5(link) || isDanglingLink(link))
|
|
961
|
-
unlinkSync(link);
|
|
962
|
-
symlinkSync(script, link);
|
|
963
|
-
log(`linked ${link}`);
|
|
964
|
-
log("SwiftBar picks it up within a minute; the item reads `sleds $… · wk …%`.");
|
|
965
|
-
return 0;
|
|
966
|
-
} catch (e) {
|
|
967
|
-
log(`could not link into ${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
968
|
-
log(`symlink it yourself: ln -sf ${script} ${link}`);
|
|
969
|
-
return 1;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
function uninstall(o, log) {
|
|
973
|
-
const { name } = parseInstallArgs(o.argv ?? [], {});
|
|
974
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
975
|
-
const link = dir ? join5(dir, pluginName(name)) : null;
|
|
976
|
-
if (link && (existsSync5(link) || isDanglingLink(link))) {
|
|
977
|
-
unlinkSync(link);
|
|
978
|
-
log(`removed ${link}`);
|
|
979
|
-
} else
|
|
980
|
-
log("no SwiftBar plugin link to remove");
|
|
981
|
-
const script = pluginPath(o.home, name);
|
|
982
|
-
if (existsSync5(script)) {
|
|
983
|
-
unlinkSync(script);
|
|
984
|
-
log(`removed ${script}`);
|
|
985
|
-
}
|
|
986
|
-
return 0;
|
|
987
|
-
}
|
|
988
|
-
var isDanglingLink = (p) => {
|
|
1269
|
+
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
1270
|
+
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
1271
|
+
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
1272
|
+
});
|
|
1273
|
+
var postJson = async (o, path, body) => {
|
|
1274
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
989
1275
|
try {
|
|
990
|
-
|
|
1276
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
1277
|
+
method: "POST",
|
|
1278
|
+
headers: { authorization: `Bearer ${o.token}`, "content-type": "application/json" },
|
|
1279
|
+
body: JSON.stringify(body),
|
|
1280
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? SEAT_POST_TIMEOUT_MS)
|
|
1281
|
+
});
|
|
1282
|
+
return res.ok;
|
|
991
1283
|
} catch {
|
|
992
1284
|
return false;
|
|
993
1285
|
}
|
|
994
1286
|
};
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
import { join as join6, resolve as resolve4 } from "node:path";
|
|
1005
|
-
var PLUGIN_NAME2 = "sleds.1m.sh";
|
|
1006
|
-
var pluginName2 = (name) => name ? `sleds-${name}.1m.sh` : PLUGIN_NAME2;
|
|
1007
|
-
function swiftBarPluginDir2() {
|
|
1008
|
-
try {
|
|
1009
|
-
const out = execFileSync2("defaults", ["read", "com.ameba.SwiftBar", "PluginDirectory"], {
|
|
1010
|
-
encoding: "utf8",
|
|
1011
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1012
|
-
}).trim();
|
|
1013
|
-
return out ? out.replace(/^~(?=$|\/)/, homedir2()) : null;
|
|
1014
|
-
} catch {
|
|
1015
|
-
return null;
|
|
1287
|
+
var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
|
|
1288
|
+
var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", input);
|
|
1289
|
+
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.";
|
|
1290
|
+
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.";
|
|
1291
|
+
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
1292
|
+
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
|
|
1293
|
+
if (stdin.isTTY) {
|
|
1294
|
+
resolve3("");
|
|
1295
|
+
return;
|
|
1016
1296
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1297
|
+
let data = "";
|
|
1298
|
+
let settled = false;
|
|
1299
|
+
const finish = () => {
|
|
1300
|
+
if (settled)
|
|
1301
|
+
return;
|
|
1302
|
+
settled = true;
|
|
1303
|
+
clearTimeout(timer);
|
|
1304
|
+
resolve3(data);
|
|
1305
|
+
};
|
|
1306
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
1307
|
+
stdin.setEncoding("utf8");
|
|
1308
|
+
stdin.on("data", (chunk) => {
|
|
1309
|
+
data += chunk;
|
|
1310
|
+
});
|
|
1311
|
+
stdin.on("end", finish);
|
|
1312
|
+
stdin.on("error", finish);
|
|
1313
|
+
stdin.resume();
|
|
1314
|
+
});
|
|
1019
1315
|
|
|
1020
1316
|
// src/switch.ts
|
|
1317
|
+
init_settings();
|
|
1021
1318
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1022
1319
|
var envOf2 = (settings) => isObject5(settings.env) ? settings.env : {};
|
|
1023
1320
|
var capturePriorEnv = (settings) => {
|
|
@@ -1092,7 +1389,141 @@ var formatDuration = (ms) => {
|
|
|
1092
1389
|
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
1093
1390
|
};
|
|
1094
1391
|
|
|
1392
|
+
// src/commands/defer.ts
|
|
1393
|
+
var parseAt = (v, now) => {
|
|
1394
|
+
const rel = /^\+(\d+)([hm])$/.exec(v.trim());
|
|
1395
|
+
if (rel) {
|
|
1396
|
+
const n = Number.parseInt(rel[1] ?? "0", 10);
|
|
1397
|
+
return now + n * (rel[2] === "h" ? 3600000 : 60000);
|
|
1398
|
+
}
|
|
1399
|
+
const t = Date.parse(v);
|
|
1400
|
+
return Number.isFinite(t) ? t : undefined;
|
|
1401
|
+
};
|
|
1402
|
+
var parseFlags = (argv) => {
|
|
1403
|
+
const flags = {};
|
|
1404
|
+
for (let i = 0;i < argv.length; i++) {
|
|
1405
|
+
const a = argv[i] ?? "";
|
|
1406
|
+
if (!a.startsWith("--"))
|
|
1407
|
+
continue;
|
|
1408
|
+
const eq = a.indexOf("=");
|
|
1409
|
+
if (eq > 0)
|
|
1410
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
1411
|
+
else {
|
|
1412
|
+
const next = argv[i + 1];
|
|
1413
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
1414
|
+
flags[a.slice(2)] = next;
|
|
1415
|
+
i++;
|
|
1416
|
+
} else
|
|
1417
|
+
flags[a.slice(2)] = "true";
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return flags;
|
|
1421
|
+
};
|
|
1422
|
+
var defer = async (o) => {
|
|
1423
|
+
const log = o.log ?? console.log;
|
|
1424
|
+
const error = o.error ?? console.error;
|
|
1425
|
+
const now = (o.now ?? Date.now)();
|
|
1426
|
+
const flags = parseFlags(o.argv);
|
|
1427
|
+
const client = o.client ?? (() => {
|
|
1428
|
+
const src = meterSource(o.home);
|
|
1429
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
1430
|
+
})();
|
|
1431
|
+
if (!client) {
|
|
1432
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
1433
|
+
return 1;
|
|
1434
|
+
}
|
|
1435
|
+
const state = readSeatState(o.home);
|
|
1436
|
+
const piped = o.stdin ? seatSessionFromStdin(o.stdin, now) : null;
|
|
1437
|
+
const remembered = piped ?? state.session;
|
|
1438
|
+
const sessionId = flags.session ?? remembered?.id;
|
|
1439
|
+
const cwd = flags.cwd ?? remembered?.cwd;
|
|
1440
|
+
const model = flags.model ?? remembered?.model;
|
|
1441
|
+
let resetAt;
|
|
1442
|
+
if (flags.at) {
|
|
1443
|
+
resetAt = parseAt(flags.at, now);
|
|
1444
|
+
if (resetAt === undefined) {
|
|
1445
|
+
error(`--at takes +2h, +90m or an ISO timestamp (got ${flags.at})`);
|
|
1446
|
+
return 1;
|
|
1447
|
+
}
|
|
1448
|
+
} else
|
|
1449
|
+
resetAt = bindingResetAt(state.window, now);
|
|
1450
|
+
if (!sessionId)
|
|
1451
|
+
log("No Claude Code session remembered on this seat yet; the job will say so when it starts. Pass --session <id> to pick one.");
|
|
1452
|
+
const r = await client.defer({
|
|
1453
|
+
...resetAt !== undefined ? { resetAt } : {},
|
|
1454
|
+
...model ? { model } : {},
|
|
1455
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
1456
|
+
...cwd ? { cwd } : {},
|
|
1457
|
+
host: o.host ?? hostname()
|
|
1458
|
+
});
|
|
1459
|
+
if (!r) {
|
|
1460
|
+
error("Could not reach the SLEDS proxy; nothing was scheduled.");
|
|
1461
|
+
return 1;
|
|
1462
|
+
}
|
|
1463
|
+
const when = r.scheduledAt <= now + 60000 ? "as soon as the musher ticks (within 5 min)" : `${formatLocal(r.scheduledAt)} (in ${formatDuration(r.scheduledAt - now)})`;
|
|
1464
|
+
log(`Deferred. This Mac resumes ${sessionId ? `session ${sessionId.slice(0, 8)}…` : "the work"}${model ? ` on ${model}` : ""} ${when}.`);
|
|
1465
|
+
log(` job ${r.jobId}${cwd ? ` · ${cwd}` : ""} · runs on your seat, no key · sleds musher --status to watch`);
|
|
1466
|
+
const cfg = readConfig(o.home);
|
|
1467
|
+
if (process.platform === "darwin" && !existsSync5(musherPlistPath(o.home)) && cfg.musher?.agent !== "launchd")
|
|
1468
|
+
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.`);
|
|
1469
|
+
log(" Keep this Mac awake at that time (System Settings → Battery → prevent sleeping on power adapter).");
|
|
1470
|
+
return 0;
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
// src/commands/doctor.ts
|
|
1474
|
+
init_config();
|
|
1475
|
+
init_meter();
|
|
1476
|
+
import { execFileSync } from "node:child_process";
|
|
1477
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync } from "node:fs";
|
|
1478
|
+
import { resolve as resolve3 } from "node:path";
|
|
1479
|
+
init_settings();
|
|
1480
|
+
|
|
1481
|
+
// src/commands/musher.ts
|
|
1482
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1483
|
+
init_config();
|
|
1484
|
+
init_meter();
|
|
1485
|
+
var defaultWhich = (cmd) => {
|
|
1486
|
+
const path = process.env.PATH ?? "";
|
|
1487
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
1488
|
+
const p = `${dir}/${cmd}`;
|
|
1489
|
+
if (existsSync6(p))
|
|
1490
|
+
return p;
|
|
1491
|
+
}
|
|
1492
|
+
return null;
|
|
1493
|
+
};
|
|
1494
|
+
var findClaude = (home, configured, which) => {
|
|
1495
|
+
if (configured && existsSync6(configured))
|
|
1496
|
+
return configured;
|
|
1497
|
+
const found = which("claude");
|
|
1498
|
+
if (found)
|
|
1499
|
+
return found;
|
|
1500
|
+
for (const p of [
|
|
1501
|
+
`${home}/.local/bin/claude`,
|
|
1502
|
+
"/opt/homebrew/bin/claude",
|
|
1503
|
+
"/usr/local/bin/claude",
|
|
1504
|
+
`${home}/.npm-global/bin/claude`
|
|
1505
|
+
])
|
|
1506
|
+
if (existsSync6(p))
|
|
1507
|
+
return p;
|
|
1508
|
+
return null;
|
|
1509
|
+
};
|
|
1510
|
+
var musherAgentState = (home, env = process.env) => {
|
|
1511
|
+
const plist = musherPlistPath(home, env);
|
|
1512
|
+
if (!existsSync6(plist))
|
|
1513
|
+
return { installed: false, thisSeat: false };
|
|
1514
|
+
try {
|
|
1515
|
+
const text = readFileSync5(plist, "utf8");
|
|
1516
|
+
const want = env.SLEDS_HOME?.trim();
|
|
1517
|
+
const has = /<key>SLEDS_HOME<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1];
|
|
1518
|
+
return { installed: true, thisSeat: (want ?? undefined) === (has ?? undefined) };
|
|
1519
|
+
} catch {
|
|
1520
|
+
return { installed: true, thisSeat: false };
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1095
1524
|
// src/commands/status.ts
|
|
1525
|
+
init_config();
|
|
1526
|
+
init_settings();
|
|
1096
1527
|
var checkHealth = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
1097
1528
|
try {
|
|
1098
1529
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -1110,16 +1541,6 @@ function nodeOk(version, min = NODE_MIN) {
|
|
|
1110
1541
|
const [x, y, z] = parse(min);
|
|
1111
1542
|
return (a ?? 0) !== (x ?? 0) ? (a ?? 0) > (x ?? 0) : (b ?? 0) !== (y ?? 0) ? (b ?? 0) > (y ?? 0) : (c ?? 0) >= (z ?? 0);
|
|
1112
1543
|
}
|
|
1113
|
-
function pluginEnv(body) {
|
|
1114
|
-
const out = {};
|
|
1115
|
-
const home = /^export SLEDS_HOME='([^']*)'/m.exec(body);
|
|
1116
|
-
const cfg = /^export CLAUDE_CONFIG_DIR='([^']*)'/m.exec(body);
|
|
1117
|
-
if (home?.[1])
|
|
1118
|
-
out.sledsHome = home[1];
|
|
1119
|
-
if (cfg?.[1])
|
|
1120
|
-
out.claudeConfigDir = cfg[1];
|
|
1121
|
-
return out;
|
|
1122
|
-
}
|
|
1123
1544
|
var MARK = { ok: "ok ", warn: "warn", fail: "FAIL" };
|
|
1124
1545
|
function formatDoctor(checks) {
|
|
1125
1546
|
const lines = checks.map((c) => `${MARK[c.level]} ${c.name.padEnd(16)}${c.detail}`);
|
|
@@ -1135,9 +1556,9 @@ function formatDoctor(checks) {
|
|
|
1135
1556
|
lines.push(fails === 0 && warns === 0 ? "All good." : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`);
|
|
1136
1557
|
return lines;
|
|
1137
1558
|
}
|
|
1138
|
-
function
|
|
1559
|
+
function defaultWhich2(cmd) {
|
|
1139
1560
|
try {
|
|
1140
|
-
return
|
|
1561
|
+
return execFileSync("which", ["-a", cmd], {
|
|
1141
1562
|
encoding: "utf8",
|
|
1142
1563
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1143
1564
|
}).split(`
|
|
@@ -1146,16 +1567,6 @@ function defaultWhich(cmd) {
|
|
|
1146
1567
|
return [];
|
|
1147
1568
|
}
|
|
1148
1569
|
}
|
|
1149
|
-
function defaultLaunchctl() {
|
|
1150
|
-
try {
|
|
1151
|
-
return execFileSync3("launchctl", ["list"], {
|
|
1152
|
-
encoding: "utf8",
|
|
1153
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1154
|
-
});
|
|
1155
|
-
} catch {
|
|
1156
|
-
return "";
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
1570
|
async function runDoctor(o) {
|
|
1160
1571
|
const env = o.env ?? process.env;
|
|
1161
1572
|
const checks = [];
|
|
@@ -1166,7 +1577,7 @@ async function runDoctor(o) {
|
|
|
1166
1577
|
].filter(Boolean);
|
|
1167
1578
|
const cfgDir = configDir(home, env);
|
|
1168
1579
|
const cfg = readConfig(home);
|
|
1169
|
-
const cfgExists =
|
|
1580
|
+
const cfgExists = existsSync7(configPath(home));
|
|
1170
1581
|
if (!cfgExists) {
|
|
1171
1582
|
checks.push({
|
|
1172
1583
|
level: "fail",
|
|
@@ -1182,7 +1593,7 @@ async function runDoctor(o) {
|
|
|
1182
1593
|
});
|
|
1183
1594
|
}
|
|
1184
1595
|
const sPath = settingsPath(home);
|
|
1185
|
-
const settings =
|
|
1596
|
+
const settings = existsSync7(sPath) ? readSettings(home) : null;
|
|
1186
1597
|
const pointed = settings ? readProxy(settings) : null;
|
|
1187
1598
|
if (!settings) {
|
|
1188
1599
|
checks.push({
|
|
@@ -1226,7 +1637,7 @@ async function runDoctor(o) {
|
|
|
1226
1637
|
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
|
|
1227
1638
|
fix: seat2 && spec ? undefined : "sleds hooks"
|
|
1228
1639
|
});
|
|
1229
|
-
const want = env.SLEDS_HOME?.trim() ?
|
|
1640
|
+
const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
|
|
1230
1641
|
const cmds = sledsCommandsIn(settings);
|
|
1231
1642
|
const wrong = cmds.filter((c) => {
|
|
1232
1643
|
const got = bakedHome(c);
|
|
@@ -1240,7 +1651,7 @@ async function runDoctor(o) {
|
|
|
1240
1651
|
fix: wrong.length === 0 ? undefined : `${overrides.length ? `${overrides.join(" ")} ` : ""}sleds hooks (re-writes the status line + hooks with this seat baked in)`
|
|
1241
1652
|
});
|
|
1242
1653
|
}
|
|
1243
|
-
const skill =
|
|
1654
|
+
const skill = existsSync7(skillPath(home));
|
|
1244
1655
|
checks.push({
|
|
1245
1656
|
level: skill ? "ok" : "warn",
|
|
1246
1657
|
name: "skill",
|
|
@@ -1255,10 +1666,10 @@ async function runDoctor(o) {
|
|
|
1255
1666
|
detail: `${nodeVersion}${nodeOk(nodeVersion) ? "" : ` — seat-sync needs ≥ ${NODE_MIN} (node:sqlite)`}`,
|
|
1256
1667
|
fix: nodeOk(nodeVersion) ? undefined : `brew install node@22 && echo 'export PATH="/opt/homebrew/opt/node@22/bin:$PATH"' >> ~/.zshrc`
|
|
1257
1668
|
});
|
|
1258
|
-
const which = (o.which ??
|
|
1669
|
+
const which = (o.which ?? defaultWhich2)("sleds");
|
|
1259
1670
|
const stale = which.filter((p) => {
|
|
1260
1671
|
try {
|
|
1261
|
-
const target =
|
|
1672
|
+
const target = lstatSync(p).isSymbolicLink() ? readlinkSync(p) : p;
|
|
1262
1673
|
return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p);
|
|
1263
1674
|
} catch {
|
|
1264
1675
|
return false;
|
|
@@ -1273,49 +1684,68 @@ async function runDoctor(o) {
|
|
|
1273
1684
|
});
|
|
1274
1685
|
else if (which.length > 0)
|
|
1275
1686
|
checks.push({ level: "ok", name: "path", detail: which[0] ?? "" });
|
|
1276
|
-
const pluginBody = existsSync6(pluginPath2(home)) ? readFileSync5(pluginPath2(home), "utf8") : null;
|
|
1277
|
-
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir2)();
|
|
1278
|
-
if (pluginBody) {
|
|
1279
|
-
const baked = pluginEnv(pluginBody);
|
|
1280
|
-
const linked = dir ? existsSync6(join7(dir, PLUGIN_NAME2)) : false;
|
|
1281
|
-
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";
|
|
1282
|
-
checks.push({
|
|
1283
|
-
level: linked ? "ok" : "warn",
|
|
1284
|
-
name: "menu bar",
|
|
1285
|
-
detail: linked ? `${PLUGIN_NAME2} linked · ${seatDesc}` : `plugin written but not linked into SwiftBar${dir ? ` (${dir})` : " (SwiftBar not set up)"}`,
|
|
1286
|
-
fix: linked ? undefined : "sleds bar --install"
|
|
1287
|
-
});
|
|
1288
|
-
} else {
|
|
1289
|
-
checks.push({
|
|
1290
|
-
level: "warn",
|
|
1291
|
-
name: "menu bar",
|
|
1292
|
-
detail: "no SwiftBar plugin",
|
|
1293
|
-
fix: "sleds bar --install"
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
const agents = join7(home, "Library", "LaunchAgents");
|
|
1297
|
-
const plists = existsSync6(agents) ? readdirSync(agents).filter((f) => f.startsWith("ai.sleds.") && f.endsWith(".plist")) : [];
|
|
1298
|
-
const loaded = (o.launchctlList ?? defaultLaunchctl)();
|
|
1299
1687
|
const seat = readSeatState(home);
|
|
1300
1688
|
const age = seat.window ? Math.round((Date.now() - seat.window.capturedAt) / 60000) : null;
|
|
1301
|
-
|
|
1302
|
-
|
|
1689
|
+
checks.push({
|
|
1690
|
+
level: age === null ? "warn" : "ok",
|
|
1691
|
+
name: "seat quota",
|
|
1692
|
+
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)}%` : ""}`,
|
|
1693
|
+
fix: age === null ? "open claude, say hi, exit — the status line posts the seat's quota" : undefined
|
|
1694
|
+
});
|
|
1695
|
+
if ((o.platform ?? process.platform) === "darwin") {
|
|
1696
|
+
const agent = musherAgentState(home, env);
|
|
1697
|
+
checks.push(!agent.installed ? {
|
|
1303
1698
|
level: "warn",
|
|
1304
|
-
name: "
|
|
1305
|
-
detail: "
|
|
1306
|
-
fix: "sleds
|
|
1699
|
+
name: "musher",
|
|
1700
|
+
detail: "agent not installed — `sleds defer` jobs would wait for `sleds run`",
|
|
1701
|
+
fix: "sleds musher --install"
|
|
1702
|
+
} : !agent.thisSeat ? {
|
|
1703
|
+
level: "warn",
|
|
1704
|
+
name: "musher",
|
|
1705
|
+
detail: "agent installed for a different seat (SLEDS_HOME differs)",
|
|
1706
|
+
fix: "sleds musher --install"
|
|
1707
|
+
} : {
|
|
1708
|
+
level: "ok",
|
|
1709
|
+
name: "musher",
|
|
1710
|
+
detail: `launchd agent ${musherLabel(env)}, every 5 min`
|
|
1307
1711
|
});
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
fix: running.length === plists.length && age !== null && age < 6 * 60 ? undefined : "sleds seat-sync (run once by hand and read its message)"
|
|
1712
|
+
const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o.which ?? defaultWhich2)(c)[0] ?? null);
|
|
1713
|
+
checks.push(claude ? { level: "ok", name: "claude cli", detail: claude } : {
|
|
1714
|
+
level: "warn",
|
|
1715
|
+
name: "claude cli",
|
|
1716
|
+
detail: "`claude` not on PATH — the musher cannot run Claude Code",
|
|
1717
|
+
fix: 'sleds musher --claude "$(which claude)"'
|
|
1315
1718
|
});
|
|
1719
|
+
const sleep = (o.pmset ?? defaultPmset)();
|
|
1720
|
+
if (sleep !== null)
|
|
1721
|
+
checks.push(sleep.acSleepMinutes === 0 ? {
|
|
1722
|
+
level: "ok",
|
|
1723
|
+
name: "awake",
|
|
1724
|
+
detail: "does not sleep on power — deferred jobs can start"
|
|
1725
|
+
} : {
|
|
1726
|
+
level: "warn",
|
|
1727
|
+
name: "awake",
|
|
1728
|
+
detail: `sleeps after ${sleep.acSleepMinutes} min on power — a deferred job needs the Mac awake at the reset`,
|
|
1729
|
+
fix: "System Settings → Battery → Options → Prevent automatic sleeping on power adapter (or: sudo pmset -c sleep 0)"
|
|
1730
|
+
});
|
|
1316
1731
|
}
|
|
1317
1732
|
return checks;
|
|
1318
1733
|
}
|
|
1734
|
+
function parsePmset(text) {
|
|
1735
|
+
const ac = text.split(/AC Power:/i)[1];
|
|
1736
|
+
const block = ac ?? text;
|
|
1737
|
+
const m = /^\s*sleep\s+(\d+)/m.exec(block);
|
|
1738
|
+
if (!m)
|
|
1739
|
+
return null;
|
|
1740
|
+
return { acSleepMinutes: Number.parseInt(m[1] ?? "0", 10) };
|
|
1741
|
+
}
|
|
1742
|
+
var defaultPmset = () => {
|
|
1743
|
+
try {
|
|
1744
|
+
return parsePmset(execFileSync("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
|
|
1745
|
+
} catch {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1319
1749
|
var doctor = async (o) => {
|
|
1320
1750
|
const log = o.log ?? console.log;
|
|
1321
1751
|
const checks = await runDoctor(o);
|
|
@@ -1325,6 +1755,7 @@ var doctor = async (o) => {
|
|
|
1325
1755
|
};
|
|
1326
1756
|
|
|
1327
1757
|
// src/commands/hooks.ts
|
|
1758
|
+
init_settings();
|
|
1328
1759
|
var hooks = (o) => {
|
|
1329
1760
|
const log = o.log ?? console.log;
|
|
1330
1761
|
const error = o.error ?? console.error;
|
|
@@ -1347,6 +1778,8 @@ var hooks = (o) => {
|
|
|
1347
1778
|
};
|
|
1348
1779
|
|
|
1349
1780
|
// src/commands/init.ts
|
|
1781
|
+
init_config();
|
|
1782
|
+
init_settings();
|
|
1350
1783
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1351
1784
|
import { createInterface } from "node:readline";
|
|
1352
1785
|
|
|
@@ -1383,6 +1816,8 @@ When the proxy is active it answers a walled request with its options. The user
|
|
|
1383
1816
|
/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)
|
|
1384
1817
|
\`\`\`
|
|
1385
1818
|
|
|
1819
|
+
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\`.
|
|
1820
|
+
|
|
1386
1821
|
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.
|
|
1387
1822
|
|
|
1388
1823
|
## Window reset
|
|
@@ -1397,9 +1832,9 @@ then restart \`claude\`. Settings go back to exactly what they were.
|
|
|
1397
1832
|
|
|
1398
1833
|
## Check
|
|
1399
1834
|
|
|
1400
|
-
\`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\`) and hand-off (\`/sled 5\`)
|
|
1835
|
+
\`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.
|
|
1401
1836
|
|
|
1402
|
-
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
|
|
1837
|
+
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).
|
|
1403
1838
|
|
|
1404
1839
|
## /sled — estimate the task, offer overnight
|
|
1405
1840
|
|
|
@@ -1474,6 +1909,7 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
|
|
|
1474
1909
|
`;
|
|
1475
1910
|
|
|
1476
1911
|
// src/commands/hooks.ts
|
|
1912
|
+
init_settings();
|
|
1477
1913
|
var hooks2 = (o) => {
|
|
1478
1914
|
const log = o.log ?? console.log;
|
|
1479
1915
|
const error = o.error ?? console.error;
|
|
@@ -1497,11 +1933,11 @@ var hooks2 = (o) => {
|
|
|
1497
1933
|
|
|
1498
1934
|
// src/commands/init.ts
|
|
1499
1935
|
var DEFAULT_PROXY_URL = "http://localhost:8787";
|
|
1500
|
-
var promptStdin = (question) => new Promise((
|
|
1936
|
+
var promptStdin = (question) => new Promise((resolve4) => {
|
|
1501
1937
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1502
1938
|
rl.question(question, (answer) => {
|
|
1503
1939
|
rl.close();
|
|
1504
|
-
|
|
1940
|
+
resolve4(answer.trim());
|
|
1505
1941
|
});
|
|
1506
1942
|
});
|
|
1507
1943
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
|
|
@@ -1520,7 +1956,12 @@ async function signupRequest(proxyUrl, payload, fetchFn) {
|
|
|
1520
1956
|
});
|
|
1521
1957
|
const body = await res.json().catch(() => ({}));
|
|
1522
1958
|
if (res.ok && typeof body.token === "string")
|
|
1523
|
-
return {
|
|
1959
|
+
return {
|
|
1960
|
+
ok: true,
|
|
1961
|
+
token: body.token,
|
|
1962
|
+
created: body.created === true,
|
|
1963
|
+
verifyPending: body.verifyPending === true
|
|
1964
|
+
};
|
|
1524
1965
|
return {
|
|
1525
1966
|
ok: false,
|
|
1526
1967
|
reason: typeof body.reason === "string" ? body.reason : `http_${res.status}`,
|
|
@@ -1606,7 +2047,7 @@ var resolveToken = async (o) => {
|
|
|
1606
2047
|
if (typed)
|
|
1607
2048
|
return { token: typed, source: "prompt" };
|
|
1608
2049
|
}
|
|
1609
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2050
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1610
2051
|
if (!url)
|
|
1611
2052
|
return null;
|
|
1612
2053
|
const log = o.log ?? console.log;
|
|
@@ -1631,6 +2072,8 @@ var resolveToken = async (o) => {
|
|
|
1631
2072
|
return null;
|
|
1632
2073
|
}
|
|
1633
2074
|
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."}`);
|
|
2075
|
+
if (r2.verifyPending)
|
|
2076
|
+
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).`);
|
|
1634
2077
|
log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
|
|
1635
2078
|
return { token: r2.token, source: "signup" };
|
|
1636
2079
|
}
|
|
@@ -1662,7 +2105,7 @@ var init = async (o) => {
|
|
|
1662
2105
|
error("Invalid seat token: expected something like sled_xxxxxxxx.");
|
|
1663
2106
|
return 2;
|
|
1664
2107
|
}
|
|
1665
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2108
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1666
2109
|
if (!url) {
|
|
1667
2110
|
error(`Invalid proxy URL: ${JSON.stringify(o.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
|
|
1668
2111
|
return 2;
|
|
@@ -1712,6 +2155,11 @@ var init = async (o) => {
|
|
|
1712
2155
|
mkdirSync6(skillDir(o.home), { recursive: true });
|
|
1713
2156
|
writeFileSync6(skillPath(o.home), SKILL_MD);
|
|
1714
2157
|
wrote.push(`installed skill at ${skillPath(o.home)}`);
|
|
2158
|
+
if (o.installMusher) {
|
|
2159
|
+
const r = o.installMusher();
|
|
2160
|
+
if (r)
|
|
2161
|
+
wrote.push(r);
|
|
2162
|
+
}
|
|
1715
2163
|
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)";
|
|
1716
2164
|
if (finalWiring === "proxy-always") {
|
|
1717
2165
|
log(`Claude Code now runs through SLEDS on the org key. (${orgLine})`);
|
|
@@ -1730,7 +2178,7 @@ var init = async (o) => {
|
|
|
1730
2178
|
log("");
|
|
1731
2179
|
log("Claude Code keeps running on your seat; the status line reports its quota to SLEDS on every turn.");
|
|
1732
2180
|
if (org && !org.capabilities.upstreamKey) {
|
|
1733
|
-
log("This org has no platform key, so at a wall the choices are hold until reset or hand off to a teammate. An admin can add a key on the dashboard to unlock continue / overnight / switch.");
|
|
2181
|
+
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.");
|
|
1734
2182
|
} else {
|
|
1735
2183
|
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.");
|
|
1736
2184
|
}
|
|
@@ -1739,6 +2187,7 @@ var init = async (o) => {
|
|
|
1739
2187
|
};
|
|
1740
2188
|
|
|
1741
2189
|
// src/mcp.ts
|
|
2190
|
+
init_meter();
|
|
1742
2191
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
1743
2192
|
var SERVER_INFO = { name: "sleds", version: "0.0.1" };
|
|
1744
2193
|
var MCP_TIMEOUT_MS = 5000;
|
|
@@ -2272,6 +2721,8 @@ var mcp = async (o) => {
|
|
|
2272
2721
|
};
|
|
2273
2722
|
|
|
2274
2723
|
// src/commands/meter.ts
|
|
2724
|
+
init_config();
|
|
2725
|
+
init_meter();
|
|
2275
2726
|
var meter = async (o) => {
|
|
2276
2727
|
const log = o.log ?? console.log;
|
|
2277
2728
|
const now = (o.now ?? Date.now)();
|
|
@@ -2290,14 +2741,235 @@ var meter = async (o) => {
|
|
|
2290
2741
|
return 0;
|
|
2291
2742
|
};
|
|
2292
2743
|
|
|
2744
|
+
// src/commands/musher.ts
|
|
2745
|
+
init_config();
|
|
2746
|
+
init_meter();
|
|
2747
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2748
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2749
|
+
import { hostname as hostname2 } from "node:os";
|
|
2750
|
+
import { dirname as dirname3 } from "node:path";
|
|
2751
|
+
var defaultWhich3 = (cmd) => {
|
|
2752
|
+
const path = process.env.PATH ?? "";
|
|
2753
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
2754
|
+
const p = `${dir}/${cmd}`;
|
|
2755
|
+
if (existsSync8(p))
|
|
2756
|
+
return p;
|
|
2757
|
+
}
|
|
2758
|
+
return null;
|
|
2759
|
+
};
|
|
2760
|
+
var findClaude2 = (home, configured, which) => {
|
|
2761
|
+
if (configured && existsSync8(configured))
|
|
2762
|
+
return configured;
|
|
2763
|
+
const found = which("claude");
|
|
2764
|
+
if (found)
|
|
2765
|
+
return found;
|
|
2766
|
+
for (const p of [
|
|
2767
|
+
`${home}/.local/bin/claude`,
|
|
2768
|
+
"/opt/homebrew/bin/claude",
|
|
2769
|
+
"/usr/local/bin/claude",
|
|
2770
|
+
`${home}/.npm-global/bin/claude`
|
|
2771
|
+
])
|
|
2772
|
+
if (existsSync8(p))
|
|
2773
|
+
return p;
|
|
2774
|
+
return null;
|
|
2775
|
+
};
|
|
2776
|
+
var musherInstall = (o) => {
|
|
2777
|
+
const log = o.log ?? console.log;
|
|
2778
|
+
const error = o.error ?? console.error;
|
|
2779
|
+
const platform = o.platform ?? process.platform;
|
|
2780
|
+
if (platform !== "darwin") {
|
|
2781
|
+
error(`sleds musher --install needs launchd (macOS). On ${platform}, run \`sleds musher tick\` from cron every 5 minutes.`);
|
|
2782
|
+
return 1;
|
|
2783
|
+
}
|
|
2784
|
+
const env = o.env ?? process.env;
|
|
2785
|
+
const plist = musherPlistPath(o.home, env);
|
|
2786
|
+
const label = musherLabel(env);
|
|
2787
|
+
const dir = configDir(o.home, env);
|
|
2788
|
+
mkdirSync7(dirname3(plist), { recursive: true });
|
|
2789
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2790
|
+
const execPath = o.sledsArgv[0] ?? process.execPath;
|
|
2791
|
+
const agentEnv = {
|
|
2792
|
+
PATH: musherPath(o.home, execPath, env.PATH ?? ""),
|
|
2793
|
+
HOME: o.home,
|
|
2794
|
+
...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
|
|
2795
|
+
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
2796
|
+
};
|
|
2797
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2798
|
+
if (existsSync8(plist))
|
|
2799
|
+
exec("launchctl", ["unload", plist]);
|
|
2800
|
+
writeFileSync7(plist, buildMusherPlist({ sledsArgv: o.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
|
|
2801
|
+
const r = exec("launchctl", ["load", plist]);
|
|
2802
|
+
if (!r.ok) {
|
|
2803
|
+
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
2804
|
+
return 1;
|
|
2805
|
+
}
|
|
2806
|
+
const cfg = readConfig(o.home);
|
|
2807
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
|
|
2808
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2809
|
+
log(`musher installed: launchd agent ${label}, every 5 min · log ${musherLogPath(dir)}`);
|
|
2810
|
+
if (!claude)
|
|
2811
|
+
log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
|
|
2812
|
+
return 0;
|
|
2813
|
+
};
|
|
2814
|
+
var musherUninstall = (o) => {
|
|
2815
|
+
const log = o.log ?? console.log;
|
|
2816
|
+
const env = o.env ?? process.env;
|
|
2817
|
+
const plist = musherPlistPath(o.home, env);
|
|
2818
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2819
|
+
if (existsSync8(plist)) {
|
|
2820
|
+
exec("launchctl", ["unload", plist]);
|
|
2821
|
+
rmSync2(plist, { force: true });
|
|
2822
|
+
log(`musher removed: ${musherLabel(env)}`);
|
|
2823
|
+
} else
|
|
2824
|
+
log("musher was not installed.");
|
|
2825
|
+
const cfg = readConfig(o.home);
|
|
2826
|
+
if (cfg.musher)
|
|
2827
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
|
|
2828
|
+
return 0;
|
|
2829
|
+
};
|
|
2830
|
+
var formatMusherStatus = (list, now) => {
|
|
2831
|
+
const lines = [];
|
|
2832
|
+
for (const j of list.running)
|
|
2833
|
+
lines.push(`running ${j.jobId}${j.startedAt ? ` · since ${formatLocal(j.startedAt)} (${formatDuration(now - j.startedAt)})` : ""}`);
|
|
2834
|
+
for (const j of list.due)
|
|
2835
|
+
lines.push(`due now ${j.jobId}${j.model ? ` · ${j.model}` : ""} · was ${formatLocal(j.scheduledAt)}`);
|
|
2836
|
+
for (const j of list.upcoming)
|
|
2837
|
+
lines.push(`waiting ${j.jobId}${j.model ? ` · ${j.model}` : ""} · starts ${formatLocal(j.scheduledAt)} (in ${formatDuration(j.scheduledAt - now)})`);
|
|
2838
|
+
if (lines.length === 0)
|
|
2839
|
+
lines.push("nothing deferred on this seat.");
|
|
2840
|
+
return lines;
|
|
2841
|
+
};
|
|
2842
|
+
var clientFor = (o) => {
|
|
2843
|
+
if (o.client)
|
|
2844
|
+
return o.client;
|
|
2845
|
+
const src = meterSource(o.home);
|
|
2846
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
2847
|
+
};
|
|
2848
|
+
var musherStatus = async (o) => {
|
|
2849
|
+
const log = o.log ?? console.log;
|
|
2850
|
+
const now = (o.now ?? Date.now)();
|
|
2851
|
+
const env = o.env ?? process.env;
|
|
2852
|
+
const plist = musherPlistPath(o.home, env);
|
|
2853
|
+
const cfg = readConfig(o.home);
|
|
2854
|
+
log(existsSync8(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
2855
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2856
|
+
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
2857
|
+
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2858
|
+
const client = clientFor(o);
|
|
2859
|
+
if (!client) {
|
|
2860
|
+
log("seat not initialised (sleds init)");
|
|
2861
|
+
return 1;
|
|
2862
|
+
}
|
|
2863
|
+
const list = await client.list();
|
|
2864
|
+
if (!list) {
|
|
2865
|
+
log("proxy unreachable");
|
|
2866
|
+
return 1;
|
|
2867
|
+
}
|
|
2868
|
+
for (const line of formatMusherStatus(list, now))
|
|
2869
|
+
log(line);
|
|
2870
|
+
return 0;
|
|
2871
|
+
};
|
|
2872
|
+
var musherTick = async (o) => {
|
|
2873
|
+
const log = o.log ?? console.log;
|
|
2874
|
+
const client = clientFor(o);
|
|
2875
|
+
if (!client) {
|
|
2876
|
+
log("musher: seat not initialised; nothing to do");
|
|
2877
|
+
return 1;
|
|
2878
|
+
}
|
|
2879
|
+
const list = await client.list();
|
|
2880
|
+
if (!list) {
|
|
2881
|
+
log("musher: proxy unreachable; will try again next tick");
|
|
2882
|
+
return 0;
|
|
2883
|
+
}
|
|
2884
|
+
if (list.due.length === 0)
|
|
2885
|
+
return 0;
|
|
2886
|
+
const cfg = readConfig(o.home);
|
|
2887
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3) ?? "claude";
|
|
2888
|
+
const seat = readSeatState(o.home);
|
|
2889
|
+
for (const job of list.due) {
|
|
2890
|
+
const out = await runJob({
|
|
2891
|
+
job,
|
|
2892
|
+
client,
|
|
2893
|
+
fallbackSession: seat.session,
|
|
2894
|
+
host: o.host ?? hostname2(),
|
|
2895
|
+
claudePath: claude,
|
|
2896
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
2897
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
2898
|
+
exec: o.execCapture,
|
|
2899
|
+
log
|
|
2900
|
+
});
|
|
2901
|
+
if (out.kind === "skipped")
|
|
2902
|
+
log(`musher: job ${job.jobId} skipped — ${out.reason}`);
|
|
2903
|
+
}
|
|
2904
|
+
return 0;
|
|
2905
|
+
};
|
|
2906
|
+
var musherConfigure = (o, flags) => {
|
|
2907
|
+
const log = o.log ?? console.log;
|
|
2908
|
+
const cfg = readConfig(o.home);
|
|
2909
|
+
const musher = { ...cfg.musher ?? {} };
|
|
2910
|
+
if (flags.claude)
|
|
2911
|
+
musher.claudePath = flags.claude;
|
|
2912
|
+
if (flags.mode)
|
|
2913
|
+
musher.permissionMode = flags.mode;
|
|
2914
|
+
if (flags.timeout) {
|
|
2915
|
+
const n = Number.parseInt(flags.timeout, 10);
|
|
2916
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
2917
|
+
(o.error ?? console.error)("--timeout takes minutes, a positive number");
|
|
2918
|
+
return 1;
|
|
2919
|
+
}
|
|
2920
|
+
musher.timeoutMinutes = n;
|
|
2921
|
+
}
|
|
2922
|
+
writeConfig(o.home, { ...cfg, musher });
|
|
2923
|
+
log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2924
|
+
return 0;
|
|
2925
|
+
};
|
|
2926
|
+
var defaultExecSync = (cmd, args) => {
|
|
2927
|
+
const r = spawnSync2(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
2928
|
+
if (r.error)
|
|
2929
|
+
return { ok: false, stderr: r.error.message };
|
|
2930
|
+
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
2931
|
+
};
|
|
2932
|
+
var musher = async (o) => {
|
|
2933
|
+
const args = [...o.argv];
|
|
2934
|
+
const flags = {};
|
|
2935
|
+
const positional = [];
|
|
2936
|
+
for (let i = 0;i < args.length; i++) {
|
|
2937
|
+
const a = args[i] ?? "";
|
|
2938
|
+
if (a.startsWith("--")) {
|
|
2939
|
+
const next = args[i + 1];
|
|
2940
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
2941
|
+
flags[a.slice(2)] = next;
|
|
2942
|
+
i++;
|
|
2943
|
+
} else
|
|
2944
|
+
flags[a.slice(2)] = "true";
|
|
2945
|
+
} else
|
|
2946
|
+
positional.push(a);
|
|
2947
|
+
}
|
|
2948
|
+
if (flags.install)
|
|
2949
|
+
return musherInstall(o);
|
|
2950
|
+
if (flags.uninstall)
|
|
2951
|
+
return musherUninstall(o);
|
|
2952
|
+
if (flags.claude || flags.mode || flags.timeout)
|
|
2953
|
+
return musherConfigure(o, flags);
|
|
2954
|
+
if (positional[0] === "tick")
|
|
2955
|
+
return musherTick(o);
|
|
2956
|
+
if (flags.status || positional.length === 0)
|
|
2957
|
+
return musherStatus(o);
|
|
2958
|
+
(o.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
|
|
2959
|
+
return 1;
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
// src/commands/off.ts
|
|
2963
|
+
init_config();
|
|
2964
|
+
|
|
2293
2965
|
// src/schedule.ts
|
|
2294
|
-
import { existsSync as
|
|
2295
|
-
import { join as
|
|
2966
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
2967
|
+
import { join as join7 } from "node:path";
|
|
2296
2968
|
var REVERT_LABEL = "ai.sleds.revert";
|
|
2297
|
-
var plistPath = (home) =>
|
|
2298
|
-
var
|
|
2969
|
+
var plistPath = (home) => join7(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
|
|
2970
|
+
var xmlEscape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2299
2971
|
var buildLaunchdPlist = (sledsArgv, deadline) => {
|
|
2300
|
-
const args = [...sledsArgv, "off"].map((a) => ` <string>${
|
|
2972
|
+
const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
|
|
2301
2973
|
`);
|
|
2302
2974
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2303
2975
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2333,9 +3005,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2333
3005
|
if (d.platform === "darwin") {
|
|
2334
3006
|
const p = plistPath(d.home);
|
|
2335
3007
|
try {
|
|
2336
|
-
|
|
3008
|
+
mkdirSync8(join7(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
2337
3009
|
d.exec("launchctl", ["unload", p]);
|
|
2338
|
-
|
|
3010
|
+
writeFileSync8(p, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
2339
3011
|
const r = d.exec("launchctl", ["load", p]);
|
|
2340
3012
|
if (r.ok)
|
|
2341
3013
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
@@ -2362,9 +3034,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2362
3034
|
var cancelRevert = (d, kind) => {
|
|
2363
3035
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
2364
3036
|
const p = plistPath(d.home);
|
|
2365
|
-
if (
|
|
3037
|
+
if (existsSync9(p)) {
|
|
2366
3038
|
d.exec("launchctl", ["unload", p]);
|
|
2367
|
-
|
|
3039
|
+
rmSync3(p, { force: true });
|
|
2368
3040
|
}
|
|
2369
3041
|
}
|
|
2370
3042
|
if (kind === "systemd" || d.platform === "linux") {
|
|
@@ -2373,6 +3045,7 @@ var cancelRevert = (d, kind) => {
|
|
|
2373
3045
|
};
|
|
2374
3046
|
|
|
2375
3047
|
// src/commands/off.ts
|
|
3048
|
+
init_settings();
|
|
2376
3049
|
var off = (o) => {
|
|
2377
3050
|
const log = o.log ?? console.log;
|
|
2378
3051
|
const cfg = readConfig(o.home);
|
|
@@ -2400,7 +3073,13 @@ var off = (o) => {
|
|
|
2400
3073
|
return 0;
|
|
2401
3074
|
};
|
|
2402
3075
|
|
|
3076
|
+
// src/commands/on.ts
|
|
3077
|
+
init_config();
|
|
3078
|
+
init_settings();
|
|
3079
|
+
|
|
2403
3080
|
// src/commands/init.ts
|
|
3081
|
+
init_config();
|
|
3082
|
+
init_settings();
|
|
2404
3083
|
var DEFAULT_PROXY_URL2 = "http://localhost:8787";
|
|
2405
3084
|
|
|
2406
3085
|
// src/commands/on.ts
|
|
@@ -2480,7 +3159,70 @@ var on = (o) => {
|
|
|
2480
3159
|
return 0;
|
|
2481
3160
|
};
|
|
2482
3161
|
|
|
3162
|
+
// src/commands/run.ts
|
|
3163
|
+
init_config();
|
|
3164
|
+
init_meter();
|
|
3165
|
+
import { hostname as hostname3 } from "node:os";
|
|
3166
|
+
var run = async (o) => {
|
|
3167
|
+
const log = o.log ?? console.log;
|
|
3168
|
+
const error = o.error ?? console.error;
|
|
3169
|
+
if (!o.jobId) {
|
|
3170
|
+
error("usage: sleds run <jobId>");
|
|
3171
|
+
return 2;
|
|
3172
|
+
}
|
|
3173
|
+
const client = o.client ?? (() => {
|
|
3174
|
+
const src = meterSource(o.home);
|
|
3175
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
3176
|
+
})();
|
|
3177
|
+
if (!client) {
|
|
3178
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
3179
|
+
return 1;
|
|
3180
|
+
}
|
|
3181
|
+
const list = await client.list();
|
|
3182
|
+
if (!list) {
|
|
3183
|
+
error("Could not reach the SLEDS proxy.");
|
|
3184
|
+
return 1;
|
|
3185
|
+
}
|
|
3186
|
+
const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o.jobId);
|
|
3187
|
+
if (!job) {
|
|
3188
|
+
error(`Job ${o.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
|
|
3189
|
+
return 1;
|
|
3190
|
+
}
|
|
3191
|
+
const cfg = readConfig(o.home);
|
|
3192
|
+
const claude = findClaude(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich);
|
|
3193
|
+
if (!claude) {
|
|
3194
|
+
error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
|
|
3195
|
+
return 1;
|
|
3196
|
+
}
|
|
3197
|
+
const out = await runJob({
|
|
3198
|
+
job,
|
|
3199
|
+
client,
|
|
3200
|
+
fallbackSession: readSeatState(o.home).session,
|
|
3201
|
+
host: o.host ?? hostname3(),
|
|
3202
|
+
claudePath: claude,
|
|
3203
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
3204
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
3205
|
+
exec: o.execCapture,
|
|
3206
|
+
log
|
|
3207
|
+
});
|
|
3208
|
+
if (out.kind === "skipped") {
|
|
3209
|
+
error(`Not run: ${out.reason}`);
|
|
3210
|
+
return 1;
|
|
3211
|
+
}
|
|
3212
|
+
log(out.summary);
|
|
3213
|
+
if (out.ok) {
|
|
3214
|
+
const a = await client.attestation(job.jobId);
|
|
3215
|
+
if (a)
|
|
3216
|
+
log(a.attestation.trailer);
|
|
3217
|
+
else
|
|
3218
|
+
log(`(attestation pending — sleds verify ${job.jobId})`);
|
|
3219
|
+
}
|
|
3220
|
+
return out.ok ? 0 : 1;
|
|
3221
|
+
};
|
|
3222
|
+
|
|
2483
3223
|
// src/commands/seatEvent.ts
|
|
3224
|
+
init_config();
|
|
3225
|
+
init_meter();
|
|
2484
3226
|
var HOOK_STDIN_TIMEOUT_MS = 1000;
|
|
2485
3227
|
var NOTIFICATION_TITLE = "sleds";
|
|
2486
3228
|
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -2533,15 +3275,15 @@ var seatEvent = async (o) => {
|
|
|
2533
3275
|
};
|
|
2534
3276
|
|
|
2535
3277
|
// src/commands/seatSync.ts
|
|
2536
|
-
import { execFileSync as
|
|
2537
|
-
import { existsSync as
|
|
3278
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3279
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
2538
3280
|
import { platform } from "node:os";
|
|
2539
|
-
import { join as
|
|
3281
|
+
import { join as join9 } from "node:path";
|
|
2540
3282
|
|
|
2541
3283
|
// src/cookieDb.ts
|
|
2542
|
-
import { copyFileSync, rmSync as
|
|
3284
|
+
import { copyFileSync, rmSync as rmSync4 } from "node:fs";
|
|
2543
3285
|
import { tmpdir } from "node:os";
|
|
2544
|
-
import { join as
|
|
3286
|
+
import { join as join8 } from "node:path";
|
|
2545
3287
|
var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
|
|
2546
3288
|
var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
2547
3289
|
async function queryRows(dbPath) {
|
|
@@ -2570,18 +3312,21 @@ function nodeSqlite() {
|
|
|
2570
3312
|
return mod;
|
|
2571
3313
|
}
|
|
2572
3314
|
async function readCookieRows(dbPath) {
|
|
2573
|
-
const copy =
|
|
3315
|
+
const copy = join8(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
|
|
2574
3316
|
copyFileSync(dbPath, copy);
|
|
2575
3317
|
try {
|
|
2576
3318
|
const rows = await queryRows(copy);
|
|
2577
3319
|
return rows.map((r) => ({ name: r.name, encrypted_value: Buffer.from(r.ev, "hex") }));
|
|
2578
3320
|
} finally {
|
|
2579
3321
|
try {
|
|
2580
|
-
|
|
3322
|
+
rmSync4(copy, { force: true });
|
|
2581
3323
|
} catch {}
|
|
2582
3324
|
}
|
|
2583
3325
|
}
|
|
2584
3326
|
|
|
3327
|
+
// src/commands/seatSync.ts
|
|
3328
|
+
init_meter();
|
|
3329
|
+
|
|
2585
3330
|
// src/seatSync.ts
|
|
2586
3331
|
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
2587
3332
|
var BROWSERS = {
|
|
@@ -2727,13 +3472,13 @@ function parseUsageResponse(raw, now = Date.now()) {
|
|
|
2727
3472
|
const resetsAt = parseResetsAt(v.resets_at);
|
|
2728
3473
|
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
2729
3474
|
continue;
|
|
2730
|
-
const
|
|
3475
|
+
const bar = { usedPct, resetsAt };
|
|
2731
3476
|
if (k === "five_hour")
|
|
2732
|
-
out.fiveHour =
|
|
3477
|
+
out.fiveHour = bar;
|
|
2733
3478
|
else if (k === "seven_day")
|
|
2734
|
-
out.sevenDay =
|
|
3479
|
+
out.sevenDay = bar;
|
|
2735
3480
|
else
|
|
2736
|
-
out.models[k] =
|
|
3481
|
+
out.models[k] = bar;
|
|
2737
3482
|
}
|
|
2738
3483
|
const hasAny = out.fiveHour || out.sevenDay || Object.keys(out.models).length > 0 || out.usedCreditsUsd !== undefined;
|
|
2739
3484
|
return hasAny ? out : null;
|
|
@@ -2843,7 +3588,7 @@ function findFile(root, name) {
|
|
|
2843
3588
|
const stack = [root];
|
|
2844
3589
|
while (stack.length > 0) {
|
|
2845
3590
|
const dir = stack.pop();
|
|
2846
|
-
if (!dir || !
|
|
3591
|
+
if (!dir || !existsSync10(dir))
|
|
2847
3592
|
continue;
|
|
2848
3593
|
let entries;
|
|
2849
3594
|
try {
|
|
@@ -2852,7 +3597,7 @@ function findFile(root, name) {
|
|
|
2852
3597
|
continue;
|
|
2853
3598
|
}
|
|
2854
3599
|
for (const e of entries) {
|
|
2855
|
-
const p =
|
|
3600
|
+
const p = join9(dir, e.name);
|
|
2856
3601
|
if (e.isDirectory())
|
|
2857
3602
|
stack.push(p);
|
|
2858
3603
|
else if (e.name === name)
|
|
@@ -2863,8 +3608,8 @@ function findFile(root, name) {
|
|
|
2863
3608
|
}
|
|
2864
3609
|
function findCookieDb(home, source) {
|
|
2865
3610
|
const b = BROWSERS[source.browser];
|
|
2866
|
-
const root =
|
|
2867
|
-
if (!
|
|
3611
|
+
const root = join9(home, APP_SUPPORT, b.appDir);
|
|
3612
|
+
if (!existsSync10(root))
|
|
2868
3613
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
2869
3614
|
if (!b.profiled) {
|
|
2870
3615
|
const db2 = findFile(root, "Cookies");
|
|
@@ -2872,15 +3617,15 @@ function findCookieDb(home, source) {
|
|
|
2872
3617
|
throw new Error(`no cookie store under ${root}`);
|
|
2873
3618
|
return db2;
|
|
2874
3619
|
}
|
|
2875
|
-
const localStatePath =
|
|
2876
|
-
const localState =
|
|
3620
|
+
const localStatePath = join9(root, "Local State");
|
|
3621
|
+
const localState = existsSync10(localStatePath) ? readFileSync8(localStatePath, "utf8") : null;
|
|
2877
3622
|
const wanted = source.profile ?? "Default";
|
|
2878
3623
|
const { dir, ambiguous } = resolveProfileDir(localState, wanted);
|
|
2879
3624
|
if (ambiguous)
|
|
2880
3625
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
2881
3626
|
if (!dir)
|
|
2882
3627
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
2883
|
-
const db = cookieDbCandidates(
|
|
3628
|
+
const db = cookieDbCandidates(join9(root, dir)).find((p) => existsSync10(p));
|
|
2884
3629
|
if (!db)
|
|
2885
3630
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
2886
3631
|
return db;
|
|
@@ -2890,7 +3635,7 @@ async function readCookieRowsBun(home, source) {
|
|
|
2890
3635
|
}
|
|
2891
3636
|
function keychainKey(service) {
|
|
2892
3637
|
try {
|
|
2893
|
-
return
|
|
3638
|
+
return execFileSync2("security", ["find-generic-password", "-w", "-s", service], {
|
|
2894
3639
|
encoding: "utf8"
|
|
2895
3640
|
}).trim();
|
|
2896
3641
|
} catch {
|
|
@@ -2902,7 +3647,7 @@ function decryptJar(rows, safeStoragePw) {
|
|
|
2902
3647
|
}
|
|
2903
3648
|
var curlUsage = (org, cookie) => {
|
|
2904
3649
|
try {
|
|
2905
|
-
const out =
|
|
3650
|
+
const out = execFileSync2("curl", usageCurlArgs(org, cookie), {
|
|
2906
3651
|
encoding: "utf8",
|
|
2907
3652
|
maxBuffer: 8000000
|
|
2908
3653
|
});
|
|
@@ -2911,8 +3656,8 @@ var curlUsage = (org, cookie) => {
|
|
|
2911
3656
|
return { status: 0, body: "" };
|
|
2912
3657
|
}
|
|
2913
3658
|
};
|
|
2914
|
-
function fetchSeatUsage(jar, org, now,
|
|
2915
|
-
const { status, body } =
|
|
3659
|
+
function fetchSeatUsage(jar, org, now, run2) {
|
|
3660
|
+
const { status, body } = run2(org, cookieHeader(jar, org));
|
|
2916
3661
|
if (status !== 200)
|
|
2917
3662
|
return { usage: null, status };
|
|
2918
3663
|
try {
|
|
@@ -2923,7 +3668,7 @@ function fetchSeatUsage(jar, org, now, run) {
|
|
|
2923
3668
|
}
|
|
2924
3669
|
function launchAgentPlist(bun, script, home, source) {
|
|
2925
3670
|
const label = launchLabel(source);
|
|
2926
|
-
const log =
|
|
3671
|
+
const log = join9(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
|
|
2927
3672
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2928
3673
|
const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
|
|
2929
3674
|
`);
|
|
@@ -2945,32 +3690,32 @@ ${args}
|
|
|
2945
3690
|
`;
|
|
2946
3691
|
}
|
|
2947
3692
|
function plistPath2(home, source) {
|
|
2948
|
-
return
|
|
3693
|
+
return join9(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
|
|
2949
3694
|
}
|
|
2950
|
-
function
|
|
3695
|
+
function install(home, argv, source, log) {
|
|
2951
3696
|
const bun = argv[0] ?? process.execPath;
|
|
2952
3697
|
const script = argv[1] ?? "";
|
|
2953
3698
|
if (!script) {
|
|
2954
3699
|
log("cannot resolve the sleds script path to install the timer");
|
|
2955
3700
|
return 1;
|
|
2956
3701
|
}
|
|
2957
|
-
const dir =
|
|
2958
|
-
|
|
3702
|
+
const dir = join9(home, "Library", "LaunchAgents");
|
|
3703
|
+
mkdirSync9(dir, { recursive: true });
|
|
2959
3704
|
const path = plistPath2(home, source);
|
|
2960
|
-
|
|
3705
|
+
writeFileSync9(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
2961
3706
|
try {
|
|
2962
|
-
|
|
3707
|
+
execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
|
|
2963
3708
|
} catch {}
|
|
2964
|
-
|
|
3709
|
+
execFileSync2("launchctl", ["load", "-w", path]);
|
|
2965
3710
|
log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
|
|
2966
3711
|
return 0;
|
|
2967
3712
|
}
|
|
2968
|
-
function
|
|
3713
|
+
function uninstall(home, source, log) {
|
|
2969
3714
|
const path = plistPath2(home, source);
|
|
2970
3715
|
try {
|
|
2971
|
-
|
|
3716
|
+
execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
2972
3717
|
} catch {}
|
|
2973
|
-
log(
|
|
3718
|
+
log(existsSync10(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
2974
3719
|
return 0;
|
|
2975
3720
|
}
|
|
2976
3721
|
var seatSync = async (o) => {
|
|
@@ -2983,9 +3728,9 @@ var seatSync = async (o) => {
|
|
|
2983
3728
|
}
|
|
2984
3729
|
const { source } = parsed;
|
|
2985
3730
|
if (source.uninstall)
|
|
2986
|
-
return
|
|
3731
|
+
return uninstall(o.home, source, log);
|
|
2987
3732
|
if (source.install)
|
|
2988
|
-
return
|
|
3733
|
+
return install(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
2989
3734
|
if (platform() !== "darwin" && !o.readCookieRows) {
|
|
2990
3735
|
log("seat-sync is macOS-only for now.");
|
|
2991
3736
|
return 2;
|
|
@@ -3065,8 +3810,9 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
|
3065
3810
|
}
|
|
3066
3811
|
|
|
3067
3812
|
// src/commands/specEvent.ts
|
|
3068
|
-
|
|
3069
|
-
import {
|
|
3813
|
+
init_meter();
|
|
3814
|
+
import { readFileSync as readFileSync9, statSync } from "node:fs";
|
|
3815
|
+
import { basename as basename2, isAbsolute, relative } from "node:path";
|
|
3070
3816
|
var HOOK_STDIN_TIMEOUT_MS2 = 1000;
|
|
3071
3817
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
3072
3818
|
var MIN_SPEC_CHARS = 200;
|
|
@@ -3079,12 +3825,12 @@ function isSpecPath(path) {
|
|
|
3079
3825
|
return false;
|
|
3080
3826
|
if (NOT_SPEC.test(path))
|
|
3081
3827
|
return false;
|
|
3082
|
-
return SPEC_DIRS.test(path) || SPEC_NAMES.test(
|
|
3828
|
+
return SPEC_DIRS.test(path) || SPEC_NAMES.test(basename2(path));
|
|
3083
3829
|
}
|
|
3084
3830
|
function specTitle(body, path) {
|
|
3085
3831
|
const m = /^#\s+(.+?)\s*$/m.exec(body);
|
|
3086
3832
|
const t = m?.[1]?.trim();
|
|
3087
|
-
return (t && t.length > 0 ? t :
|
|
3833
|
+
return (t && t.length > 0 ? t : basename2(path).replace(/\.(md|mdx)$/i, "")).slice(0, 120);
|
|
3088
3834
|
}
|
|
3089
3835
|
function looksLikeSpec(body) {
|
|
3090
3836
|
if (body.trim().length < MIN_SPEC_CHARS)
|
|
@@ -3121,7 +3867,7 @@ function readFileOrNull(path) {
|
|
|
3121
3867
|
try {
|
|
3122
3868
|
if (statSync(path).size > 2000000)
|
|
3123
3869
|
return null;
|
|
3124
|
-
return
|
|
3870
|
+
return readFileSync9(path, "utf8");
|
|
3125
3871
|
} catch {
|
|
3126
3872
|
return null;
|
|
3127
3873
|
}
|
|
@@ -3142,7 +3888,7 @@ var specEvent = async (o) => {
|
|
|
3142
3888
|
const spec = {
|
|
3143
3889
|
title: specTitle(body, input.filePath),
|
|
3144
3890
|
body: body.slice(0, MAX_SPEC_CHARS),
|
|
3145
|
-
repoHint: input.cwd ?
|
|
3891
|
+
repoHint: input.cwd ? basename2(input.cwd) : undefined
|
|
3146
3892
|
};
|
|
3147
3893
|
try {
|
|
3148
3894
|
const res = await (o.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
|
|
@@ -3164,7 +3910,9 @@ var specEvent = async (o) => {
|
|
|
3164
3910
|
};
|
|
3165
3911
|
|
|
3166
3912
|
// src/commands/status.ts
|
|
3167
|
-
|
|
3913
|
+
init_config();
|
|
3914
|
+
init_settings();
|
|
3915
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3168
3916
|
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
3169
3917
|
try {
|
|
3170
3918
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -3236,7 +3984,7 @@ var status = async (o) => {
|
|
|
3236
3984
|
const log = o.log ?? console.log;
|
|
3237
3985
|
const nowMs = (o.now ?? Date.now)();
|
|
3238
3986
|
const cfg = readConfig(o.home);
|
|
3239
|
-
const settings =
|
|
3987
|
+
const settings = existsSync11(settingsPath(o.home)) ? readSettings(o.home) : {};
|
|
3240
3988
|
const inSettings = readProxy(settings);
|
|
3241
3989
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
3242
3990
|
const token = inSettings?.token ?? cfg.token;
|
|
@@ -3290,15 +4038,19 @@ var status = async (o) => {
|
|
|
3290
4038
|
};
|
|
3291
4039
|
|
|
3292
4040
|
// src/commands/statusline.ts
|
|
4041
|
+
init_meter();
|
|
3293
4042
|
var statusline = async (o) => {
|
|
3294
4043
|
const log = o.log ?? console.log;
|
|
3295
4044
|
const now = (o.now ?? Date.now)();
|
|
3296
4045
|
const input = o.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
|
|
3297
4046
|
const window = seatWindowFromStdin(input, now);
|
|
4047
|
+
const session = seatSessionFromStdin(input, now);
|
|
3298
4048
|
let posts = [];
|
|
3299
4049
|
try {
|
|
3300
4050
|
if (window)
|
|
3301
|
-
posts = rememberSeat(o, window, now);
|
|
4051
|
+
posts = rememberSeat(o, window, session, now);
|
|
4052
|
+
else if (session)
|
|
4053
|
+
writeSeatState(o.home, { ...readSeatState(o.home), session });
|
|
3302
4054
|
} catch {}
|
|
3303
4055
|
const seatFragment = formatSeatFragment(window);
|
|
3304
4056
|
let line;
|
|
@@ -3317,9 +4069,9 @@ var statusline = async (o) => {
|
|
|
3317
4069
|
await Promise.allSettled(posts);
|
|
3318
4070
|
return 0;
|
|
3319
4071
|
};
|
|
3320
|
-
var rememberSeat = (o, window, now) => {
|
|
4072
|
+
var rememberSeat = (o, window, session, now) => {
|
|
3321
4073
|
const prior = readSeatState(o.home);
|
|
3322
|
-
let state = { ...prior, window };
|
|
4074
|
+
let state = { ...prior, window, ...session ? { session } : {} };
|
|
3323
4075
|
const src = meterSource(o.home);
|
|
3324
4076
|
const posts = [];
|
|
3325
4077
|
if (src) {
|
|
@@ -3342,11 +4094,18 @@ var rememberSeat = (o, window, now) => {
|
|
|
3342
4094
|
};
|
|
3343
4095
|
|
|
3344
4096
|
// src/commands/uninstall.ts
|
|
3345
|
-
|
|
3346
|
-
|
|
4097
|
+
init_config();
|
|
4098
|
+
init_meter();
|
|
4099
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4100
|
+
import { existsSync as existsSync12, rmSync as rmSync5 } from "node:fs";
|
|
4101
|
+
init_settings();
|
|
4102
|
+
var defaultUnload = (plist) => {
|
|
4103
|
+
spawnSync3("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
4104
|
+
};
|
|
4105
|
+
var uninstall2 = (o) => {
|
|
3347
4106
|
const log = o.log ?? console.log;
|
|
3348
4107
|
const removed = [];
|
|
3349
|
-
if (
|
|
4108
|
+
if (existsSync12(settingsPath(o.home))) {
|
|
3350
4109
|
let settings = readSettings(o.home);
|
|
3351
4110
|
let changed = false;
|
|
3352
4111
|
if (hasEitherKey(settings)) {
|
|
@@ -3378,16 +4137,22 @@ var uninstall3 = (o) => {
|
|
|
3378
4137
|
writeSettings(o.home, settings);
|
|
3379
4138
|
}
|
|
3380
4139
|
for (const file of [meterCachePath(o.home), seatStatePath(o.home)]) {
|
|
3381
|
-
if (
|
|
3382
|
-
|
|
4140
|
+
if (existsSync12(file)) {
|
|
4141
|
+
rmSync5(file, { force: true });
|
|
3383
4142
|
removed.push(file);
|
|
3384
4143
|
}
|
|
3385
4144
|
}
|
|
3386
4145
|
const skill = skillDir(o.home);
|
|
3387
|
-
if (
|
|
3388
|
-
|
|
4146
|
+
if (existsSync12(skill)) {
|
|
4147
|
+
rmSync5(skill, { recursive: true, force: true });
|
|
3389
4148
|
removed.push(skill);
|
|
3390
4149
|
}
|
|
4150
|
+
const plist = musherPlistPath(o.home);
|
|
4151
|
+
if (existsSync12(plist)) {
|
|
4152
|
+
(o.unloadAgent ?? defaultUnload)(plist);
|
|
4153
|
+
rmSync5(plist, { force: true });
|
|
4154
|
+
removed.push(`launchd agent ${musherLabel()}`);
|
|
4155
|
+
}
|
|
3391
4156
|
if (removed.length === 0) {
|
|
3392
4157
|
log("Nothing to remove; SLEDS was not installed.");
|
|
3393
4158
|
} else {
|
|
@@ -3405,15 +4170,78 @@ var hasEitherKey = (settings) => {
|
|
|
3405
4170
|
return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
|
|
3406
4171
|
};
|
|
3407
4172
|
|
|
4173
|
+
// src/commands/verify.ts
|
|
4174
|
+
init_meter();
|
|
4175
|
+
var jobIdFromTarget = (target) => {
|
|
4176
|
+
const t = target.trim();
|
|
4177
|
+
const m = /SLEDS-Attest:.*\bjob=([^\s]+)/.exec(t);
|
|
4178
|
+
if (m?.[1])
|
|
4179
|
+
return m[1];
|
|
4180
|
+
return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
|
|
4181
|
+
};
|
|
4182
|
+
var verify = async (o) => {
|
|
4183
|
+
const log = o.log ?? console.log;
|
|
4184
|
+
const error = o.error ?? console.error;
|
|
4185
|
+
const jobId = o.target ? jobIdFromTarget(o.target) : null;
|
|
4186
|
+
if (!jobId) {
|
|
4187
|
+
error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
|
|
4188
|
+
return 2;
|
|
4189
|
+
}
|
|
4190
|
+
const client = o.client ?? (() => {
|
|
4191
|
+
const src = meterSource(o.home);
|
|
4192
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
4193
|
+
})();
|
|
4194
|
+
if (!client) {
|
|
4195
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
4196
|
+
return 1;
|
|
4197
|
+
}
|
|
4198
|
+
const a = await client.attestation(jobId);
|
|
4199
|
+
if (!a) {
|
|
4200
|
+
error(`No attestation for ${jobId} (not delivered, not signed, not your org, or the proxy is down).`);
|
|
4201
|
+
return 1;
|
|
4202
|
+
}
|
|
4203
|
+
if (!a.publicKey) {
|
|
4204
|
+
error(`Attestation found but its key ${a.attestation.keyId} is not on file — cannot verify.`);
|
|
4205
|
+
return 1;
|
|
4206
|
+
}
|
|
4207
|
+
const ok = verifyAttestation(a.attestation.payload, a.attestation.sig, a.publicKey);
|
|
4208
|
+
const f = parsePayload(a.attestation.payload);
|
|
4209
|
+
log(`${ok ? "VERIFIED" : "INVALID "} job ${jobId}`);
|
|
4210
|
+
log(` model ${f.model ?? a.model ?? "unknown"}`);
|
|
4211
|
+
log(` engine ${f.engine ?? a.engine}${f.seat ? ` · seat ${f.seat}` : ""}`);
|
|
4212
|
+
log(` finished ${f.finished ?? "?"} · signed ${formatLocal(a.attestation.signedAt)} · key ${a.attestation.keyId}`);
|
|
4213
|
+
if (a.outputs.length)
|
|
4214
|
+
log(` outputs ${a.outputs.join(" ")}`);
|
|
4215
|
+
log(` ${a.attestation.trailer}`);
|
|
4216
|
+
if (!ok)
|
|
4217
|
+
log(" The signature does not match the payload under the org's public key.");
|
|
4218
|
+
return ok ? 0 : 1;
|
|
4219
|
+
};
|
|
4220
|
+
|
|
3408
4221
|
// src/schedule.ts
|
|
3409
|
-
import { spawnSync } from "node:child_process";
|
|
4222
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3410
4223
|
var defaultExec = (cmd, args) => {
|
|
3411
|
-
const r =
|
|
4224
|
+
const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
3412
4225
|
if (r.error)
|
|
3413
4226
|
return { ok: false, stderr: r.error.message };
|
|
3414
4227
|
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
3415
4228
|
};
|
|
3416
4229
|
|
|
4230
|
+
// src/settings.ts
|
|
4231
|
+
import { dirname as dirname4, join as join10, resolve as resolve4 } from "node:path";
|
|
4232
|
+
var withSeatEnv2 = (command, home, env = process.env) => {
|
|
4233
|
+
const parts = [];
|
|
4234
|
+
const sh = env.SLEDS_HOME?.trim();
|
|
4235
|
+
const cc = env.CLAUDE_CONFIG_DIR?.trim();
|
|
4236
|
+
if (sh)
|
|
4237
|
+
parts.push(`SLEDS_HOME=${shellWord2(resolve4(home, sh))}`);
|
|
4238
|
+
if (cc)
|
|
4239
|
+
parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve4(home, cc))}`);
|
|
4240
|
+
parts.push(command);
|
|
4241
|
+
return parts.join(" ");
|
|
4242
|
+
};
|
|
4243
|
+
var shellWord2 = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
4244
|
+
|
|
3417
4245
|
// src/index.ts
|
|
3418
4246
|
var USAGE = `usage: sleds <command> [options]
|
|
3419
4247
|
|
|
@@ -3434,14 +4262,21 @@ commands:
|
|
|
3434
4262
|
--until-reset <when> revert at this time: +5h, +90m, or an ISO timestamp
|
|
3435
4263
|
--reason "<text>" note shown by \`sleds status\`
|
|
3436
4264
|
--no-schedule record the deadline but do not install a launchd/systemd revert
|
|
4265
|
+
defer Hit a limit? Resume this session on this Mac at the reset \u2014 runs itself on your
|
|
4266
|
+
seat, no key needed (the musher). Uses the last session the status line saw.
|
|
4267
|
+
--at +2h | <ISO> start then instead of the binding reset
|
|
4268
|
+
--session <id> --cwd <dir> --model <id> override what was remembered
|
|
4269
|
+
musher The agent that runs deferred jobs here: --install / --uninstall / --status,
|
|
4270
|
+
--claude <path>, --mode <permission-mode> (default acceptEdits), --timeout <min>.
|
|
4271
|
+
run <jobId> Run one deferred job on this Mac now (the manual musher).
|
|
4272
|
+
verify <jobId | SLEDS-Attest line> Check a finished job's attestation: which model built it,
|
|
4273
|
+
signed by the org's key, verified locally.
|
|
3437
4274
|
off Switch Claude Code back to your seat. Restores whatever \`on\` replaced.
|
|
3438
4275
|
uninstall Remove the proxy settings, skill and status line. Keeps ~/.sleds-token.
|
|
3439
4276
|
status Show the switch state, deadline, proxy health and wall mode.
|
|
3440
4277
|
doctor Check the whole install: which seat this shell is, Claude settings, proxy, hooks,
|
|
3441
|
-
Node version, stale links, the
|
|
4278
|
+
Node version, stale links, the seat's last quota report.
|
|
3442
4279
|
meter Show the pipeline: built today, queued with its band, delivered overnight.
|
|
3443
|
-
bar Print the menu-bar meter in SwiftBar format; --install links it into SwiftBar
|
|
3444
|
-
(--home/--claude-config pick the seat it tracks; --name <x> adds a second item).
|
|
3445
4280
|
mcp Run the MCP server on stdio: tools usage, estimate, overnight, jobs.
|
|
3446
4281
|
Register with \`claude mcp add sleds -- sleds mcp\` (Cowork sees the same server).
|
|
3447
4282
|
seat-sync Read the seat's subscription usage (weekly/5h limits, per-model, credits) from a
|
|
@@ -3492,17 +4327,24 @@ var stringFlag = (flags, name) => {
|
|
|
3492
4327
|
const v = flags[name];
|
|
3493
4328
|
return typeof v === "string" ? v : undefined;
|
|
3494
4329
|
};
|
|
4330
|
+
var hookStdin = async () => {
|
|
4331
|
+
if (process.stdin.isTTY)
|
|
4332
|
+
return;
|
|
4333
|
+
const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
|
|
4334
|
+
const text = await readStdin3(300);
|
|
4335
|
+
return text.trim() ? text : undefined;
|
|
4336
|
+
};
|
|
3495
4337
|
var sledsArgv = () => {
|
|
3496
4338
|
const script = process.argv[1];
|
|
3497
4339
|
return script ? [process.execPath, script] : [process.execPath];
|
|
3498
4340
|
};
|
|
3499
|
-
var
|
|
3500
|
-
var statusLineCommand = (argv) => [...argv, "statusline"].map(
|
|
3501
|
-
var seatEventCommand = (argv) => [...argv, "seat-event"].map(
|
|
3502
|
-
var specEventCommand = (argv) => [...argv, "spec-event"].map(
|
|
4341
|
+
var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
4342
|
+
var statusLineCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
|
|
4343
|
+
var seatEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
|
|
4344
|
+
var specEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
|
|
3503
4345
|
var main = async (argv) => {
|
|
3504
4346
|
const { cmd, flags } = parseArgs(argv);
|
|
3505
|
-
const home =
|
|
4347
|
+
const home = homedir();
|
|
3506
4348
|
const scheduler = {
|
|
3507
4349
|
platform: platform2(),
|
|
3508
4350
|
home,
|
|
@@ -3522,6 +4364,19 @@ var main = async (argv) => {
|
|
|
3522
4364
|
noProbe: flags["no-probe"] === true,
|
|
3523
4365
|
proxyAlways: flags["proxy-always"] === true,
|
|
3524
4366
|
seatFirst: flags["seat-first"] === true,
|
|
4367
|
+
installMusher: () => {
|
|
4368
|
+
if (platform2() !== "darwin")
|
|
4369
|
+
return null;
|
|
4370
|
+
const lines = [];
|
|
4371
|
+
const code = musherInstall({
|
|
4372
|
+
home,
|
|
4373
|
+
argv: [],
|
|
4374
|
+
sledsArgv: sledsArgv(),
|
|
4375
|
+
log: (l) => lines.push(l),
|
|
4376
|
+
error: (l) => lines.push(l)
|
|
4377
|
+
});
|
|
4378
|
+
return code === 0 ? lines[0] ?? "installed the musher agent" : `musher not installed: ${lines.join(" ")}`;
|
|
4379
|
+
},
|
|
3525
4380
|
prompt: promptStdin,
|
|
3526
4381
|
statusLineCommand: statusLineCommand(sledsArgv()),
|
|
3527
4382
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
@@ -3548,16 +4403,22 @@ var main = async (argv) => {
|
|
|
3548
4403
|
});
|
|
3549
4404
|
case "off":
|
|
3550
4405
|
return off({ home, scheduler });
|
|
4406
|
+
case "defer":
|
|
4407
|
+
return defer({ home, argv: process.argv.slice(3), stdin: await hookStdin() });
|
|
4408
|
+
case "musher":
|
|
4409
|
+
return musher({ home, argv: process.argv.slice(3), sledsArgv: sledsArgv() });
|
|
4410
|
+
case "run":
|
|
4411
|
+
return run({ home, jobId: process.argv[3] });
|
|
4412
|
+
case "verify":
|
|
4413
|
+
return verify({ home, target: process.argv.slice(3).join(" ") || undefined });
|
|
3551
4414
|
case "uninstall":
|
|
3552
|
-
return
|
|
4415
|
+
return uninstall2({ home });
|
|
3553
4416
|
case "doctor":
|
|
3554
4417
|
return doctor({ home });
|
|
3555
4418
|
case "status":
|
|
3556
4419
|
return status({ home });
|
|
3557
4420
|
case "meter":
|
|
3558
4421
|
return meter({ home });
|
|
3559
|
-
case "bar":
|
|
3560
|
-
return bar({ home, argv: process.argv.slice(3) });
|
|
3561
4422
|
case "mcp":
|
|
3562
4423
|
return mcp({ home });
|
|
3563
4424
|
case "seat-sync":
|
|
@@ -3572,7 +4433,7 @@ var main = async (argv) => {
|
|
|
3572
4433
|
case "version":
|
|
3573
4434
|
case "--version":
|
|
3574
4435
|
case "-v":
|
|
3575
|
-
console.log(
|
|
4436
|
+
console.log("sleds 0.1.15");
|
|
3576
4437
|
return 0;
|
|
3577
4438
|
default:
|
|
3578
4439
|
console.error(`unknown command: ${cmd}
|