@sleds/cli 0.1.11 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/sleds.mjs +1355 -524
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -1,47 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __returnValue = (v) => v;
|
|
6
|
+
function __exportSetter(name, newValue) {
|
|
7
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
+
}
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
set: __exportSetter.bind(all, name)
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
20
|
|
|
6
|
-
// src/index.ts
|
|
7
|
-
import { realpathSync } from "fs";
|
|
8
|
-
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
|
|
11
|
-
// src/commands/bar.ts
|
|
12
|
-
import { execFileSync } from "node:child_process";
|
|
13
|
-
import {
|
|
14
|
-
chmodSync as chmodSync2,
|
|
15
|
-
existsSync as existsSync5,
|
|
16
|
-
lstatSync,
|
|
17
|
-
mkdirSync as mkdirSync5,
|
|
18
|
-
symlinkSync,
|
|
19
|
-
unlinkSync,
|
|
20
|
-
writeFileSync as writeFileSync5
|
|
21
|
-
} from "node:fs";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
-
import { join as join5, resolve as resolve3 } from "node:path";
|
|
24
|
-
|
|
25
|
-
// src/meter.ts
|
|
26
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27
|
-
import { join as join3 } from "node:path";
|
|
28
|
-
|
|
29
|
-
// src/config.ts
|
|
30
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
31
|
-
import { join as join2, resolve as resolve2 } from "node:path";
|
|
32
|
-
|
|
33
21
|
// src/settings.ts
|
|
34
22
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
35
23
|
import { dirname, join, resolve } from "node:path";
|
|
36
|
-
var BASE_URL_KEY = "ANTHROPIC_BASE_URL"
|
|
37
|
-
var AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN";
|
|
38
|
-
var claudeDir = (home, env = process.env) => {
|
|
24
|
+
var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN", claudeDir = (home, env = process.env) => {
|
|
39
25
|
const o = env.CLAUDE_CONFIG_DIR?.trim();
|
|
40
26
|
return o ? resolve(home, o) : join(home, ".claude");
|
|
41
|
-
}
|
|
42
|
-
var hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim());
|
|
43
|
-
var settingsPath = (home) => join(claudeDir(home), "settings.json");
|
|
44
|
-
var sledsCommand = (sub, home, env = process.env) => {
|
|
27
|
+
}, hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim()), settingsPath = (home) => join(claudeDir(home), "settings.json"), sledsCommand = (sub, home, env = process.env) => {
|
|
45
28
|
const parts = [];
|
|
46
29
|
const sh = env.SLEDS_HOME?.trim();
|
|
47
30
|
const cc = env.CLAUDE_CONFIG_DIR?.trim();
|
|
@@ -51,8 +34,7 @@ var sledsCommand = (sub, home, env = process.env) => {
|
|
|
51
34
|
parts.push(`CLAUDE_CONFIG_DIR=${shellWord(resolve(home, cc))}`);
|
|
52
35
|
parts.push(`sleds ${sub}`);
|
|
53
36
|
return parts.join(" ");
|
|
54
|
-
}
|
|
55
|
-
var sledsCommandsIn = (settings) => {
|
|
37
|
+
}, sledsCommandsIn = (settings) => {
|
|
56
38
|
const out = [];
|
|
57
39
|
const visit = (v) => {
|
|
58
40
|
if (Array.isArray(v)) {
|
|
@@ -70,74 +52,50 @@ var sledsCommandsIn = (settings) => {
|
|
|
70
52
|
visit(settings[STATUS_LINE_KEY]);
|
|
71
53
|
visit(settings.hooks);
|
|
72
54
|
return out;
|
|
73
|
-
}
|
|
74
|
-
var bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1];
|
|
75
|
-
var shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
76
|
-
var backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup");
|
|
77
|
-
var tokenPath = (home, env = process.env) => {
|
|
55
|
+
}, bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1], shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`, backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup"), tokenPath = (home, env = process.env) => {
|
|
78
56
|
const o = env.SLEDS_HOME?.trim();
|
|
79
57
|
return o ? join(resolve(home, o), ".sleds-token") : join(home, ".sleds-token");
|
|
80
|
-
}
|
|
81
|
-
var skillDir = (home) => join(claudeDir(home), "skills", "sled");
|
|
82
|
-
var skillPath = (home) => join(skillDir(home), "SKILL.md");
|
|
83
|
-
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
84
|
-
var envOf = (settings) => {
|
|
58
|
+
}, skillDir = (home) => join(claudeDir(home), "skills", "sled"), skillPath = (home) => join(skillDir(home), "SKILL.md"), isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v), envOf = (settings) => {
|
|
85
59
|
const env = settings.env;
|
|
86
60
|
return isObject(env) ? env : {};
|
|
87
|
-
}
|
|
88
|
-
var applyProxy = (settings, cfg) => ({
|
|
61
|
+
}, applyProxy = (settings, cfg) => ({
|
|
89
62
|
...settings,
|
|
90
63
|
env: { ...envOf(settings), [BASE_URL_KEY]: cfg.url, [AUTH_TOKEN_KEY]: cfg.token }
|
|
91
|
-
})
|
|
92
|
-
var removeProxy = (settings) => {
|
|
64
|
+
}), removeProxy = (settings) => {
|
|
93
65
|
if (!isObject(settings.env))
|
|
94
66
|
return { ...settings };
|
|
95
67
|
const { [BASE_URL_KEY]: _url, [AUTH_TOKEN_KEY]: _token, ...rest } = settings.env;
|
|
96
68
|
return { ...settings, env: rest };
|
|
97
|
-
}
|
|
98
|
-
var readProxy = (settings) => {
|
|
69
|
+
}, readProxy = (settings) => {
|
|
99
70
|
const env = envOf(settings);
|
|
100
71
|
const url = env[BASE_URL_KEY];
|
|
101
72
|
const token = env[AUTH_TOKEN_KEY];
|
|
102
73
|
if (typeof url !== "string" || typeof token !== "string")
|
|
103
74
|
return null;
|
|
104
75
|
return { url, token };
|
|
105
|
-
}
|
|
106
|
-
var STATUS_LINE_KEY = "statusLine";
|
|
107
|
-
var statusLineEntry = (command) => ({ type: "command", command });
|
|
108
|
-
var isSledsStatusLine = (value) => isObject(value) && value.type === "command" && typeof value.command === "string" && /\bstatusline\b/.test(value.command);
|
|
109
|
-
var applyStatusLine = (settings, command) => {
|
|
76
|
+
}, STATUS_LINE_KEY = "statusLine", statusLineEntry = (command) => ({ type: "command", command }), isSledsStatusLine = (value) => isObject(value) && value.type === "command" && typeof value.command === "string" && /\bstatusline\b/.test(value.command), applyStatusLine = (settings, command) => {
|
|
110
77
|
const current = settings[STATUS_LINE_KEY];
|
|
111
78
|
const prior = current === undefined || isSledsStatusLine(current) ? undefined : current;
|
|
112
79
|
return { settings: { ...settings, [STATUS_LINE_KEY]: statusLineEntry(command) }, prior };
|
|
113
|
-
}
|
|
114
|
-
var removeStatusLine = (settings, prior) => {
|
|
80
|
+
}, removeStatusLine = (settings, prior) => {
|
|
115
81
|
if (!isSledsStatusLine(settings[STATUS_LINE_KEY]))
|
|
116
82
|
return { ...settings };
|
|
117
83
|
const { [STATUS_LINE_KEY]: _ours, ...rest } = settings;
|
|
118
84
|
return prior === undefined ? rest : { ...rest, [STATUS_LINE_KEY]: prior };
|
|
119
|
-
}
|
|
120
|
-
var HOOKS_KEY = "hooks";
|
|
121
|
-
var NOTIFICATION_EVENT = "Notification";
|
|
122
|
-
var SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled";
|
|
123
|
-
var SEAT_EVENT_TIMEOUT_S = 10;
|
|
124
|
-
var isSledsSeatHook = (entry) => {
|
|
85
|
+
}, HOOKS_KEY = "hooks", NOTIFICATION_EVENT = "Notification", SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled", SEAT_EVENT_TIMEOUT_S = 10, isSledsSeatHook = (entry) => {
|
|
125
86
|
if (!isObject(entry) || !Array.isArray(entry.hooks))
|
|
126
87
|
return false;
|
|
127
88
|
return entry.hooks.some((h) => isObject(h) && h.type === "command" && typeof h.command === "string" && /\bseat-event\b/.test(h.command));
|
|
128
|
-
}
|
|
129
|
-
var seatHookEntry = (command) => ({
|
|
89
|
+
}, seatHookEntry = (command) => ({
|
|
130
90
|
matcher: SEAT_EVENT_MATCHER,
|
|
131
91
|
hooks: [{ type: "command", command, timeout: SEAT_EVENT_TIMEOUT_S }]
|
|
132
|
-
})
|
|
133
|
-
var notificationHooks = (settings) => {
|
|
92
|
+
}), notificationHooks = (settings) => {
|
|
134
93
|
const hooks = settings[HOOKS_KEY];
|
|
135
94
|
if (!isObject(hooks))
|
|
136
95
|
return [];
|
|
137
96
|
const list = hooks[NOTIFICATION_EVENT];
|
|
138
97
|
return Array.isArray(list) ? list : [];
|
|
139
|
-
}
|
|
140
|
-
var withNotificationHooks = (settings, list) => {
|
|
98
|
+
}, withNotificationHooks = (settings, list) => {
|
|
141
99
|
const hooks = isObject(settings[HOOKS_KEY]) ? settings[HOOKS_KEY] : {};
|
|
142
100
|
if (list.length === 0) {
|
|
143
101
|
const { [NOTIFICATION_EVENT]: _gone, ...restHooks } = hooks;
|
|
@@ -148,41 +106,31 @@ var withNotificationHooks = (settings, list) => {
|
|
|
148
106
|
return { ...settings, [HOOKS_KEY]: restHooks };
|
|
149
107
|
}
|
|
150
108
|
return { ...settings, [HOOKS_KEY]: { ...hooks, [NOTIFICATION_EVENT]: list } };
|
|
151
|
-
}
|
|
152
|
-
var applySeatHook = (settings, command) => {
|
|
109
|
+
}, applySeatHook = (settings, command) => {
|
|
153
110
|
const list = notificationHooks(settings);
|
|
154
111
|
const idx = list.findIndex(isSledsSeatHook);
|
|
155
112
|
const entry = seatHookEntry(command);
|
|
156
113
|
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
157
114
|
return withNotificationHooks(settings, next);
|
|
158
|
-
}
|
|
159
|
-
var removeSeatHook = (settings) => {
|
|
115
|
+
}, removeSeatHook = (settings) => {
|
|
160
116
|
const list = notificationHooks(settings);
|
|
161
117
|
if (!list.some(isSledsSeatHook))
|
|
162
118
|
return { ...settings };
|
|
163
119
|
return withNotificationHooks(settings, list.filter((e) => !isSledsSeatHook(e)));
|
|
164
|
-
}
|
|
165
|
-
var hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook);
|
|
166
|
-
var POST_TOOL_EVENT = "PostToolUse";
|
|
167
|
-
var SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit";
|
|
168
|
-
var SPEC_EVENT_TIMEOUT_S = 5;
|
|
169
|
-
var isSledsSpecHook = (entry) => {
|
|
120
|
+
}, hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook), POST_TOOL_EVENT = "PostToolUse", SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit", SPEC_EVENT_TIMEOUT_S = 5, isSledsSpecHook = (entry) => {
|
|
170
121
|
if (!isObject(entry) || !Array.isArray(entry.hooks))
|
|
171
122
|
return false;
|
|
172
123
|
return entry.hooks.some((h) => isObject(h) && h.type === "command" && typeof h.command === "string" && /\bspec-event\b/.test(h.command));
|
|
173
|
-
}
|
|
174
|
-
var specHookEntry = (command) => ({
|
|
124
|
+
}, specHookEntry = (command) => ({
|
|
175
125
|
matcher: SPEC_EVENT_MATCHER,
|
|
176
126
|
hooks: [{ type: "command", command, timeout: SPEC_EVENT_TIMEOUT_S }]
|
|
177
|
-
})
|
|
178
|
-
var eventHooks = (settings, event) => {
|
|
127
|
+
}), eventHooks = (settings, event) => {
|
|
179
128
|
const hooks = settings[HOOKS_KEY];
|
|
180
129
|
if (!isObject(hooks))
|
|
181
130
|
return [];
|
|
182
131
|
const list = hooks[event];
|
|
183
132
|
return Array.isArray(list) ? list : [];
|
|
184
|
-
}
|
|
185
|
-
var withEventHooks = (settings, event, list) => {
|
|
133
|
+
}, withEventHooks = (settings, event, list) => {
|
|
186
134
|
const hooks = isObject(settings[HOOKS_KEY]) ? settings[HOOKS_KEY] : {};
|
|
187
135
|
if (list.length === 0) {
|
|
188
136
|
const { [event]: _gone, ...restHooks } = hooks;
|
|
@@ -193,27 +141,19 @@ var withEventHooks = (settings, event, list) => {
|
|
|
193
141
|
return { ...settings, [HOOKS_KEY]: restHooks };
|
|
194
142
|
}
|
|
195
143
|
return { ...settings, [HOOKS_KEY]: { ...hooks, [event]: list } };
|
|
196
|
-
}
|
|
197
|
-
var applySpecHook = (settings, command) => {
|
|
144
|
+
}, applySpecHook = (settings, command) => {
|
|
198
145
|
const list = eventHooks(settings, POST_TOOL_EVENT);
|
|
199
146
|
const idx = list.findIndex(isSledsSpecHook);
|
|
200
147
|
const entry = specHookEntry(command);
|
|
201
148
|
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
202
149
|
return withEventHooks(settings, POST_TOOL_EVENT, next);
|
|
203
|
-
}
|
|
204
|
-
var removeSpecHook = (settings) => {
|
|
150
|
+
}, removeSpecHook = (settings) => {
|
|
205
151
|
const list = eventHooks(settings, POST_TOOL_EVENT);
|
|
206
152
|
if (!list.some(isSledsSpecHook))
|
|
207
153
|
return { ...settings };
|
|
208
154
|
return withEventHooks(settings, POST_TOOL_EVENT, list.filter((e) => !isSledsSpecHook(e)));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
var isSeatToken = (token) => /^sled_[A-Za-z0-9_-]+$/.test(token);
|
|
212
|
-
var maskToken = (token) => token.length <= 9 ? `${token.slice(0, 5)}…` : `${token.slice(0, 9)}…`;
|
|
213
|
-
var serialize = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
214
|
-
`;
|
|
215
|
-
var hasClaudeCode = (home) => existsSync(claudeDir(home));
|
|
216
|
-
var readSettings = (home) => {
|
|
155
|
+
}, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook), isSeatToken = (token) => /^sled_[A-Za-z0-9_-]+$/.test(token), maskToken = (token) => token.length <= 9 ? `${token.slice(0, 5)}…` : `${token.slice(0, 9)}…`, serialize = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
156
|
+
`, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
|
|
217
157
|
const p = settingsPath(home);
|
|
218
158
|
if (!existsSync(p))
|
|
219
159
|
return {};
|
|
@@ -221,44 +161,39 @@ var readSettings = (home) => {
|
|
|
221
161
|
if (!isObject(parsed))
|
|
222
162
|
throw new Error(`${p} is not a JSON object`);
|
|
223
163
|
return parsed;
|
|
224
|
-
}
|
|
225
|
-
var writeSettings = (home, settings) => {
|
|
164
|
+
}, writeSettings = (home, settings) => {
|
|
226
165
|
mkdirSync(claudeDir(home), { recursive: true });
|
|
227
166
|
const target = settingsPath(home);
|
|
228
167
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
229
168
|
writeFileSync(tmp, serialize(settings));
|
|
230
169
|
renameSync(tmp, target);
|
|
231
|
-
}
|
|
232
|
-
var backupSettings = (home) => {
|
|
170
|
+
}, backupSettings = (home) => {
|
|
233
171
|
const src = settingsPath(home);
|
|
234
172
|
const dst = backupPath(home);
|
|
235
173
|
if (!existsSync(src) || existsSync(dst))
|
|
236
174
|
return false;
|
|
237
175
|
writeFileSync(dst, readFileSync(src));
|
|
238
176
|
return true;
|
|
239
|
-
}
|
|
240
|
-
var readTokenFile = (home) => {
|
|
177
|
+
}, readTokenFile = (home) => {
|
|
241
178
|
const p = tokenPath(home);
|
|
242
179
|
if (!existsSync(p))
|
|
243
180
|
return null;
|
|
244
181
|
const t = readFileSync(p, "utf8").trim();
|
|
245
182
|
return t.length > 0 ? t : null;
|
|
246
|
-
}
|
|
247
|
-
var writeTokenFile = (home, token) => {
|
|
183
|
+
}, writeTokenFile = (home, token) => {
|
|
248
184
|
mkdirSync(dirname(tokenPath(home)), { recursive: true, mode: 448 });
|
|
249
185
|
writeFileSync(tokenPath(home), `${token}
|
|
250
186
|
`, { mode: 384 });
|
|
251
187
|
};
|
|
188
|
+
var init_settings = () => {};
|
|
252
189
|
|
|
253
190
|
// src/config.ts
|
|
191
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
192
|
+
import { join as join2, resolve as resolve2 } from "node:path";
|
|
254
193
|
var configDir = (home, env = process.env) => {
|
|
255
194
|
const o = env.SLEDS_HOME?.trim();
|
|
256
195
|
return o ? resolve2(home, o) : join2(home, ".config", "sleds");
|
|
257
|
-
}
|
|
258
|
-
var configPath = (home) => join2(configDir(home), "config.json");
|
|
259
|
-
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
260
|
-
var optString = (v) => typeof v === "string" ? v : undefined;
|
|
261
|
-
var parsePriorEnv = (v) => {
|
|
196
|
+
}, configPath = (home) => join2(configDir(home), "config.json"), isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), optString = (v) => typeof v === "string" ? v : undefined, parsePriorEnv = (v) => {
|
|
262
197
|
if (!isObject2(v))
|
|
263
198
|
return;
|
|
264
199
|
const out = {};
|
|
@@ -271,9 +206,7 @@ var parsePriorEnv = (v) => {
|
|
|
271
206
|
if (typeof v.hadEnv === "boolean")
|
|
272
207
|
out.hadEnv = v.hadEnv;
|
|
273
208
|
return out;
|
|
274
|
-
}
|
|
275
|
-
var parseSchedule = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined;
|
|
276
|
-
var parseConfig = (raw) => {
|
|
209
|
+
}, parseSchedule = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined, parseConfig = (raw) => {
|
|
277
210
|
if (!isObject2(raw))
|
|
278
211
|
return {};
|
|
279
212
|
const cfg = {};
|
|
@@ -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,548 @@ 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 join10 } from "node:path";
|
|
535
|
+
var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_MS2 = 1500, STDIN_TIMEOUT_MS2 = 300, isObject9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num5 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, EPOCH_MS_THRESHOLD2 = 100000000000, parseResetsAt2 = (v) => {
|
|
536
|
+
const n = num5(v);
|
|
537
|
+
if (n !== undefined) {
|
|
538
|
+
if (n <= 0)
|
|
539
|
+
return;
|
|
540
|
+
return n >= EPOCH_MS_THRESHOLD2 ? Math.round(n) : Math.round(n * 1000);
|
|
541
|
+
}
|
|
542
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
543
|
+
const parsed = Date.parse(v);
|
|
544
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}, parseBar2 = (v) => {
|
|
548
|
+
if (!isObject9(v))
|
|
549
|
+
return;
|
|
550
|
+
const usedPct = num5(v.used_percentage) ?? num5(v.usedPct);
|
|
551
|
+
const resetsAt = parseResetsAt2(v.resets_at ?? v.resetsAt);
|
|
552
|
+
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
553
|
+
return;
|
|
554
|
+
return { usedPct, resetsAt };
|
|
555
|
+
}, parseSeatWindow2 = (raw, capturedAt) => {
|
|
556
|
+
if (!isObject9(raw))
|
|
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) => join10(configDir(home), "seat.json"), parseWindowStored2 = (v) => {
|
|
608
|
+
if (!isObject9(v))
|
|
609
|
+
return;
|
|
610
|
+
const capturedAt = num5(v.capturedAt);
|
|
611
|
+
if (capturedAt === undefined)
|
|
612
|
+
return;
|
|
613
|
+
return parseSeatWindow2(v, capturedAt) ?? undefined;
|
|
614
|
+
}, parseSeatState2 = (raw) => {
|
|
615
|
+
if (!isObject9(raw))
|
|
616
|
+
return {};
|
|
617
|
+
const out = {};
|
|
618
|
+
const window = parseWindowStored2(raw.window);
|
|
619
|
+
if (window)
|
|
620
|
+
out.window = window;
|
|
621
|
+
const lastPostedAt = num5(raw.lastPostedAt);
|
|
622
|
+
if (lastPostedAt !== undefined)
|
|
623
|
+
out.lastPostedAt = lastPostedAt;
|
|
624
|
+
if (isObject9(raw.lastPosted)) {
|
|
625
|
+
const lp = {};
|
|
626
|
+
const f = num5(raw.lastPosted.fiveHour);
|
|
627
|
+
const s = num5(raw.lastPosted.sevenDay);
|
|
628
|
+
const sp = num5(raw.lastPosted.spendLimit);
|
|
629
|
+
if (f !== undefined)
|
|
630
|
+
lp.fiveHour = f;
|
|
631
|
+
if (s !== undefined)
|
|
632
|
+
lp.sevenDay = s;
|
|
633
|
+
if (sp !== undefined)
|
|
634
|
+
lp.spendLimit = sp;
|
|
635
|
+
out.lastPosted = lp;
|
|
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;
|
|
599
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;
|
|
655
|
+
return out;
|
|
656
|
+
}, seatSessionFromStdin2 = (text, now) => {
|
|
657
|
+
const trimmed = text.trim();
|
|
658
|
+
if (!trimmed)
|
|
659
|
+
return null;
|
|
660
|
+
try {
|
|
661
|
+
const raw = JSON.parse(trimmed);
|
|
662
|
+
if (!isObject9(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
663
|
+
return null;
|
|
664
|
+
const out = { id: raw.session_id, at: now };
|
|
665
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
666
|
+
out.cwd = raw.cwd;
|
|
667
|
+
const model = isObject9(raw.model) ? raw.model.id : raw.model;
|
|
668
|
+
if (typeof model === "string" && model)
|
|
669
|
+
out.model = model;
|
|
670
|
+
return out;
|
|
671
|
+
} catch {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}, bindingResetAt2 = (w, now) => {
|
|
675
|
+
if (!w)
|
|
676
|
+
return;
|
|
677
|
+
const bars = [
|
|
678
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
679
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
680
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
681
|
+
...Object.values(w.models ?? {})
|
|
682
|
+
].filter((b) => b.resetsAt > now);
|
|
683
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
684
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
685
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
686
|
+
}, readSeatState2 = (home) => {
|
|
687
|
+
const p = seatStatePath2(home);
|
|
688
|
+
if (!existsSync13(p))
|
|
689
|
+
return {};
|
|
690
|
+
try {
|
|
691
|
+
return parseSeatState2(JSON.parse(readFileSync10(p, "utf8")));
|
|
692
|
+
} catch {
|
|
693
|
+
return {};
|
|
694
|
+
}
|
|
695
|
+
}, writeSeatState2 = (home, state) => {
|
|
696
|
+
const dir = configDir(home);
|
|
697
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
698
|
+
const target = seatStatePath2(home);
|
|
699
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
700
|
+
writeFileSync10(tmp, `${JSON.stringify(state)}
|
|
701
|
+
`, { mode: 384 });
|
|
702
|
+
renameSync5(tmp, target);
|
|
703
|
+
}, pctOf2 = (b) => b?.usedPct, shouldPostSeat2 = (state, next, now) => {
|
|
704
|
+
if (state.lastPostedAt === undefined || !state.lastPosted)
|
|
705
|
+
return true;
|
|
706
|
+
if (now - state.lastPostedAt >= SEAT_POST_MIN_INTERVAL_MS2)
|
|
707
|
+
return true;
|
|
708
|
+
const pairs = [
|
|
709
|
+
[state.lastPosted.fiveHour, pctOf2(next.fiveHour)],
|
|
710
|
+
[state.lastPosted.sevenDay, pctOf2(next.sevenDay)],
|
|
711
|
+
[state.lastPosted.spendLimit, pctOf2(next.spendLimit)]
|
|
712
|
+
];
|
|
713
|
+
for (const [prev, cur] of pairs) {
|
|
714
|
+
if (prev === undefined && cur === undefined)
|
|
715
|
+
continue;
|
|
716
|
+
if (prev === undefined || cur === undefined)
|
|
717
|
+
return true;
|
|
718
|
+
if (Math.abs(cur - prev) >= SEAT_POST_MIN_DELTA_PCT2)
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
}, markPosted2 = (state, w, now) => {
|
|
723
|
+
const lastPosted = {};
|
|
724
|
+
if (w.fiveHour)
|
|
725
|
+
lastPosted.fiveHour = w.fiveHour.usedPct;
|
|
726
|
+
if (w.sevenDay)
|
|
727
|
+
lastPosted.sevenDay = w.sevenDay.usedPct;
|
|
728
|
+
if (w.spendLimit)
|
|
729
|
+
lastPosted.spendLimit = w.spendLimit.usedPct;
|
|
730
|
+
return { ...state, window: w, lastPostedAt: now, lastPosted };
|
|
731
|
+
}, seatWallDue2 = (state, w) => {
|
|
732
|
+
if (!w?.sevenDay || w.sevenDay.usedPct < 100)
|
|
733
|
+
return false;
|
|
734
|
+
return state.lastWallResetsAt === undefined || state.lastWallResetsAt < w.sevenDay.resetsAt;
|
|
735
|
+
}, seatWindowBody2 = (w) => ({
|
|
736
|
+
capturedAt: w.capturedAt,
|
|
737
|
+
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
738
|
+
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
739
|
+
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
740
|
+
}), postJson2 = async (o, path, body) => {
|
|
741
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: { authorization: `Bearer ${o.token}`, "content-type": "application/json" },
|
|
746
|
+
body: JSON.stringify(body),
|
|
747
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? SEAT_POST_TIMEOUT_MS2)
|
|
748
|
+
});
|
|
749
|
+
return res.ok;
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}, postSeatWindow2 = (o, w) => postJson2(o, "/proxy/seat", seatWindowBody2(w)), postSeatWall2 = (o, input) => postJson2(o, "/proxy/seat-wall", input), SEAT_WALL_LINE2 = "Seat limit hit. sleds on --until-reset to continue on the org key, or sleds defer to resume here at the reset.", SEAT_WALL_LINE_NO_KEY2 = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.", seatWallLine2 = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE2 : SEAT_WALL_LINE_NO_KEY2, readStdin2 = (timeoutMs, stdin = process.stdin) => new Promise((resolve4) => {
|
|
754
|
+
if (stdin.isTTY) {
|
|
755
|
+
resolve4("");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
let data = "";
|
|
759
|
+
let settled = false;
|
|
760
|
+
const finish = () => {
|
|
761
|
+
if (settled)
|
|
762
|
+
return;
|
|
763
|
+
settled = true;
|
|
764
|
+
clearTimeout(timer);
|
|
765
|
+
resolve4(data);
|
|
766
|
+
};
|
|
767
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
768
|
+
stdin.setEncoding("utf8");
|
|
769
|
+
stdin.on("data", (chunk) => {
|
|
770
|
+
data += chunk;
|
|
771
|
+
});
|
|
772
|
+
stdin.on("end", finish);
|
|
773
|
+
stdin.on("error", finish);
|
|
774
|
+
stdin.resume();
|
|
775
|
+
});
|
|
776
|
+
var init_seat = __esm(() => {
|
|
777
|
+
init_config();
|
|
778
|
+
init_meter();
|
|
779
|
+
SEAT_POST_MIN_INTERVAL_MS2 = 5 * 60000;
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// src/index.ts
|
|
783
|
+
import { realpathSync } from "fs";
|
|
784
|
+
import { homedir, platform as platform2 } from "os";
|
|
785
|
+
import { fileURLToPath } from "url";
|
|
786
|
+
|
|
787
|
+
// src/commands/defer.ts
|
|
788
|
+
init_config();
|
|
789
|
+
init_meter();
|
|
790
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
791
|
+
import { hostname } from "node:os";
|
|
792
|
+
|
|
793
|
+
// src/musher.ts
|
|
794
|
+
import { spawnSync } from "node:child_process";
|
|
795
|
+
import { createPublicKey, verify as cryptoVerify } from "node:crypto";
|
|
796
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
797
|
+
var MUSHER_LABEL = "ai.sleds.musher";
|
|
798
|
+
var MUSHER_INTERVAL_S = 300;
|
|
799
|
+
var DEFAULT_PERMISSION_MODE = "acceptEdits";
|
|
800
|
+
var DEFAULT_TIMEOUT_MINUTES = 90;
|
|
801
|
+
var SUMMARY_MAX = 2000;
|
|
802
|
+
var musherPlistPath = (home) => join4(home, "Library", "LaunchAgents", `${MUSHER_LABEL}.plist`);
|
|
803
|
+
var musherLogPath = (configDir2) => join4(configDir2, "musher.log");
|
|
804
|
+
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
805
|
+
var musherPath = (home, execPath, currentPath = "") => {
|
|
806
|
+
const parts = [
|
|
807
|
+
dirname2(execPath),
|
|
808
|
+
join4(home, ".sleds", "bin"),
|
|
809
|
+
join4(home, ".local", "bin"),
|
|
810
|
+
join4(home, ".bun", "bin"),
|
|
811
|
+
"/opt/homebrew/bin",
|
|
812
|
+
"/usr/local/bin",
|
|
813
|
+
"/usr/bin",
|
|
814
|
+
"/bin",
|
|
815
|
+
...currentPath.split(":").filter(Boolean)
|
|
816
|
+
];
|
|
817
|
+
return [...new Set(parts)].join(":");
|
|
600
818
|
};
|
|
601
|
-
var
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
819
|
+
var buildMusherPlist = (o) => {
|
|
820
|
+
const args = [...o.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
|
|
821
|
+
`);
|
|
822
|
+
const env = Object.entries(o.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
823
|
+
<string>${xmlEscape(v)}</string>`).join(`
|
|
824
|
+
`);
|
|
825
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
826
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
827
|
+
<plist version="1.0">
|
|
828
|
+
<dict>
|
|
829
|
+
<key>Label</key>
|
|
830
|
+
<string>${MUSHER_LABEL}</string>
|
|
831
|
+
<key>ProgramArguments</key>
|
|
832
|
+
<array>
|
|
833
|
+
${args}
|
|
834
|
+
</array>
|
|
835
|
+
<key>EnvironmentVariables</key>
|
|
836
|
+
<dict>
|
|
837
|
+
${env}
|
|
838
|
+
</dict>
|
|
839
|
+
<key>StartInterval</key>
|
|
840
|
+
<integer>${MUSHER_INTERVAL_S}</integer>
|
|
841
|
+
<key>RunAtLoad</key>
|
|
842
|
+
<true/>
|
|
843
|
+
<key>StandardOutPath</key>
|
|
844
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
845
|
+
<key>StandardErrorPath</key>
|
|
846
|
+
<string>${xmlEscape(o.logPath)}</string>
|
|
847
|
+
</dict>
|
|
848
|
+
</plist>
|
|
849
|
+
`;
|
|
850
|
+
};
|
|
851
|
+
var RESUME_PROMPT = [
|
|
852
|
+
"SLEDS musher: the usage limit that stopped this session has reset, and the seat's owner",
|
|
853
|
+
"scheduled this unattended run to continue it. Pick up exactly where the session left off:",
|
|
854
|
+
"finish the task that was in flight, run the checks you would normally run, follow the repo's",
|
|
855
|
+
"own conventions for branches and commits, and do not start unrelated work. If you are blocked",
|
|
856
|
+
"on a decision only the owner can make, stop there and say so. Finish with a summary under",
|
|
857
|
+
"200 words: what was done, what remains, anything the owner must decide."
|
|
858
|
+
].join(" ");
|
|
859
|
+
var NO_SESSION_PROMPT = [
|
|
860
|
+
"SLEDS musher: a deferred job started on this seat, but no Claude Code session was recorded",
|
|
861
|
+
"to resume. Do not guess at work. Reply with one line saying so, and stop."
|
|
862
|
+
].join(" ");
|
|
863
|
+
var claudeArgs = (o) => [
|
|
864
|
+
"-p",
|
|
865
|
+
o.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
|
|
866
|
+
...o.sessionId ? ["--resume", o.sessionId] : [],
|
|
867
|
+
...o.model ? ["--model", o.model] : [],
|
|
868
|
+
"--output-format",
|
|
869
|
+
"json",
|
|
870
|
+
"--permission-mode",
|
|
871
|
+
o.permissionMode
|
|
872
|
+
];
|
|
873
|
+
var parseClaudeOutput = (stdout, exitCode) => {
|
|
874
|
+
const lines = stdout.split(`
|
|
875
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
876
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
877
|
+
const line = lines[i] ?? "";
|
|
878
|
+
if (!line.startsWith("{"))
|
|
879
|
+
continue;
|
|
880
|
+
try {
|
|
881
|
+
const j = JSON.parse(line);
|
|
882
|
+
if (j.type !== "result" && typeof j.result !== "string")
|
|
883
|
+
continue;
|
|
884
|
+
const isError = j.is_error === true || typeof j.subtype === "string" && j.subtype !== "success";
|
|
885
|
+
const text = typeof j.result === "string" ? j.result : "";
|
|
886
|
+
const models = j.modelUsage;
|
|
887
|
+
const model = typeof j.model === "string" ? j.model : models && typeof models === "object" ? Object.keys(models)[0] : undefined;
|
|
888
|
+
return {
|
|
889
|
+
ok: !isError && exitCode === 0,
|
|
890
|
+
summary: (text || (isError ? "Claude Code reported an error" : "")).slice(0, SUMMARY_MAX),
|
|
891
|
+
...typeof j.session_id === "string" ? { sessionId: j.session_id } : {},
|
|
892
|
+
...model ? { model } : {},
|
|
893
|
+
...typeof j.duration_ms === "number" ? { durationMs: j.duration_ms } : {},
|
|
894
|
+
...typeof j.total_cost_usd === "number" ? { costUsd: j.total_cost_usd } : {}
|
|
895
|
+
};
|
|
896
|
+
} catch {}
|
|
897
|
+
}
|
|
898
|
+
const tail = lines.slice(-5).join(" ").slice(0, SUMMARY_MAX);
|
|
899
|
+
return { ok: false, summary: tail || `claude exited ${exitCode ?? "without a code"}` };
|
|
900
|
+
};
|
|
901
|
+
var defaultExecCapture = (cmd, args, o) => {
|
|
902
|
+
const r = spawnSync(cmd, [...args], {
|
|
903
|
+
cwd: o.cwd,
|
|
904
|
+
encoding: "utf8",
|
|
905
|
+
timeout: o.timeoutMs,
|
|
906
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
907
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
908
|
+
env: { ...process.env, SLEDS_MUSHER: "1" }
|
|
909
|
+
});
|
|
910
|
+
return {
|
|
911
|
+
stdout: r.stdout ?? "",
|
|
912
|
+
stderr: r.stderr ?? "",
|
|
913
|
+
status: r.status,
|
|
914
|
+
...r.error ? { error: r.error.message } : {}
|
|
915
|
+
};
|
|
916
|
+
};
|
|
917
|
+
var musherClient = (o) => {
|
|
918
|
+
const fetchFn = o.fetchFn ?? fetch;
|
|
919
|
+
const headers = { authorization: `Bearer ${o.token}`, "content-type": "application/json" };
|
|
920
|
+
const call = async (path, body) => {
|
|
921
|
+
try {
|
|
922
|
+
const res = await fetchFn(`${o.proxyUrl}${path}`, {
|
|
923
|
+
method: body === undefined ? "GET" : "POST",
|
|
924
|
+
headers,
|
|
925
|
+
...body === undefined ? {} : { body: JSON.stringify(body) },
|
|
926
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? 1e4)
|
|
927
|
+
});
|
|
928
|
+
if (!res.ok)
|
|
929
|
+
return null;
|
|
930
|
+
return await res.json();
|
|
931
|
+
} catch {
|
|
932
|
+
return null;
|
|
628
933
|
}
|
|
629
|
-
|
|
630
|
-
|
|
934
|
+
};
|
|
935
|
+
return {
|
|
936
|
+
list: () => call("/proxy/musher"),
|
|
937
|
+
start: (jobId, local) => call("/proxy/musher/start", { jobId, ...local }),
|
|
938
|
+
finish: async (input) => await call("/proxy/musher/finish", input) !== null,
|
|
939
|
+
defer: (input) => call("/proxy/defer", input),
|
|
940
|
+
attestation: (jobId) => call(`/proxy/attest/${encodeURIComponent(jobId)}`)
|
|
941
|
+
};
|
|
942
|
+
};
|
|
943
|
+
var gitHead = (cwd, exec) => {
|
|
944
|
+
if (!cwd)
|
|
945
|
+
return null;
|
|
946
|
+
const r = exec("git", ["rev-parse", "HEAD"], { cwd, timeoutMs: 5000 });
|
|
947
|
+
const sha = r.stdout.trim();
|
|
948
|
+
return r.status === 0 && /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
949
|
+
};
|
|
950
|
+
var gitNewCommits = (cwd, before, after, exec) => {
|
|
951
|
+
if (!cwd || !after || after === before)
|
|
952
|
+
return [];
|
|
953
|
+
if (!before)
|
|
954
|
+
return [after];
|
|
955
|
+
const r = exec("git", ["rev-list", `${before}..${after}`], { cwd, timeoutMs: 5000 });
|
|
956
|
+
if (r.status !== 0)
|
|
957
|
+
return [after];
|
|
958
|
+
return r.stdout.split(`
|
|
959
|
+
`).map((l) => l.trim()).filter((l) => /^[0-9a-f]{40}$/.test(l)).slice(0, 40);
|
|
960
|
+
};
|
|
961
|
+
var collectOutputs = (commits, summary) => {
|
|
962
|
+
const out = commits.map((c) => `git:${c}`);
|
|
963
|
+
for (const m of summary.matchAll(/https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+/g))
|
|
964
|
+
if (!out.includes(`pr:${m[0]}`))
|
|
965
|
+
out.push(`pr:${m[0]}`);
|
|
966
|
+
return out.slice(0, 50);
|
|
967
|
+
};
|
|
968
|
+
var runJob = async (o) => {
|
|
969
|
+
const log = o.log ?? (() => {});
|
|
970
|
+
const sessionId = o.job.claudeSessionId ?? o.fallbackSession?.id ?? null;
|
|
971
|
+
const cwd = o.job.cwd ?? o.fallbackSession?.cwd ?? undefined;
|
|
972
|
+
const started = await o.client.start(o.job.jobId, {
|
|
973
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
974
|
+
...cwd ? { cwd } : {},
|
|
975
|
+
host: o.host
|
|
976
|
+
});
|
|
977
|
+
if (!started)
|
|
978
|
+
return { kind: "skipped", reason: "control plane unreachable" };
|
|
979
|
+
if (!started.ok)
|
|
980
|
+
return { kind: "skipped", reason: started.reason };
|
|
981
|
+
const model = o.job.model ?? started.model ?? o.fallbackSession?.model ?? null;
|
|
982
|
+
const args = claudeArgs({ sessionId, model, permissionMode: o.permissionMode });
|
|
983
|
+
log(`musher: running job ${o.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
|
|
984
|
+
const exec = o.exec ?? defaultExecCapture;
|
|
985
|
+
const headBefore = gitHead(cwd, exec);
|
|
986
|
+
const t0 = Date.now();
|
|
987
|
+
const r = exec(o.claudePath, args, { cwd, timeoutMs: o.timeoutMs });
|
|
988
|
+
const durationMs = Date.now() - t0;
|
|
989
|
+
const result = r.error ? { ok: false, summary: `could not run ${o.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
|
|
990
|
+
if (!r.error && r.status === null)
|
|
991
|
+
result.summary = `timed out after ${Math.round(o.timeoutMs / 60000)} min. ${result.summary}`.trim();
|
|
992
|
+
const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
|
|
993
|
+
await o.client.finish({
|
|
994
|
+
jobId: o.job.jobId,
|
|
995
|
+
runId: started.runId,
|
|
996
|
+
ok: result.ok,
|
|
997
|
+
summary: result.summary,
|
|
998
|
+
...outputs.length ? { outputs } : {},
|
|
999
|
+
...result.model ?? model ? { model: result.model ?? model } : {},
|
|
1000
|
+
durationMs: result.durationMs ?? durationMs,
|
|
1001
|
+
...result.sessionId ?? sessionId ? { claudeSessionId: result.sessionId ?? sessionId } : {}
|
|
1002
|
+
});
|
|
1003
|
+
log(`musher: job ${o.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
|
|
1004
|
+
return { kind: "ran", ok: result.ok, summary: result.summary, durationMs, outputs };
|
|
1005
|
+
};
|
|
1006
|
+
var verifyAttestation = (payload, sig, publicKeyB64) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const pub = createPublicKey({
|
|
1009
|
+
key: Buffer.from(publicKeyB64, "base64"),
|
|
1010
|
+
format: "der",
|
|
1011
|
+
type: "spki"
|
|
1012
|
+
});
|
|
1013
|
+
const raw = Buffer.from(sig.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
1014
|
+
return cryptoVerify(null, Buffer.from(payload, "utf8"), pub, raw);
|
|
1015
|
+
} catch {
|
|
1016
|
+
return false;
|
|
631
1017
|
}
|
|
632
|
-
lines.push("---");
|
|
633
|
-
lines.push(`Open dashboard | href=${DASHBOARD_URL}`);
|
|
634
|
-
lines.push("Refresh | refresh=true");
|
|
635
|
-
return lines;
|
|
636
1018
|
};
|
|
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, `'\\''`)}'`;
|
|
1019
|
+
var parsePayload = (payload) => {
|
|
1020
|
+
const out = {};
|
|
1021
|
+
for (const line of payload.split(`
|
|
1022
|
+
`).slice(1)) {
|
|
1023
|
+
const eq = line.indexOf("=");
|
|
1024
|
+
if (eq > 0)
|
|
1025
|
+
out[line.slice(0, eq)] = line.slice(eq + 1);
|
|
1026
|
+
}
|
|
1027
|
+
return out;
|
|
1028
|
+
};
|
|
658
1029
|
|
|
659
1030
|
// src/seat.ts
|
|
1031
|
+
init_config();
|
|
1032
|
+
init_meter();
|
|
660
1033
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
661
|
-
import { join as
|
|
1034
|
+
import { join as join5 } from "node:path";
|
|
662
1035
|
var SEAT_POST_MIN_DELTA_PCT = 1;
|
|
663
1036
|
var SEAT_POST_MIN_INTERVAL_MS = 5 * 60000;
|
|
664
1037
|
var SEAT_POST_TIMEOUT_MS = 1500;
|
|
@@ -743,7 +1116,7 @@ var formatSeatFragment = (w) => {
|
|
|
743
1116
|
return null;
|
|
744
1117
|
return `seat ${parts.join(SEP)}`;
|
|
745
1118
|
};
|
|
746
|
-
var seatStatePath = (home) =>
|
|
1119
|
+
var seatStatePath = (home) => join5(configDir(home), "seat.json");
|
|
747
1120
|
var parseWindowStored = (v) => {
|
|
748
1121
|
if (!isObject4(v))
|
|
749
1122
|
return;
|
|
@@ -775,10 +1148,58 @@ var parseSeatState = (raw) => {
|
|
|
775
1148
|
lp.spendLimit = sp;
|
|
776
1149
|
out.lastPosted = lp;
|
|
777
1150
|
}
|
|
778
|
-
const lastWallResetsAt = num2(raw.lastWallResetsAt);
|
|
779
|
-
if (lastWallResetsAt !== undefined)
|
|
780
|
-
out.lastWallResetsAt = lastWallResetsAt;
|
|
781
|
-
|
|
1151
|
+
const lastWallResetsAt = num2(raw.lastWallResetsAt);
|
|
1152
|
+
if (lastWallResetsAt !== undefined)
|
|
1153
|
+
out.lastWallResetsAt = lastWallResetsAt;
|
|
1154
|
+
const session = parseSeatSession(raw.session);
|
|
1155
|
+
if (session)
|
|
1156
|
+
out.session = session;
|
|
1157
|
+
return out;
|
|
1158
|
+
};
|
|
1159
|
+
var parseSeatSession = (v) => {
|
|
1160
|
+
if (!isObject4(v) || typeof v.id !== "string" || !v.id)
|
|
1161
|
+
return;
|
|
1162
|
+
const at = num2(v.at);
|
|
1163
|
+
if (at === undefined)
|
|
1164
|
+
return;
|
|
1165
|
+
const out = { id: v.id, at };
|
|
1166
|
+
if (typeof v.cwd === "string" && v.cwd)
|
|
1167
|
+
out.cwd = v.cwd;
|
|
1168
|
+
if (typeof v.model === "string" && v.model)
|
|
1169
|
+
out.model = v.model;
|
|
1170
|
+
return out;
|
|
1171
|
+
};
|
|
1172
|
+
var seatSessionFromStdin = (text, now) => {
|
|
1173
|
+
const trimmed = text.trim();
|
|
1174
|
+
if (!trimmed)
|
|
1175
|
+
return null;
|
|
1176
|
+
try {
|
|
1177
|
+
const raw = JSON.parse(trimmed);
|
|
1178
|
+
if (!isObject4(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
1179
|
+
return null;
|
|
1180
|
+
const out = { id: raw.session_id, at: now };
|
|
1181
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
1182
|
+
out.cwd = raw.cwd;
|
|
1183
|
+
const model = isObject4(raw.model) ? raw.model.id : raw.model;
|
|
1184
|
+
if (typeof model === "string" && model)
|
|
1185
|
+
out.model = model;
|
|
1186
|
+
return out;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var bindingResetAt = (w, now) => {
|
|
1192
|
+
if (!w)
|
|
1193
|
+
return;
|
|
1194
|
+
const bars = [
|
|
1195
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
1196
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
1197
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
1198
|
+
...Object.values(w.models ?? {})
|
|
1199
|
+
].filter((b) => b.resetsAt > now);
|
|
1200
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
1201
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
1202
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
782
1203
|
};
|
|
783
1204
|
var readSeatState = (home) => {
|
|
784
1205
|
const p = seatStatePath(home);
|
|
@@ -857,8 +1278,8 @@ var postJson = async (o, path, body) => {
|
|
|
857
1278
|
};
|
|
858
1279
|
var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
|
|
859
1280
|
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.
|
|
1281
|
+
var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the org key, or sleds defer to resume here at the reset.";
|
|
1282
|
+
var SEAT_WALL_LINE_NO_KEY = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.";
|
|
862
1283
|
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
863
1284
|
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
|
|
864
1285
|
if (stdin.isTTY) {
|
|
@@ -884,140 +1305,8 @@ var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) =>
|
|
|
884
1305
|
stdin.resume();
|
|
885
1306
|
});
|
|
886
1307
|
|
|
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) => {
|
|
989
|
-
try {
|
|
990
|
-
return lstatSync(p).isSymbolicLink();
|
|
991
|
-
} catch {
|
|
992
|
-
return false;
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
// src/commands/doctor.ts
|
|
997
|
-
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
998
|
-
import { existsSync as existsSync6, lstatSync as lstatSync2, readFileSync as readFileSync5, readdirSync, readlinkSync } from "node:fs";
|
|
999
|
-
import { join as join7, resolve as resolve5 } from "node:path";
|
|
1000
|
-
|
|
1001
|
-
// src/commands/bar.ts
|
|
1002
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1003
|
-
import { homedir as homedir2 } from "node:os";
|
|
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;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
var pluginPath2 = (home, name) => join6(configDir(home, {}), "swiftbar", pluginName2(name));
|
|
1019
|
-
|
|
1020
1308
|
// src/switch.ts
|
|
1309
|
+
init_settings();
|
|
1021
1310
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1022
1311
|
var envOf2 = (settings) => isObject5(settings.env) ? settings.env : {};
|
|
1023
1312
|
var capturePriorEnv = (settings) => {
|
|
@@ -1092,7 +1381,141 @@ var formatDuration = (ms) => {
|
|
|
1092
1381
|
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
1093
1382
|
};
|
|
1094
1383
|
|
|
1384
|
+
// src/commands/defer.ts
|
|
1385
|
+
var parseAt = (v, now) => {
|
|
1386
|
+
const rel = /^\+(\d+)([hm])$/.exec(v.trim());
|
|
1387
|
+
if (rel) {
|
|
1388
|
+
const n = Number.parseInt(rel[1] ?? "0", 10);
|
|
1389
|
+
return now + n * (rel[2] === "h" ? 3600000 : 60000);
|
|
1390
|
+
}
|
|
1391
|
+
const t = Date.parse(v);
|
|
1392
|
+
return Number.isFinite(t) ? t : undefined;
|
|
1393
|
+
};
|
|
1394
|
+
var parseFlags = (argv) => {
|
|
1395
|
+
const flags = {};
|
|
1396
|
+
for (let i = 0;i < argv.length; i++) {
|
|
1397
|
+
const a = argv[i] ?? "";
|
|
1398
|
+
if (!a.startsWith("--"))
|
|
1399
|
+
continue;
|
|
1400
|
+
const eq = a.indexOf("=");
|
|
1401
|
+
if (eq > 0)
|
|
1402
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
1403
|
+
else {
|
|
1404
|
+
const next = argv[i + 1];
|
|
1405
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
1406
|
+
flags[a.slice(2)] = next;
|
|
1407
|
+
i++;
|
|
1408
|
+
} else
|
|
1409
|
+
flags[a.slice(2)] = "true";
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return flags;
|
|
1413
|
+
};
|
|
1414
|
+
var defer = async (o) => {
|
|
1415
|
+
const log = o.log ?? console.log;
|
|
1416
|
+
const error = o.error ?? console.error;
|
|
1417
|
+
const now = (o.now ?? Date.now)();
|
|
1418
|
+
const flags = parseFlags(o.argv);
|
|
1419
|
+
const client = o.client ?? (() => {
|
|
1420
|
+
const src = meterSource(o.home);
|
|
1421
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
1422
|
+
})();
|
|
1423
|
+
if (!client) {
|
|
1424
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
1425
|
+
return 1;
|
|
1426
|
+
}
|
|
1427
|
+
const state = readSeatState(o.home);
|
|
1428
|
+
const piped = o.stdin ? seatSessionFromStdin(o.stdin, now) : null;
|
|
1429
|
+
const remembered = piped ?? state.session;
|
|
1430
|
+
const sessionId = flags.session ?? remembered?.id;
|
|
1431
|
+
const cwd = flags.cwd ?? remembered?.cwd;
|
|
1432
|
+
const model = flags.model ?? remembered?.model;
|
|
1433
|
+
let resetAt;
|
|
1434
|
+
if (flags.at) {
|
|
1435
|
+
resetAt = parseAt(flags.at, now);
|
|
1436
|
+
if (resetAt === undefined) {
|
|
1437
|
+
error(`--at takes +2h, +90m or an ISO timestamp (got ${flags.at})`);
|
|
1438
|
+
return 1;
|
|
1439
|
+
}
|
|
1440
|
+
} else
|
|
1441
|
+
resetAt = bindingResetAt(state.window, now);
|
|
1442
|
+
if (!sessionId)
|
|
1443
|
+
log("No Claude Code session remembered on this seat yet; the job will say so when it starts. Pass --session <id> to pick one.");
|
|
1444
|
+
const r = await client.defer({
|
|
1445
|
+
...resetAt !== undefined ? { resetAt } : {},
|
|
1446
|
+
...model ? { model } : {},
|
|
1447
|
+
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
1448
|
+
...cwd ? { cwd } : {},
|
|
1449
|
+
host: o.host ?? hostname()
|
|
1450
|
+
});
|
|
1451
|
+
if (!r) {
|
|
1452
|
+
error("Could not reach the SLEDS proxy; nothing was scheduled.");
|
|
1453
|
+
return 1;
|
|
1454
|
+
}
|
|
1455
|
+
const when = r.scheduledAt <= now + 60000 ? "as soon as the musher ticks (within 5 min)" : `${formatLocal(r.scheduledAt)} (in ${formatDuration(r.scheduledAt - now)})`;
|
|
1456
|
+
log(`Deferred. This Mac resumes ${sessionId ? `session ${sessionId.slice(0, 8)}…` : "the work"}${model ? ` on ${model}` : ""} ${when}.`);
|
|
1457
|
+
log(` job ${r.jobId}${cwd ? ` · ${cwd}` : ""} · runs on your seat, no key · sleds musher --status to watch`);
|
|
1458
|
+
const cfg = readConfig(o.home);
|
|
1459
|
+
if (process.platform === "darwin" && !existsSync5(musherPlistPath(o.home)) && cfg.musher?.agent !== "launchd")
|
|
1460
|
+
log(` The musher agent is not installed on this Mac: run \`sleds musher --install\` once, or \`sleds run ${r.jobId}\` by hand at the reset.`);
|
|
1461
|
+
log(" Keep this Mac awake at that time (System Settings → Battery → prevent sleeping on power adapter).");
|
|
1462
|
+
return 0;
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// src/commands/doctor.ts
|
|
1466
|
+
init_config();
|
|
1467
|
+
init_meter();
|
|
1468
|
+
import { execFileSync } from "node:child_process";
|
|
1469
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync } from "node:fs";
|
|
1470
|
+
import { resolve as resolve3 } from "node:path";
|
|
1471
|
+
init_settings();
|
|
1472
|
+
|
|
1473
|
+
// src/commands/musher.ts
|
|
1474
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1475
|
+
init_config();
|
|
1476
|
+
init_meter();
|
|
1477
|
+
var defaultWhich = (cmd) => {
|
|
1478
|
+
const path = process.env.PATH ?? "";
|
|
1479
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
1480
|
+
const p = `${dir}/${cmd}`;
|
|
1481
|
+
if (existsSync6(p))
|
|
1482
|
+
return p;
|
|
1483
|
+
}
|
|
1484
|
+
return null;
|
|
1485
|
+
};
|
|
1486
|
+
var findClaude = (home, configured, which) => {
|
|
1487
|
+
if (configured && existsSync6(configured))
|
|
1488
|
+
return configured;
|
|
1489
|
+
const found = which("claude");
|
|
1490
|
+
if (found)
|
|
1491
|
+
return found;
|
|
1492
|
+
for (const p of [
|
|
1493
|
+
`${home}/.local/bin/claude`,
|
|
1494
|
+
"/opt/homebrew/bin/claude",
|
|
1495
|
+
"/usr/local/bin/claude",
|
|
1496
|
+
`${home}/.npm-global/bin/claude`
|
|
1497
|
+
])
|
|
1498
|
+
if (existsSync6(p))
|
|
1499
|
+
return p;
|
|
1500
|
+
return null;
|
|
1501
|
+
};
|
|
1502
|
+
var musherAgentState = (home, env = process.env) => {
|
|
1503
|
+
const plist = musherPlistPath(home);
|
|
1504
|
+
if (!existsSync6(plist))
|
|
1505
|
+
return { installed: false, thisSeat: false };
|
|
1506
|
+
try {
|
|
1507
|
+
const text = readFileSync5(plist, "utf8");
|
|
1508
|
+
const want = env.SLEDS_HOME?.trim();
|
|
1509
|
+
const has = /<key>SLEDS_HOME<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1];
|
|
1510
|
+
return { installed: true, thisSeat: (want ?? undefined) === (has ?? undefined) };
|
|
1511
|
+
} catch {
|
|
1512
|
+
return { installed: true, thisSeat: false };
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1095
1516
|
// src/commands/status.ts
|
|
1517
|
+
init_config();
|
|
1518
|
+
init_settings();
|
|
1096
1519
|
var checkHealth = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
1097
1520
|
try {
|
|
1098
1521
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -1110,16 +1533,6 @@ function nodeOk(version, min = NODE_MIN) {
|
|
|
1110
1533
|
const [x, y, z] = parse(min);
|
|
1111
1534
|
return (a ?? 0) !== (x ?? 0) ? (a ?? 0) > (x ?? 0) : (b ?? 0) !== (y ?? 0) ? (b ?? 0) > (y ?? 0) : (c ?? 0) >= (z ?? 0);
|
|
1112
1535
|
}
|
|
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
1536
|
var MARK = { ok: "ok ", warn: "warn", fail: "FAIL" };
|
|
1124
1537
|
function formatDoctor(checks) {
|
|
1125
1538
|
const lines = checks.map((c) => `${MARK[c.level]} ${c.name.padEnd(16)}${c.detail}`);
|
|
@@ -1135,9 +1548,9 @@ function formatDoctor(checks) {
|
|
|
1135
1548
|
lines.push(fails === 0 && warns === 0 ? "All good." : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`);
|
|
1136
1549
|
return lines;
|
|
1137
1550
|
}
|
|
1138
|
-
function
|
|
1551
|
+
function defaultWhich2(cmd) {
|
|
1139
1552
|
try {
|
|
1140
|
-
return
|
|
1553
|
+
return execFileSync("which", ["-a", cmd], {
|
|
1141
1554
|
encoding: "utf8",
|
|
1142
1555
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1143
1556
|
}).split(`
|
|
@@ -1146,16 +1559,6 @@ function defaultWhich(cmd) {
|
|
|
1146
1559
|
return [];
|
|
1147
1560
|
}
|
|
1148
1561
|
}
|
|
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
1562
|
async function runDoctor(o) {
|
|
1160
1563
|
const env = o.env ?? process.env;
|
|
1161
1564
|
const checks = [];
|
|
@@ -1166,7 +1569,7 @@ async function runDoctor(o) {
|
|
|
1166
1569
|
].filter(Boolean);
|
|
1167
1570
|
const cfgDir = configDir(home, env);
|
|
1168
1571
|
const cfg = readConfig(home);
|
|
1169
|
-
const cfgExists =
|
|
1572
|
+
const cfgExists = existsSync7(configPath(home));
|
|
1170
1573
|
if (!cfgExists) {
|
|
1171
1574
|
checks.push({
|
|
1172
1575
|
level: "fail",
|
|
@@ -1182,7 +1585,7 @@ async function runDoctor(o) {
|
|
|
1182
1585
|
});
|
|
1183
1586
|
}
|
|
1184
1587
|
const sPath = settingsPath(home);
|
|
1185
|
-
const settings =
|
|
1588
|
+
const settings = existsSync7(sPath) ? readSettings(home) : null;
|
|
1186
1589
|
const pointed = settings ? readProxy(settings) : null;
|
|
1187
1590
|
if (!settings) {
|
|
1188
1591
|
checks.push({
|
|
@@ -1226,7 +1629,7 @@ async function runDoctor(o) {
|
|
|
1226
1629
|
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
|
|
1227
1630
|
fix: seat2 && spec ? undefined : "sleds hooks"
|
|
1228
1631
|
});
|
|
1229
|
-
const want = env.SLEDS_HOME?.trim() ?
|
|
1632
|
+
const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
|
|
1230
1633
|
const cmds = sledsCommandsIn(settings);
|
|
1231
1634
|
const wrong = cmds.filter((c) => {
|
|
1232
1635
|
const got = bakedHome(c);
|
|
@@ -1240,7 +1643,7 @@ async function runDoctor(o) {
|
|
|
1240
1643
|
fix: wrong.length === 0 ? undefined : `${overrides.length ? `${overrides.join(" ")} ` : ""}sleds hooks (re-writes the status line + hooks with this seat baked in)`
|
|
1241
1644
|
});
|
|
1242
1645
|
}
|
|
1243
|
-
const skill =
|
|
1646
|
+
const skill = existsSync7(skillPath(home));
|
|
1244
1647
|
checks.push({
|
|
1245
1648
|
level: skill ? "ok" : "warn",
|
|
1246
1649
|
name: "skill",
|
|
@@ -1255,10 +1658,10 @@ async function runDoctor(o) {
|
|
|
1255
1658
|
detail: `${nodeVersion}${nodeOk(nodeVersion) ? "" : ` — seat-sync needs ≥ ${NODE_MIN} (node:sqlite)`}`,
|
|
1256
1659
|
fix: nodeOk(nodeVersion) ? undefined : `brew install node@22 && echo 'export PATH="/opt/homebrew/opt/node@22/bin:$PATH"' >> ~/.zshrc`
|
|
1257
1660
|
});
|
|
1258
|
-
const which = (o.which ??
|
|
1661
|
+
const which = (o.which ?? defaultWhich2)("sleds");
|
|
1259
1662
|
const stale = which.filter((p) => {
|
|
1260
1663
|
try {
|
|
1261
|
-
const target =
|
|
1664
|
+
const target = lstatSync(p).isSymbolicLink() ? readlinkSync(p) : p;
|
|
1262
1665
|
return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p);
|
|
1263
1666
|
} catch {
|
|
1264
1667
|
return false;
|
|
@@ -1273,49 +1676,64 @@ async function runDoctor(o) {
|
|
|
1273
1676
|
});
|
|
1274
1677
|
else if (which.length > 0)
|
|
1275
1678
|
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
1679
|
const seat = readSeatState(home);
|
|
1300
1680
|
const age = seat.window ? Math.round((Date.now() - seat.window.capturedAt) / 60000) : null;
|
|
1301
|
-
|
|
1302
|
-
|
|
1681
|
+
checks.push({
|
|
1682
|
+
level: age === null ? "warn" : "ok",
|
|
1683
|
+
name: "seat quota",
|
|
1684
|
+
detail: age === null ? "no report yet — run one Claude Code turn on this seat" : `last report ${age < 1 ? "just now" : `${age}m ago`} via the status line${seat.window?.sevenDay ? ` · wk ${Math.round(seat.window.sevenDay.usedPct)}%` : ""}`,
|
|
1685
|
+
fix: age === null ? "open claude, say hi, exit — the status line posts the seat's quota" : undefined
|
|
1686
|
+
});
|
|
1687
|
+
if ((o.platform ?? process.platform) === "darwin") {
|
|
1688
|
+
const agent = musherAgentState(home, env);
|
|
1689
|
+
checks.push(!agent.installed ? {
|
|
1303
1690
|
level: "warn",
|
|
1304
|
-
name: "
|
|
1305
|
-
detail: "
|
|
1306
|
-
fix: "sleds
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1691
|
+
name: "musher",
|
|
1692
|
+
detail: "agent not installed — `sleds defer` jobs would wait for `sleds run`",
|
|
1693
|
+
fix: "sleds musher --install"
|
|
1694
|
+
} : !agent.thisSeat ? {
|
|
1695
|
+
level: "warn",
|
|
1696
|
+
name: "musher",
|
|
1697
|
+
detail: "agent installed for a different seat (SLEDS_HOME differs)",
|
|
1698
|
+
fix: "sleds musher --install"
|
|
1699
|
+
} : { level: "ok", name: "musher", detail: `launchd agent ${MUSHER_LABEL}, every 5 min` });
|
|
1700
|
+
const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o.which ?? defaultWhich2)(c)[0] ?? null);
|
|
1701
|
+
checks.push(claude ? { level: "ok", name: "claude cli", detail: claude } : {
|
|
1702
|
+
level: "warn",
|
|
1703
|
+
name: "claude cli",
|
|
1704
|
+
detail: "`claude` not on PATH — the musher cannot run Claude Code",
|
|
1705
|
+
fix: 'sleds musher --claude "$(which claude)"'
|
|
1315
1706
|
});
|
|
1707
|
+
const sleep = (o.pmset ?? defaultPmset)();
|
|
1708
|
+
if (sleep !== null)
|
|
1709
|
+
checks.push(sleep.acSleepMinutes === 0 ? {
|
|
1710
|
+
level: "ok",
|
|
1711
|
+
name: "awake",
|
|
1712
|
+
detail: "does not sleep on power — deferred jobs can start"
|
|
1713
|
+
} : {
|
|
1714
|
+
level: "warn",
|
|
1715
|
+
name: "awake",
|
|
1716
|
+
detail: `sleeps after ${sleep.acSleepMinutes} min on power — a deferred job needs the Mac awake at the reset`,
|
|
1717
|
+
fix: "System Settings → Battery → Options → Prevent automatic sleeping on power adapter (or: sudo pmset -c sleep 0)"
|
|
1718
|
+
});
|
|
1316
1719
|
}
|
|
1317
1720
|
return checks;
|
|
1318
1721
|
}
|
|
1722
|
+
function parsePmset(text) {
|
|
1723
|
+
const ac = text.split(/AC Power:/i)[1];
|
|
1724
|
+
const block = ac ?? text;
|
|
1725
|
+
const m = /^\s*sleep\s+(\d+)/m.exec(block);
|
|
1726
|
+
if (!m)
|
|
1727
|
+
return null;
|
|
1728
|
+
return { acSleepMinutes: Number.parseInt(m[1] ?? "0", 10) };
|
|
1729
|
+
}
|
|
1730
|
+
var defaultPmset = () => {
|
|
1731
|
+
try {
|
|
1732
|
+
return parsePmset(execFileSync("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1319
1737
|
var doctor = async (o) => {
|
|
1320
1738
|
const log = o.log ?? console.log;
|
|
1321
1739
|
const checks = await runDoctor(o);
|
|
@@ -1325,6 +1743,7 @@ var doctor = async (o) => {
|
|
|
1325
1743
|
};
|
|
1326
1744
|
|
|
1327
1745
|
// src/commands/hooks.ts
|
|
1746
|
+
init_settings();
|
|
1328
1747
|
var hooks = (o) => {
|
|
1329
1748
|
const log = o.log ?? console.log;
|
|
1330
1749
|
const error = o.error ?? console.error;
|
|
@@ -1347,6 +1766,8 @@ var hooks = (o) => {
|
|
|
1347
1766
|
};
|
|
1348
1767
|
|
|
1349
1768
|
// src/commands/init.ts
|
|
1769
|
+
init_config();
|
|
1770
|
+
init_settings();
|
|
1350
1771
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1351
1772
|
import { createInterface } from "node:readline";
|
|
1352
1773
|
|
|
@@ -1383,6 +1804,8 @@ When the proxy is active it answers a walled request with its options. The user
|
|
|
1383
1804
|
/sled 6 defer to reset: the job runs itself when the seat resets — same model, done in minutes, full price (offered when the wall has a reset and the org has a key)
|
|
1384
1805
|
\`\`\`
|
|
1385
1806
|
|
|
1807
|
+
No key (Team plan, seat-first)? The musher still runs: \`sleds defer\` in a terminal schedules this Mac to resume the current session itself at the reset — \`claude --resume\` on the seat's own quota, the model pinned. \`sleds musher --status\` lists what is waiting; \`sleds run <jobId>\` runs one now; the Mac must be awake at the reset (\`sleds doctor\` checks). When the user says "defer this" or "pick it up at the reset" and the org has no key, tell them to run \`sleds defer\`.
|
|
1808
|
+
|
|
1386
1809
|
A hand-off carries a brief (the packet): what, note, cost so far, how to pick it up. The \`handoffs\` MCP tool and the dashboard's Jobs page show it; read it before taking one.
|
|
1387
1810
|
|
|
1388
1811
|
## Window reset
|
|
@@ -1397,9 +1820,9 @@ then restart \`claude\`. Settings go back to exactly what they were.
|
|
|
1397
1820
|
|
|
1398
1821
|
## Check
|
|
1399
1822
|
|
|
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\`)
|
|
1823
|
+
\`sleds status\` — on/off, since, reverts at, proxy health, wall mode, and the org's shape (\`Org:\` / \`Wiring:\`). An org with no platform key runs seat-first and its walls offer hold (\`/sled 2\`), \`sleds defer\` (this Mac resumes at the reset) and hand-off (\`/sled 5\`) — continue, overnight and switch need a key an admin adds on the dashboard.
|
|
1401
1824
|
|
|
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
|
|
1825
|
+
The status line's \`seat wk 52% · 5h 12%\` is the user's own Pro/Max window as Claude Code reports it (weekly, then 5-hour); \`window\` is the org key's headroom through the proxy. At \`wk 100%\` the answer is \`sleds on --until-reset\` (org key) or \`sleds defer\` (this Mac resumes at the reset, no key).
|
|
1403
1826
|
|
|
1404
1827
|
## /sled — estimate the task, offer overnight
|
|
1405
1828
|
|
|
@@ -1474,6 +1897,7 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
|
|
|
1474
1897
|
`;
|
|
1475
1898
|
|
|
1476
1899
|
// src/commands/hooks.ts
|
|
1900
|
+
init_settings();
|
|
1477
1901
|
var hooks2 = (o) => {
|
|
1478
1902
|
const log = o.log ?? console.log;
|
|
1479
1903
|
const error = o.error ?? console.error;
|
|
@@ -1497,11 +1921,11 @@ var hooks2 = (o) => {
|
|
|
1497
1921
|
|
|
1498
1922
|
// src/commands/init.ts
|
|
1499
1923
|
var DEFAULT_PROXY_URL = "http://localhost:8787";
|
|
1500
|
-
var promptStdin = (question) => new Promise((
|
|
1924
|
+
var promptStdin = (question) => new Promise((resolve4) => {
|
|
1501
1925
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1502
1926
|
rl.question(question, (answer) => {
|
|
1503
1927
|
rl.close();
|
|
1504
|
-
|
|
1928
|
+
resolve4(answer.trim());
|
|
1505
1929
|
});
|
|
1506
1930
|
});
|
|
1507
1931
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
|
|
@@ -1520,7 +1944,12 @@ async function signupRequest(proxyUrl, payload, fetchFn) {
|
|
|
1520
1944
|
});
|
|
1521
1945
|
const body = await res.json().catch(() => ({}));
|
|
1522
1946
|
if (res.ok && typeof body.token === "string")
|
|
1523
|
-
return {
|
|
1947
|
+
return {
|
|
1948
|
+
ok: true,
|
|
1949
|
+
token: body.token,
|
|
1950
|
+
created: body.created === true,
|
|
1951
|
+
verifyPending: body.verifyPending === true
|
|
1952
|
+
};
|
|
1524
1953
|
return {
|
|
1525
1954
|
ok: false,
|
|
1526
1955
|
reason: typeof body.reason === "string" ? body.reason : `http_${res.status}`,
|
|
@@ -1606,7 +2035,7 @@ var resolveToken = async (o) => {
|
|
|
1606
2035
|
if (typed)
|
|
1607
2036
|
return { token: typed, source: "prompt" };
|
|
1608
2037
|
}
|
|
1609
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2038
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1610
2039
|
if (!url)
|
|
1611
2040
|
return null;
|
|
1612
2041
|
const log = o.log ?? console.log;
|
|
@@ -1631,6 +2060,8 @@ var resolveToken = async (o) => {
|
|
|
1631
2060
|
return null;
|
|
1632
2061
|
}
|
|
1633
2062
|
log(`Created ${team} with you as owner (${email}).${credential2 ? " Your platform key is stored encrypted as the org's upstream key." : " No platform key yet — add one on the dashboard (Admin → Keys) to unlock continue / overnight / switch at a wall."}`);
|
|
2063
|
+
if (r2.verifyPending)
|
|
2064
|
+
log(`Check ${email} for a verification link and click it — inviting members unlocks then (an unverified team is removed after 48 h; your own seat works meanwhile).`);
|
|
1634
2065
|
log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
|
|
1635
2066
|
return { token: r2.token, source: "signup" };
|
|
1636
2067
|
}
|
|
@@ -1662,7 +2093,7 @@ var init = async (o) => {
|
|
|
1662
2093
|
error("Invalid seat token: expected something like sled_xxxxxxxx.");
|
|
1663
2094
|
return 2;
|
|
1664
2095
|
}
|
|
1665
|
-
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
|
|
2096
|
+
const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
1666
2097
|
if (!url) {
|
|
1667
2098
|
error(`Invalid proxy URL: ${JSON.stringify(o.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
|
|
1668
2099
|
return 2;
|
|
@@ -1712,6 +2143,11 @@ var init = async (o) => {
|
|
|
1712
2143
|
mkdirSync6(skillDir(o.home), { recursive: true });
|
|
1713
2144
|
writeFileSync6(skillPath(o.home), SKILL_MD);
|
|
1714
2145
|
wrote.push(`installed skill at ${skillPath(o.home)}`);
|
|
2146
|
+
if (o.installMusher) {
|
|
2147
|
+
const r = o.installMusher();
|
|
2148
|
+
if (r)
|
|
2149
|
+
wrote.push(r);
|
|
2150
|
+
}
|
|
1715
2151
|
const orgLine = org ? `${org.name} · ${org.plan}${org.capabilities.upstreamKey ? ` · ${org.capabilities.upstreamSource === "own" ? "org key" : "shared key"}` : " · no platform key"}` : "org unknown (proxy did not answer /proxy/meter)";
|
|
1716
2152
|
if (finalWiring === "proxy-always") {
|
|
1717
2153
|
log(`Claude Code now runs through SLEDS on the org key. (${orgLine})`);
|
|
@@ -1730,7 +2166,7 @@ var init = async (o) => {
|
|
|
1730
2166
|
log("");
|
|
1731
2167
|
log("Claude Code keeps running on your seat; the status line reports its quota to SLEDS on every turn.");
|
|
1732
2168
|
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.");
|
|
2169
|
+
log("This org has no platform key, so at a wall the choices are hold until reset, `sleds defer` (this Mac resumes the session itself at the reset), or hand off to a teammate. An admin can add a key on the dashboard to unlock continue / overnight / switch.");
|
|
1734
2170
|
} else {
|
|
1735
2171
|
log("At a wall: `sleds on --until-reset +5h` (or /sled 1) switches to the org key through the proxy until the reset; `sleds off` puts you back.");
|
|
1736
2172
|
}
|
|
@@ -1739,6 +2175,7 @@ var init = async (o) => {
|
|
|
1739
2175
|
};
|
|
1740
2176
|
|
|
1741
2177
|
// src/mcp.ts
|
|
2178
|
+
init_meter();
|
|
1742
2179
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
1743
2180
|
var SERVER_INFO = { name: "sleds", version: "0.0.1" };
|
|
1744
2181
|
var MCP_TIMEOUT_MS = 5000;
|
|
@@ -2272,6 +2709,8 @@ var mcp = async (o) => {
|
|
|
2272
2709
|
};
|
|
2273
2710
|
|
|
2274
2711
|
// src/commands/meter.ts
|
|
2712
|
+
init_config();
|
|
2713
|
+
init_meter();
|
|
2275
2714
|
var meter = async (o) => {
|
|
2276
2715
|
const log = o.log ?? console.log;
|
|
2277
2716
|
const now = (o.now ?? Date.now)();
|
|
@@ -2290,14 +2729,232 @@ var meter = async (o) => {
|
|
|
2290
2729
|
return 0;
|
|
2291
2730
|
};
|
|
2292
2731
|
|
|
2732
|
+
// src/commands/musher.ts
|
|
2733
|
+
init_config();
|
|
2734
|
+
init_meter();
|
|
2735
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2736
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2737
|
+
import { hostname as hostname2 } from "node:os";
|
|
2738
|
+
import { dirname as dirname3 } from "node:path";
|
|
2739
|
+
var defaultWhich3 = (cmd) => {
|
|
2740
|
+
const path = process.env.PATH ?? "";
|
|
2741
|
+
for (const dir of path.split(":").filter(Boolean)) {
|
|
2742
|
+
const p = `${dir}/${cmd}`;
|
|
2743
|
+
if (existsSync8(p))
|
|
2744
|
+
return p;
|
|
2745
|
+
}
|
|
2746
|
+
return null;
|
|
2747
|
+
};
|
|
2748
|
+
var findClaude2 = (home, configured, which) => {
|
|
2749
|
+
if (configured && existsSync8(configured))
|
|
2750
|
+
return configured;
|
|
2751
|
+
const found = which("claude");
|
|
2752
|
+
if (found)
|
|
2753
|
+
return found;
|
|
2754
|
+
for (const p of [
|
|
2755
|
+
`${home}/.local/bin/claude`,
|
|
2756
|
+
"/opt/homebrew/bin/claude",
|
|
2757
|
+
"/usr/local/bin/claude",
|
|
2758
|
+
`${home}/.npm-global/bin/claude`
|
|
2759
|
+
])
|
|
2760
|
+
if (existsSync8(p))
|
|
2761
|
+
return p;
|
|
2762
|
+
return null;
|
|
2763
|
+
};
|
|
2764
|
+
var musherInstall = (o) => {
|
|
2765
|
+
const log = o.log ?? console.log;
|
|
2766
|
+
const error = o.error ?? console.error;
|
|
2767
|
+
const platform = o.platform ?? process.platform;
|
|
2768
|
+
if (platform !== "darwin") {
|
|
2769
|
+
error(`sleds musher --install needs launchd (macOS). On ${platform}, run \`sleds musher tick\` from cron every 5 minutes.`);
|
|
2770
|
+
return 1;
|
|
2771
|
+
}
|
|
2772
|
+
const env = o.env ?? process.env;
|
|
2773
|
+
const plist = musherPlistPath(o.home);
|
|
2774
|
+
const dir = configDir(o.home, env);
|
|
2775
|
+
mkdirSync7(dirname3(plist), { recursive: true });
|
|
2776
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2777
|
+
const execPath = o.sledsArgv[0] ?? process.execPath;
|
|
2778
|
+
const agentEnv = {
|
|
2779
|
+
PATH: musherPath(o.home, execPath, env.PATH ?? ""),
|
|
2780
|
+
HOME: o.home,
|
|
2781
|
+
...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
|
|
2782
|
+
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
2783
|
+
};
|
|
2784
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2785
|
+
if (existsSync8(plist))
|
|
2786
|
+
exec("launchctl", ["unload", plist]);
|
|
2787
|
+
writeFileSync7(plist, buildMusherPlist({ sledsArgv: o.sledsArgv, env: agentEnv, logPath: musherLogPath(dir) }));
|
|
2788
|
+
const r = exec("launchctl", ["load", plist]);
|
|
2789
|
+
if (!r.ok) {
|
|
2790
|
+
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
2791
|
+
return 1;
|
|
2792
|
+
}
|
|
2793
|
+
const cfg = readConfig(o.home);
|
|
2794
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
|
|
2795
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2796
|
+
log(`musher installed: launchd agent ${MUSHER_LABEL}, every ${5} min · log ${musherLogPath(dir)}`);
|
|
2797
|
+
if (!claude)
|
|
2798
|
+
log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
|
|
2799
|
+
return 0;
|
|
2800
|
+
};
|
|
2801
|
+
var musherUninstall = (o) => {
|
|
2802
|
+
const log = o.log ?? console.log;
|
|
2803
|
+
const plist = musherPlistPath(o.home);
|
|
2804
|
+
const exec = o.exec ?? defaultExecSync;
|
|
2805
|
+
if (existsSync8(plist)) {
|
|
2806
|
+
exec("launchctl", ["unload", plist]);
|
|
2807
|
+
rmSync2(plist, { force: true });
|
|
2808
|
+
log(`musher removed: ${MUSHER_LABEL}`);
|
|
2809
|
+
} else
|
|
2810
|
+
log("musher was not installed.");
|
|
2811
|
+
const cfg = readConfig(o.home);
|
|
2812
|
+
if (cfg.musher)
|
|
2813
|
+
writeConfig(o.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
|
|
2814
|
+
return 0;
|
|
2815
|
+
};
|
|
2816
|
+
var formatMusherStatus = (list, now) => {
|
|
2817
|
+
const lines = [];
|
|
2818
|
+
for (const j of list.running)
|
|
2819
|
+
lines.push(`running ${j.jobId}${j.startedAt ? ` · since ${formatLocal(j.startedAt)} (${formatDuration(now - j.startedAt)})` : ""}`);
|
|
2820
|
+
for (const j of list.due)
|
|
2821
|
+
lines.push(`due now ${j.jobId}${j.model ? ` · ${j.model}` : ""} · was ${formatLocal(j.scheduledAt)}`);
|
|
2822
|
+
for (const j of list.upcoming)
|
|
2823
|
+
lines.push(`waiting ${j.jobId}${j.model ? ` · ${j.model}` : ""} · starts ${formatLocal(j.scheduledAt)} (in ${formatDuration(j.scheduledAt - now)})`);
|
|
2824
|
+
if (lines.length === 0)
|
|
2825
|
+
lines.push("nothing deferred on this seat.");
|
|
2826
|
+
return lines;
|
|
2827
|
+
};
|
|
2828
|
+
var clientFor = (o) => {
|
|
2829
|
+
if (o.client)
|
|
2830
|
+
return o.client;
|
|
2831
|
+
const src = meterSource(o.home);
|
|
2832
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
2833
|
+
};
|
|
2834
|
+
var musherStatus = async (o) => {
|
|
2835
|
+
const log = o.log ?? console.log;
|
|
2836
|
+
const now = (o.now ?? Date.now)();
|
|
2837
|
+
const plist = musherPlistPath(o.home);
|
|
2838
|
+
const cfg = readConfig(o.home);
|
|
2839
|
+
log(existsSync8(plist) ? `agent ${MUSHER_LABEL} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
2840
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
|
|
2841
|
+
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
2842
|
+
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2843
|
+
const client = clientFor(o);
|
|
2844
|
+
if (!client) {
|
|
2845
|
+
log("seat not initialised (sleds init)");
|
|
2846
|
+
return 1;
|
|
2847
|
+
}
|
|
2848
|
+
const list = await client.list();
|
|
2849
|
+
if (!list) {
|
|
2850
|
+
log("proxy unreachable");
|
|
2851
|
+
return 1;
|
|
2852
|
+
}
|
|
2853
|
+
for (const line of formatMusherStatus(list, now))
|
|
2854
|
+
log(line);
|
|
2855
|
+
return 0;
|
|
2856
|
+
};
|
|
2857
|
+
var musherTick = async (o) => {
|
|
2858
|
+
const log = o.log ?? console.log;
|
|
2859
|
+
const client = clientFor(o);
|
|
2860
|
+
if (!client) {
|
|
2861
|
+
log("musher: seat not initialised; nothing to do");
|
|
2862
|
+
return 1;
|
|
2863
|
+
}
|
|
2864
|
+
const list = await client.list();
|
|
2865
|
+
if (!list) {
|
|
2866
|
+
log("musher: proxy unreachable; will try again next tick");
|
|
2867
|
+
return 0;
|
|
2868
|
+
}
|
|
2869
|
+
if (list.due.length === 0)
|
|
2870
|
+
return 0;
|
|
2871
|
+
const cfg = readConfig(o.home);
|
|
2872
|
+
const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3) ?? "claude";
|
|
2873
|
+
const seat = readSeatState(o.home);
|
|
2874
|
+
for (const job of list.due) {
|
|
2875
|
+
const out = await runJob({
|
|
2876
|
+
job,
|
|
2877
|
+
client,
|
|
2878
|
+
fallbackSession: seat.session,
|
|
2879
|
+
host: o.host ?? hostname2(),
|
|
2880
|
+
claudePath: claude,
|
|
2881
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
2882
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
2883
|
+
exec: o.execCapture,
|
|
2884
|
+
log
|
|
2885
|
+
});
|
|
2886
|
+
if (out.kind === "skipped")
|
|
2887
|
+
log(`musher: job ${job.jobId} skipped — ${out.reason}`);
|
|
2888
|
+
}
|
|
2889
|
+
return 0;
|
|
2890
|
+
};
|
|
2891
|
+
var musherConfigure = (o, flags) => {
|
|
2892
|
+
const log = o.log ?? console.log;
|
|
2893
|
+
const cfg = readConfig(o.home);
|
|
2894
|
+
const musher = { ...cfg.musher ?? {} };
|
|
2895
|
+
if (flags.claude)
|
|
2896
|
+
musher.claudePath = flags.claude;
|
|
2897
|
+
if (flags.mode)
|
|
2898
|
+
musher.permissionMode = flags.mode;
|
|
2899
|
+
if (flags.timeout) {
|
|
2900
|
+
const n = Number.parseInt(flags.timeout, 10);
|
|
2901
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
2902
|
+
(o.error ?? console.error)("--timeout takes minutes, a positive number");
|
|
2903
|
+
return 1;
|
|
2904
|
+
}
|
|
2905
|
+
musher.timeoutMinutes = n;
|
|
2906
|
+
}
|
|
2907
|
+
writeConfig(o.home, { ...cfg, musher });
|
|
2908
|
+
log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
2909
|
+
return 0;
|
|
2910
|
+
};
|
|
2911
|
+
var defaultExecSync = (cmd, args) => {
|
|
2912
|
+
const r = spawnSync2(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
2913
|
+
if (r.error)
|
|
2914
|
+
return { ok: false, stderr: r.error.message };
|
|
2915
|
+
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
2916
|
+
};
|
|
2917
|
+
var musher = async (o) => {
|
|
2918
|
+
const args = [...o.argv];
|
|
2919
|
+
const flags = {};
|
|
2920
|
+
const positional = [];
|
|
2921
|
+
for (let i = 0;i < args.length; i++) {
|
|
2922
|
+
const a = args[i] ?? "";
|
|
2923
|
+
if (a.startsWith("--")) {
|
|
2924
|
+
const next = args[i + 1];
|
|
2925
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
2926
|
+
flags[a.slice(2)] = next;
|
|
2927
|
+
i++;
|
|
2928
|
+
} else
|
|
2929
|
+
flags[a.slice(2)] = "true";
|
|
2930
|
+
} else
|
|
2931
|
+
positional.push(a);
|
|
2932
|
+
}
|
|
2933
|
+
if (flags.install)
|
|
2934
|
+
return musherInstall(o);
|
|
2935
|
+
if (flags.uninstall)
|
|
2936
|
+
return musherUninstall(o);
|
|
2937
|
+
if (flags.claude || flags.mode || flags.timeout)
|
|
2938
|
+
return musherConfigure(o, flags);
|
|
2939
|
+
if (positional[0] === "tick")
|
|
2940
|
+
return musherTick(o);
|
|
2941
|
+
if (flags.status || positional.length === 0)
|
|
2942
|
+
return musherStatus(o);
|
|
2943
|
+
(o.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
|
|
2944
|
+
return 1;
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
// src/commands/off.ts
|
|
2948
|
+
init_config();
|
|
2949
|
+
|
|
2293
2950
|
// src/schedule.ts
|
|
2294
|
-
import { existsSync as
|
|
2295
|
-
import { join as
|
|
2951
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
2952
|
+
import { join as join7 } from "node:path";
|
|
2296
2953
|
var REVERT_LABEL = "ai.sleds.revert";
|
|
2297
|
-
var plistPath = (home) =>
|
|
2298
|
-
var
|
|
2954
|
+
var plistPath = (home) => join7(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
|
|
2955
|
+
var xmlEscape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2299
2956
|
var buildLaunchdPlist = (sledsArgv, deadline) => {
|
|
2300
|
-
const args = [...sledsArgv, "off"].map((a) => ` <string>${
|
|
2957
|
+
const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
|
|
2301
2958
|
`);
|
|
2302
2959
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2303
2960
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2333,9 +2990,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2333
2990
|
if (d.platform === "darwin") {
|
|
2334
2991
|
const p = plistPath(d.home);
|
|
2335
2992
|
try {
|
|
2336
|
-
|
|
2993
|
+
mkdirSync8(join7(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
2337
2994
|
d.exec("launchctl", ["unload", p]);
|
|
2338
|
-
|
|
2995
|
+
writeFileSync8(p, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
2339
2996
|
const r = d.exec("launchctl", ["load", p]);
|
|
2340
2997
|
if (r.ok)
|
|
2341
2998
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
@@ -2362,9 +3019,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
2362
3019
|
var cancelRevert = (d, kind) => {
|
|
2363
3020
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
2364
3021
|
const p = plistPath(d.home);
|
|
2365
|
-
if (
|
|
3022
|
+
if (existsSync9(p)) {
|
|
2366
3023
|
d.exec("launchctl", ["unload", p]);
|
|
2367
|
-
|
|
3024
|
+
rmSync3(p, { force: true });
|
|
2368
3025
|
}
|
|
2369
3026
|
}
|
|
2370
3027
|
if (kind === "systemd" || d.platform === "linux") {
|
|
@@ -2373,6 +3030,7 @@ var cancelRevert = (d, kind) => {
|
|
|
2373
3030
|
};
|
|
2374
3031
|
|
|
2375
3032
|
// src/commands/off.ts
|
|
3033
|
+
init_settings();
|
|
2376
3034
|
var off = (o) => {
|
|
2377
3035
|
const log = o.log ?? console.log;
|
|
2378
3036
|
const cfg = readConfig(o.home);
|
|
@@ -2400,7 +3058,13 @@ var off = (o) => {
|
|
|
2400
3058
|
return 0;
|
|
2401
3059
|
};
|
|
2402
3060
|
|
|
3061
|
+
// src/commands/on.ts
|
|
3062
|
+
init_config();
|
|
3063
|
+
init_settings();
|
|
3064
|
+
|
|
2403
3065
|
// src/commands/init.ts
|
|
3066
|
+
init_config();
|
|
3067
|
+
init_settings();
|
|
2404
3068
|
var DEFAULT_PROXY_URL2 = "http://localhost:8787";
|
|
2405
3069
|
|
|
2406
3070
|
// src/commands/on.ts
|
|
@@ -2480,7 +3144,70 @@ var on = (o) => {
|
|
|
2480
3144
|
return 0;
|
|
2481
3145
|
};
|
|
2482
3146
|
|
|
3147
|
+
// src/commands/run.ts
|
|
3148
|
+
init_config();
|
|
3149
|
+
init_meter();
|
|
3150
|
+
import { hostname as hostname3 } from "node:os";
|
|
3151
|
+
var run = async (o) => {
|
|
3152
|
+
const log = o.log ?? console.log;
|
|
3153
|
+
const error = o.error ?? console.error;
|
|
3154
|
+
if (!o.jobId) {
|
|
3155
|
+
error("usage: sleds run <jobId>");
|
|
3156
|
+
return 2;
|
|
3157
|
+
}
|
|
3158
|
+
const client = o.client ?? (() => {
|
|
3159
|
+
const src = meterSource(o.home);
|
|
3160
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
3161
|
+
})();
|
|
3162
|
+
if (!client) {
|
|
3163
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
3164
|
+
return 1;
|
|
3165
|
+
}
|
|
3166
|
+
const list = await client.list();
|
|
3167
|
+
if (!list) {
|
|
3168
|
+
error("Could not reach the SLEDS proxy.");
|
|
3169
|
+
return 1;
|
|
3170
|
+
}
|
|
3171
|
+
const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o.jobId);
|
|
3172
|
+
if (!job) {
|
|
3173
|
+
error(`Job ${o.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
|
|
3174
|
+
return 1;
|
|
3175
|
+
}
|
|
3176
|
+
const cfg = readConfig(o.home);
|
|
3177
|
+
const claude = findClaude(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich);
|
|
3178
|
+
if (!claude) {
|
|
3179
|
+
error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
|
|
3180
|
+
return 1;
|
|
3181
|
+
}
|
|
3182
|
+
const out = await runJob({
|
|
3183
|
+
job,
|
|
3184
|
+
client,
|
|
3185
|
+
fallbackSession: readSeatState(o.home).session,
|
|
3186
|
+
host: o.host ?? hostname3(),
|
|
3187
|
+
claudePath: claude,
|
|
3188
|
+
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
3189
|
+
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
3190
|
+
exec: o.execCapture,
|
|
3191
|
+
log
|
|
3192
|
+
});
|
|
3193
|
+
if (out.kind === "skipped") {
|
|
3194
|
+
error(`Not run: ${out.reason}`);
|
|
3195
|
+
return 1;
|
|
3196
|
+
}
|
|
3197
|
+
log(out.summary);
|
|
3198
|
+
if (out.ok) {
|
|
3199
|
+
const a = await client.attestation(job.jobId);
|
|
3200
|
+
if (a)
|
|
3201
|
+
log(a.attestation.trailer);
|
|
3202
|
+
else
|
|
3203
|
+
log(`(attestation pending — sleds verify ${job.jobId})`);
|
|
3204
|
+
}
|
|
3205
|
+
return out.ok ? 0 : 1;
|
|
3206
|
+
};
|
|
3207
|
+
|
|
2483
3208
|
// src/commands/seatEvent.ts
|
|
3209
|
+
init_config();
|
|
3210
|
+
init_meter();
|
|
2484
3211
|
var HOOK_STDIN_TIMEOUT_MS = 1000;
|
|
2485
3212
|
var NOTIFICATION_TITLE = "sleds";
|
|
2486
3213
|
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -2533,15 +3260,15 @@ var seatEvent = async (o) => {
|
|
|
2533
3260
|
};
|
|
2534
3261
|
|
|
2535
3262
|
// src/commands/seatSync.ts
|
|
2536
|
-
import { execFileSync as
|
|
2537
|
-
import { existsSync as
|
|
3263
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3264
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
2538
3265
|
import { platform } from "node:os";
|
|
2539
|
-
import { join as
|
|
3266
|
+
import { join as join9 } from "node:path";
|
|
2540
3267
|
|
|
2541
3268
|
// src/cookieDb.ts
|
|
2542
|
-
import { copyFileSync, rmSync as
|
|
3269
|
+
import { copyFileSync, rmSync as rmSync4 } from "node:fs";
|
|
2543
3270
|
import { tmpdir } from "node:os";
|
|
2544
|
-
import { join as
|
|
3271
|
+
import { join as join8 } from "node:path";
|
|
2545
3272
|
var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
|
|
2546
3273
|
var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
2547
3274
|
async function queryRows(dbPath) {
|
|
@@ -2570,18 +3297,21 @@ function nodeSqlite() {
|
|
|
2570
3297
|
return mod;
|
|
2571
3298
|
}
|
|
2572
3299
|
async function readCookieRows(dbPath) {
|
|
2573
|
-
const copy =
|
|
3300
|
+
const copy = join8(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
|
|
2574
3301
|
copyFileSync(dbPath, copy);
|
|
2575
3302
|
try {
|
|
2576
3303
|
const rows = await queryRows(copy);
|
|
2577
3304
|
return rows.map((r) => ({ name: r.name, encrypted_value: Buffer.from(r.ev, "hex") }));
|
|
2578
3305
|
} finally {
|
|
2579
3306
|
try {
|
|
2580
|
-
|
|
3307
|
+
rmSync4(copy, { force: true });
|
|
2581
3308
|
} catch {}
|
|
2582
3309
|
}
|
|
2583
3310
|
}
|
|
2584
3311
|
|
|
3312
|
+
// src/commands/seatSync.ts
|
|
3313
|
+
init_meter();
|
|
3314
|
+
|
|
2585
3315
|
// src/seatSync.ts
|
|
2586
3316
|
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
2587
3317
|
var BROWSERS = {
|
|
@@ -2727,13 +3457,13 @@ function parseUsageResponse(raw, now = Date.now()) {
|
|
|
2727
3457
|
const resetsAt = parseResetsAt(v.resets_at);
|
|
2728
3458
|
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
2729
3459
|
continue;
|
|
2730
|
-
const
|
|
3460
|
+
const bar = { usedPct, resetsAt };
|
|
2731
3461
|
if (k === "five_hour")
|
|
2732
|
-
out.fiveHour =
|
|
3462
|
+
out.fiveHour = bar;
|
|
2733
3463
|
else if (k === "seven_day")
|
|
2734
|
-
out.sevenDay =
|
|
3464
|
+
out.sevenDay = bar;
|
|
2735
3465
|
else
|
|
2736
|
-
out.models[k] =
|
|
3466
|
+
out.models[k] = bar;
|
|
2737
3467
|
}
|
|
2738
3468
|
const hasAny = out.fiveHour || out.sevenDay || Object.keys(out.models).length > 0 || out.usedCreditsUsd !== undefined;
|
|
2739
3469
|
return hasAny ? out : null;
|
|
@@ -2843,7 +3573,7 @@ function findFile(root, name) {
|
|
|
2843
3573
|
const stack = [root];
|
|
2844
3574
|
while (stack.length > 0) {
|
|
2845
3575
|
const dir = stack.pop();
|
|
2846
|
-
if (!dir || !
|
|
3576
|
+
if (!dir || !existsSync10(dir))
|
|
2847
3577
|
continue;
|
|
2848
3578
|
let entries;
|
|
2849
3579
|
try {
|
|
@@ -2852,7 +3582,7 @@ function findFile(root, name) {
|
|
|
2852
3582
|
continue;
|
|
2853
3583
|
}
|
|
2854
3584
|
for (const e of entries) {
|
|
2855
|
-
const p =
|
|
3585
|
+
const p = join9(dir, e.name);
|
|
2856
3586
|
if (e.isDirectory())
|
|
2857
3587
|
stack.push(p);
|
|
2858
3588
|
else if (e.name === name)
|
|
@@ -2863,8 +3593,8 @@ function findFile(root, name) {
|
|
|
2863
3593
|
}
|
|
2864
3594
|
function findCookieDb(home, source) {
|
|
2865
3595
|
const b = BROWSERS[source.browser];
|
|
2866
|
-
const root =
|
|
2867
|
-
if (!
|
|
3596
|
+
const root = join9(home, APP_SUPPORT, b.appDir);
|
|
3597
|
+
if (!existsSync10(root))
|
|
2868
3598
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
2869
3599
|
if (!b.profiled) {
|
|
2870
3600
|
const db2 = findFile(root, "Cookies");
|
|
@@ -2872,15 +3602,15 @@ function findCookieDb(home, source) {
|
|
|
2872
3602
|
throw new Error(`no cookie store under ${root}`);
|
|
2873
3603
|
return db2;
|
|
2874
3604
|
}
|
|
2875
|
-
const localStatePath =
|
|
2876
|
-
const localState =
|
|
3605
|
+
const localStatePath = join9(root, "Local State");
|
|
3606
|
+
const localState = existsSync10(localStatePath) ? readFileSync8(localStatePath, "utf8") : null;
|
|
2877
3607
|
const wanted = source.profile ?? "Default";
|
|
2878
3608
|
const { dir, ambiguous } = resolveProfileDir(localState, wanted);
|
|
2879
3609
|
if (ambiguous)
|
|
2880
3610
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
2881
3611
|
if (!dir)
|
|
2882
3612
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
2883
|
-
const db = cookieDbCandidates(
|
|
3613
|
+
const db = cookieDbCandidates(join9(root, dir)).find((p) => existsSync10(p));
|
|
2884
3614
|
if (!db)
|
|
2885
3615
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
2886
3616
|
return db;
|
|
@@ -2890,7 +3620,7 @@ async function readCookieRowsBun(home, source) {
|
|
|
2890
3620
|
}
|
|
2891
3621
|
function keychainKey(service) {
|
|
2892
3622
|
try {
|
|
2893
|
-
return
|
|
3623
|
+
return execFileSync2("security", ["find-generic-password", "-w", "-s", service], {
|
|
2894
3624
|
encoding: "utf8"
|
|
2895
3625
|
}).trim();
|
|
2896
3626
|
} catch {
|
|
@@ -2902,7 +3632,7 @@ function decryptJar(rows, safeStoragePw) {
|
|
|
2902
3632
|
}
|
|
2903
3633
|
var curlUsage = (org, cookie) => {
|
|
2904
3634
|
try {
|
|
2905
|
-
const out =
|
|
3635
|
+
const out = execFileSync2("curl", usageCurlArgs(org, cookie), {
|
|
2906
3636
|
encoding: "utf8",
|
|
2907
3637
|
maxBuffer: 8000000
|
|
2908
3638
|
});
|
|
@@ -2911,8 +3641,8 @@ var curlUsage = (org, cookie) => {
|
|
|
2911
3641
|
return { status: 0, body: "" };
|
|
2912
3642
|
}
|
|
2913
3643
|
};
|
|
2914
|
-
function fetchSeatUsage(jar, org, now,
|
|
2915
|
-
const { status, body } =
|
|
3644
|
+
function fetchSeatUsage(jar, org, now, run2) {
|
|
3645
|
+
const { status, body } = run2(org, cookieHeader(jar, org));
|
|
2916
3646
|
if (status !== 200)
|
|
2917
3647
|
return { usage: null, status };
|
|
2918
3648
|
try {
|
|
@@ -2923,7 +3653,7 @@ function fetchSeatUsage(jar, org, now, run) {
|
|
|
2923
3653
|
}
|
|
2924
3654
|
function launchAgentPlist(bun, script, home, source) {
|
|
2925
3655
|
const label = launchLabel(source);
|
|
2926
|
-
const log =
|
|
3656
|
+
const log = join9(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
|
|
2927
3657
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2928
3658
|
const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
|
|
2929
3659
|
`);
|
|
@@ -2945,32 +3675,32 @@ ${args}
|
|
|
2945
3675
|
`;
|
|
2946
3676
|
}
|
|
2947
3677
|
function plistPath2(home, source) {
|
|
2948
|
-
return
|
|
3678
|
+
return join9(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
|
|
2949
3679
|
}
|
|
2950
|
-
function
|
|
3680
|
+
function install(home, argv, source, log) {
|
|
2951
3681
|
const bun = argv[0] ?? process.execPath;
|
|
2952
3682
|
const script = argv[1] ?? "";
|
|
2953
3683
|
if (!script) {
|
|
2954
3684
|
log("cannot resolve the sleds script path to install the timer");
|
|
2955
3685
|
return 1;
|
|
2956
3686
|
}
|
|
2957
|
-
const dir =
|
|
2958
|
-
|
|
3687
|
+
const dir = join9(home, "Library", "LaunchAgents");
|
|
3688
|
+
mkdirSync9(dir, { recursive: true });
|
|
2959
3689
|
const path = plistPath2(home, source);
|
|
2960
|
-
|
|
3690
|
+
writeFileSync9(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
2961
3691
|
try {
|
|
2962
|
-
|
|
3692
|
+
execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
|
|
2963
3693
|
} catch {}
|
|
2964
|
-
|
|
3694
|
+
execFileSync2("launchctl", ["load", "-w", path]);
|
|
2965
3695
|
log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
|
|
2966
3696
|
return 0;
|
|
2967
3697
|
}
|
|
2968
|
-
function
|
|
3698
|
+
function uninstall(home, source, log) {
|
|
2969
3699
|
const path = plistPath2(home, source);
|
|
2970
3700
|
try {
|
|
2971
|
-
|
|
3701
|
+
execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
2972
3702
|
} catch {}
|
|
2973
|
-
log(
|
|
3703
|
+
log(existsSync10(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
2974
3704
|
return 0;
|
|
2975
3705
|
}
|
|
2976
3706
|
var seatSync = async (o) => {
|
|
@@ -2983,9 +3713,9 @@ var seatSync = async (o) => {
|
|
|
2983
3713
|
}
|
|
2984
3714
|
const { source } = parsed;
|
|
2985
3715
|
if (source.uninstall)
|
|
2986
|
-
return
|
|
3716
|
+
return uninstall(o.home, source, log);
|
|
2987
3717
|
if (source.install)
|
|
2988
|
-
return
|
|
3718
|
+
return install(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
2989
3719
|
if (platform() !== "darwin" && !o.readCookieRows) {
|
|
2990
3720
|
log("seat-sync is macOS-only for now.");
|
|
2991
3721
|
return 2;
|
|
@@ -3065,7 +3795,8 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
|
3065
3795
|
}
|
|
3066
3796
|
|
|
3067
3797
|
// src/commands/specEvent.ts
|
|
3068
|
-
|
|
3798
|
+
init_meter();
|
|
3799
|
+
import { readFileSync as readFileSync9, statSync } from "node:fs";
|
|
3069
3800
|
import { basename, isAbsolute, relative } from "node:path";
|
|
3070
3801
|
var HOOK_STDIN_TIMEOUT_MS2 = 1000;
|
|
3071
3802
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
@@ -3121,7 +3852,7 @@ function readFileOrNull(path) {
|
|
|
3121
3852
|
try {
|
|
3122
3853
|
if (statSync(path).size > 2000000)
|
|
3123
3854
|
return null;
|
|
3124
|
-
return
|
|
3855
|
+
return readFileSync9(path, "utf8");
|
|
3125
3856
|
} catch {
|
|
3126
3857
|
return null;
|
|
3127
3858
|
}
|
|
@@ -3164,7 +3895,9 @@ var specEvent = async (o) => {
|
|
|
3164
3895
|
};
|
|
3165
3896
|
|
|
3166
3897
|
// src/commands/status.ts
|
|
3167
|
-
|
|
3898
|
+
init_config();
|
|
3899
|
+
init_settings();
|
|
3900
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3168
3901
|
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
3169
3902
|
try {
|
|
3170
3903
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -3236,7 +3969,7 @@ var status = async (o) => {
|
|
|
3236
3969
|
const log = o.log ?? console.log;
|
|
3237
3970
|
const nowMs = (o.now ?? Date.now)();
|
|
3238
3971
|
const cfg = readConfig(o.home);
|
|
3239
|
-
const settings =
|
|
3972
|
+
const settings = existsSync11(settingsPath(o.home)) ? readSettings(o.home) : {};
|
|
3240
3973
|
const inSettings = readProxy(settings);
|
|
3241
3974
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
3242
3975
|
const token = inSettings?.token ?? cfg.token;
|
|
@@ -3290,15 +4023,19 @@ var status = async (o) => {
|
|
|
3290
4023
|
};
|
|
3291
4024
|
|
|
3292
4025
|
// src/commands/statusline.ts
|
|
4026
|
+
init_meter();
|
|
3293
4027
|
var statusline = async (o) => {
|
|
3294
4028
|
const log = o.log ?? console.log;
|
|
3295
4029
|
const now = (o.now ?? Date.now)();
|
|
3296
4030
|
const input = o.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
|
|
3297
4031
|
const window = seatWindowFromStdin(input, now);
|
|
4032
|
+
const session = seatSessionFromStdin(input, now);
|
|
3298
4033
|
let posts = [];
|
|
3299
4034
|
try {
|
|
3300
4035
|
if (window)
|
|
3301
|
-
posts = rememberSeat(o, window, now);
|
|
4036
|
+
posts = rememberSeat(o, window, session, now);
|
|
4037
|
+
else if (session)
|
|
4038
|
+
writeSeatState(o.home, { ...readSeatState(o.home), session });
|
|
3302
4039
|
} catch {}
|
|
3303
4040
|
const seatFragment = formatSeatFragment(window);
|
|
3304
4041
|
let line;
|
|
@@ -3317,9 +4054,9 @@ var statusline = async (o) => {
|
|
|
3317
4054
|
await Promise.allSettled(posts);
|
|
3318
4055
|
return 0;
|
|
3319
4056
|
};
|
|
3320
|
-
var rememberSeat = (o, window, now) => {
|
|
4057
|
+
var rememberSeat = (o, window, session, now) => {
|
|
3321
4058
|
const prior = readSeatState(o.home);
|
|
3322
|
-
let state = { ...prior, window };
|
|
4059
|
+
let state = { ...prior, window, ...session ? { session } : {} };
|
|
3323
4060
|
const src = meterSource(o.home);
|
|
3324
4061
|
const posts = [];
|
|
3325
4062
|
if (src) {
|
|
@@ -3342,11 +4079,18 @@ var rememberSeat = (o, window, now) => {
|
|
|
3342
4079
|
};
|
|
3343
4080
|
|
|
3344
4081
|
// src/commands/uninstall.ts
|
|
3345
|
-
|
|
3346
|
-
|
|
4082
|
+
init_config();
|
|
4083
|
+
init_meter();
|
|
4084
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4085
|
+
import { existsSync as existsSync12, rmSync as rmSync5 } from "node:fs";
|
|
4086
|
+
init_settings();
|
|
4087
|
+
var defaultUnload = (plist) => {
|
|
4088
|
+
spawnSync3("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
4089
|
+
};
|
|
4090
|
+
var uninstall2 = (o) => {
|
|
3347
4091
|
const log = o.log ?? console.log;
|
|
3348
4092
|
const removed = [];
|
|
3349
|
-
if (
|
|
4093
|
+
if (existsSync12(settingsPath(o.home))) {
|
|
3350
4094
|
let settings = readSettings(o.home);
|
|
3351
4095
|
let changed = false;
|
|
3352
4096
|
if (hasEitherKey(settings)) {
|
|
@@ -3378,16 +4122,22 @@ var uninstall3 = (o) => {
|
|
|
3378
4122
|
writeSettings(o.home, settings);
|
|
3379
4123
|
}
|
|
3380
4124
|
for (const file of [meterCachePath(o.home), seatStatePath(o.home)]) {
|
|
3381
|
-
if (
|
|
3382
|
-
|
|
4125
|
+
if (existsSync12(file)) {
|
|
4126
|
+
rmSync5(file, { force: true });
|
|
3383
4127
|
removed.push(file);
|
|
3384
4128
|
}
|
|
3385
4129
|
}
|
|
3386
4130
|
const skill = skillDir(o.home);
|
|
3387
|
-
if (
|
|
3388
|
-
|
|
4131
|
+
if (existsSync12(skill)) {
|
|
4132
|
+
rmSync5(skill, { recursive: true, force: true });
|
|
3389
4133
|
removed.push(skill);
|
|
3390
4134
|
}
|
|
4135
|
+
const plist = musherPlistPath(o.home);
|
|
4136
|
+
if (existsSync12(plist)) {
|
|
4137
|
+
(o.unloadAgent ?? defaultUnload)(plist);
|
|
4138
|
+
rmSync5(plist, { force: true });
|
|
4139
|
+
removed.push(`launchd agent ${MUSHER_LABEL}`);
|
|
4140
|
+
}
|
|
3391
4141
|
if (removed.length === 0) {
|
|
3392
4142
|
log("Nothing to remove; SLEDS was not installed.");
|
|
3393
4143
|
} else {
|
|
@@ -3405,10 +4155,58 @@ var hasEitherKey = (settings) => {
|
|
|
3405
4155
|
return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
|
|
3406
4156
|
};
|
|
3407
4157
|
|
|
4158
|
+
// src/commands/verify.ts
|
|
4159
|
+
init_meter();
|
|
4160
|
+
var jobIdFromTarget = (target) => {
|
|
4161
|
+
const t = target.trim();
|
|
4162
|
+
const m = /SLEDS-Attest:.*\bjob=([^\s]+)/.exec(t);
|
|
4163
|
+
if (m?.[1])
|
|
4164
|
+
return m[1];
|
|
4165
|
+
return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
|
|
4166
|
+
};
|
|
4167
|
+
var verify = async (o) => {
|
|
4168
|
+
const log = o.log ?? console.log;
|
|
4169
|
+
const error = o.error ?? console.error;
|
|
4170
|
+
const jobId = o.target ? jobIdFromTarget(o.target) : null;
|
|
4171
|
+
if (!jobId) {
|
|
4172
|
+
error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
|
|
4173
|
+
return 2;
|
|
4174
|
+
}
|
|
4175
|
+
const client = o.client ?? (() => {
|
|
4176
|
+
const src = meterSource(o.home);
|
|
4177
|
+
return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
|
|
4178
|
+
})();
|
|
4179
|
+
if (!client) {
|
|
4180
|
+
error("No seat on this machine. Run `sleds init` first.");
|
|
4181
|
+
return 1;
|
|
4182
|
+
}
|
|
4183
|
+
const a = await client.attestation(jobId);
|
|
4184
|
+
if (!a) {
|
|
4185
|
+
error(`No attestation for ${jobId} (not delivered, not signed, not your org, or the proxy is down).`);
|
|
4186
|
+
return 1;
|
|
4187
|
+
}
|
|
4188
|
+
if (!a.publicKey) {
|
|
4189
|
+
error(`Attestation found but its key ${a.attestation.keyId} is not on file — cannot verify.`);
|
|
4190
|
+
return 1;
|
|
4191
|
+
}
|
|
4192
|
+
const ok = verifyAttestation(a.attestation.payload, a.attestation.sig, a.publicKey);
|
|
4193
|
+
const f = parsePayload(a.attestation.payload);
|
|
4194
|
+
log(`${ok ? "VERIFIED" : "INVALID "} job ${jobId}`);
|
|
4195
|
+
log(` model ${f.model ?? a.model ?? "unknown"}`);
|
|
4196
|
+
log(` engine ${f.engine ?? a.engine}${f.seat ? ` · seat ${f.seat}` : ""}`);
|
|
4197
|
+
log(` finished ${f.finished ?? "?"} · signed ${formatLocal(a.attestation.signedAt)} · key ${a.attestation.keyId}`);
|
|
4198
|
+
if (a.outputs.length)
|
|
4199
|
+
log(` outputs ${a.outputs.join(" ")}`);
|
|
4200
|
+
log(` ${a.attestation.trailer}`);
|
|
4201
|
+
if (!ok)
|
|
4202
|
+
log(" The signature does not match the payload under the org's public key.");
|
|
4203
|
+
return ok ? 0 : 1;
|
|
4204
|
+
};
|
|
4205
|
+
|
|
3408
4206
|
// src/schedule.ts
|
|
3409
|
-
import { spawnSync } from "node:child_process";
|
|
4207
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3410
4208
|
var defaultExec = (cmd, args) => {
|
|
3411
|
-
const r =
|
|
4209
|
+
const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
3412
4210
|
if (r.error)
|
|
3413
4211
|
return { ok: false, stderr: r.error.message };
|
|
3414
4212
|
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
@@ -3434,14 +4232,21 @@ commands:
|
|
|
3434
4232
|
--until-reset <when> revert at this time: +5h, +90m, or an ISO timestamp
|
|
3435
4233
|
--reason "<text>" note shown by \`sleds status\`
|
|
3436
4234
|
--no-schedule record the deadline but do not install a launchd/systemd revert
|
|
4235
|
+
defer Hit a limit? Resume this session on this Mac at the reset \u2014 runs itself on your
|
|
4236
|
+
seat, no key needed (the musher). Uses the last session the status line saw.
|
|
4237
|
+
--at +2h | <ISO> start then instead of the binding reset
|
|
4238
|
+
--session <id> --cwd <dir> --model <id> override what was remembered
|
|
4239
|
+
musher The agent that runs deferred jobs here: --install / --uninstall / --status,
|
|
4240
|
+
--claude <path>, --mode <permission-mode> (default acceptEdits), --timeout <min>.
|
|
4241
|
+
run <jobId> Run one deferred job on this Mac now (the manual musher).
|
|
4242
|
+
verify <jobId | SLEDS-Attest line> Check a finished job's attestation: which model built it,
|
|
4243
|
+
signed by the org's key, verified locally.
|
|
3437
4244
|
off Switch Claude Code back to your seat. Restores whatever \`on\` replaced.
|
|
3438
4245
|
uninstall Remove the proxy settings, skill and status line. Keeps ~/.sleds-token.
|
|
3439
4246
|
status Show the switch state, deadline, proxy health and wall mode.
|
|
3440
4247
|
doctor Check the whole install: which seat this shell is, Claude settings, proxy, hooks,
|
|
3441
|
-
Node version, stale links, the
|
|
4248
|
+
Node version, stale links, the seat's last quota report.
|
|
3442
4249
|
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
4250
|
mcp Run the MCP server on stdio: tools usage, estimate, overnight, jobs.
|
|
3446
4251
|
Register with \`claude mcp add sleds -- sleds mcp\` (Cowork sees the same server).
|
|
3447
4252
|
seat-sync Read the seat's subscription usage (weekly/5h limits, per-model, credits) from a
|
|
@@ -3492,17 +4297,24 @@ var stringFlag = (flags, name) => {
|
|
|
3492
4297
|
const v = flags[name];
|
|
3493
4298
|
return typeof v === "string" ? v : undefined;
|
|
3494
4299
|
};
|
|
4300
|
+
var hookStdin = async () => {
|
|
4301
|
+
if (process.stdin.isTTY)
|
|
4302
|
+
return;
|
|
4303
|
+
const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
|
|
4304
|
+
const text = await readStdin3(300);
|
|
4305
|
+
return text.trim() ? text : undefined;
|
|
4306
|
+
};
|
|
3495
4307
|
var sledsArgv = () => {
|
|
3496
4308
|
const script = process.argv[1];
|
|
3497
4309
|
return script ? [process.execPath, script] : [process.execPath];
|
|
3498
4310
|
};
|
|
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(
|
|
4311
|
+
var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
4312
|
+
var statusLineCommand = (argv) => [...argv, "statusline"].map(shellQuote).join(" ");
|
|
4313
|
+
var seatEventCommand = (argv) => [...argv, "seat-event"].map(shellQuote).join(" ");
|
|
4314
|
+
var specEventCommand = (argv) => [...argv, "spec-event"].map(shellQuote).join(" ");
|
|
3503
4315
|
var main = async (argv) => {
|
|
3504
4316
|
const { cmd, flags } = parseArgs(argv);
|
|
3505
|
-
const home =
|
|
4317
|
+
const home = homedir();
|
|
3506
4318
|
const scheduler = {
|
|
3507
4319
|
platform: platform2(),
|
|
3508
4320
|
home,
|
|
@@ -3522,6 +4334,19 @@ var main = async (argv) => {
|
|
|
3522
4334
|
noProbe: flags["no-probe"] === true,
|
|
3523
4335
|
proxyAlways: flags["proxy-always"] === true,
|
|
3524
4336
|
seatFirst: flags["seat-first"] === true,
|
|
4337
|
+
installMusher: () => {
|
|
4338
|
+
if (platform2() !== "darwin")
|
|
4339
|
+
return null;
|
|
4340
|
+
const lines = [];
|
|
4341
|
+
const code = musherInstall({
|
|
4342
|
+
home,
|
|
4343
|
+
argv: [],
|
|
4344
|
+
sledsArgv: sledsArgv(),
|
|
4345
|
+
log: (l) => lines.push(l),
|
|
4346
|
+
error: (l) => lines.push(l)
|
|
4347
|
+
});
|
|
4348
|
+
return code === 0 ? lines[0] ?? "installed the musher agent" : `musher not installed: ${lines.join(" ")}`;
|
|
4349
|
+
},
|
|
3525
4350
|
prompt: promptStdin,
|
|
3526
4351
|
statusLineCommand: statusLineCommand(sledsArgv()),
|
|
3527
4352
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
@@ -3548,16 +4373,22 @@ var main = async (argv) => {
|
|
|
3548
4373
|
});
|
|
3549
4374
|
case "off":
|
|
3550
4375
|
return off({ home, scheduler });
|
|
4376
|
+
case "defer":
|
|
4377
|
+
return defer({ home, argv: process.argv.slice(3), stdin: await hookStdin() });
|
|
4378
|
+
case "musher":
|
|
4379
|
+
return musher({ home, argv: process.argv.slice(3), sledsArgv: sledsArgv() });
|
|
4380
|
+
case "run":
|
|
4381
|
+
return run({ home, jobId: process.argv[3] });
|
|
4382
|
+
case "verify":
|
|
4383
|
+
return verify({ home, target: process.argv.slice(3).join(" ") || undefined });
|
|
3551
4384
|
case "uninstall":
|
|
3552
|
-
return
|
|
4385
|
+
return uninstall2({ home });
|
|
3553
4386
|
case "doctor":
|
|
3554
4387
|
return doctor({ home });
|
|
3555
4388
|
case "status":
|
|
3556
4389
|
return status({ home });
|
|
3557
4390
|
case "meter":
|
|
3558
4391
|
return meter({ home });
|
|
3559
|
-
case "bar":
|
|
3560
|
-
return bar({ home, argv: process.argv.slice(3) });
|
|
3561
4392
|
case "mcp":
|
|
3562
4393
|
return mcp({ home });
|
|
3563
4394
|
case "seat-sync":
|
|
@@ -3572,7 +4403,7 @@ var main = async (argv) => {
|
|
|
3572
4403
|
case "version":
|
|
3573
4404
|
case "--version":
|
|
3574
4405
|
case "-v":
|
|
3575
|
-
console.log(
|
|
4406
|
+
console.log("sleds 0.1.14");
|
|
3576
4407
|
return 0;
|
|
3577
4408
|
default:
|
|
3578
4409
|
console.error(`unknown command: ${cmd}
|