@azure-id/orc 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +455 -0
- package/README-id.md +52 -23
- package/README.md +36 -20
- package/bin/build-agents.js +205 -206
- package/bin/clear-logs.js +620 -0
- package/bin/cli.js +47417 -46420
- package/bin/fix.js +459 -0
- package/bin/gotcha.js +288 -8
- package/bin/habit.js +1538 -1453
- package/bin/mockrun-catalog.js +3 -0
- package/bin/onboarding-content.js +2 -1
- package/bin/pricing.json +28 -21
- package/bin/trace-write.js +869 -657
- package/bin/verify-contracts.js +5552 -5471
- package/bin/verify-package.js +716 -705
- package/bin/webui/api.js +125 -6
- package/bin/webui/css/panels/hookui.css +79 -0
- package/bin/webui/fixtures/behaviour.js +30 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/flow.js +81 -81
- package/bin/webui/fixtures/hookui.js +506 -431
- package/bin/webui/fixtures/index.js +32 -3
- package/bin/webui/fixtures/knowledge.js +2 -2
- package/bin/webui/fixtures/maintenance.js +123 -42
- package/bin/webui/fixtures/rules.js +1007 -1002
- package/bin/webui/fixtures/settings.js +6 -7
- package/bin/webui/fixtures/stats.js +9 -6
- package/bin/webui/fixtures/wait.js +98 -97
- package/bin/webui/i18n/en/behaviour.json +13 -0
- package/bin/webui/i18n/en/hookui.json +290 -194
- package/bin/webui/i18n/en/maintenance.json +71 -52
- package/bin/webui/i18n/en/settings.json +60 -60
- package/bin/webui/i18n/id/behaviour.json +13 -0
- package/bin/webui/i18n/id/hookui.json +290 -194
- package/bin/webui/i18n/id/maintenance.json +71 -52
- package/bin/webui/i18n/id/settings.json +60 -60
- package/bin/webui/js/05-banners.js +181 -172
- package/bin/webui/js/06-edit.js +193 -183
- package/bin/webui/js/panels/behaviour.js +77 -12
- package/bin/webui/js/panels/hookui.js +2196 -1691
- package/bin/webui/js/panels/maintenance.js +336 -235
- package/bin/webui/js/panels/settings.js +726 -719
- package/mock-run/INDEX.md +1 -0
- package/mock-run/a-normal-day.md +2 -2
- package/mock-run/extra-slots.md +177 -177
- package/mock-run/habits.md +1 -1
- package/mock-run/orc-aftermath.md +392 -392
- package/mock-run/orc-budget.md +29 -33
- package/mock-run/orc-cli.md +200 -200
- package/mock-run/orc-explain.md +86 -86
- package/mock-run/orc-extra.md +389 -392
- package/mock-run/orc-fast.md +2 -2
- package/mock-run/orc-fix.md +87 -0
- package/mock-run/orc-pattern.md +112 -112
- package/mock-run/orc-quick.md +146 -146
- package/mock-run/orc.md +4 -4
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +176 -173
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
- package/templates/agents/orc-executor-haiku-4-5.md +1 -1
- package/templates/agents/orc-executor-opus-5-low.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
- package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
- package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-recon-sonnet-4-6-med.md → orc-recon-sonnet-5-med.md} +4 -4
- package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
- package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
- package/templates/commands/orc-fast.md +10 -10
- package/templates/commands/orc-fix.md +18 -0
- package/templates/commands/orc-wiki.md +64 -64
- package/templates/hooks/README.md +75 -7
- package/templates/hooks/orc-session-hook.js +78 -3
- package/templates/hooks/orc-statusline-render.js +317 -23
- package/templates/hooks/orc-statusline.js +162 -49
- package/templates/hooks/orc-subagent-line.js +23 -5
- package/templates/hooks/orc-weather-fetch.js +123 -0
- package/templates/skills/_shared/extra-dispatch.md +1331 -1331
- package/templates/skills/_shared/gotchas.md +27 -7
- package/templates/skills/_shared/habits.md +2 -2
- package/templates/skills/_shared/opus5-only.md +137 -137
- package/templates/skills/_shared/phases/review.md +2 -2
- package/templates/skills/_shared/phases/trace-verbs.md +17 -3
- package/templates/skills/_shared/phases/trace.md +136 -136
- package/templates/skills/_shared/review-slice.md +28 -1
- package/templates/skills/_shared/wait.md +1 -0
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +227 -227
- package/templates/skills/orc/config.md +25 -27
- package/templates/skills/orc/examples/full-run-mock.md +74 -73
- package/templates/skills/orc/references/effort-and-mode.md +222 -222
- package/templates/skills/orc/references/preflight-report.md +3 -3
- package/templates/skills/orc/references/ultra-mode.md +1 -1
- package/templates/skills/orc/schemas/checkpoint.md +122 -122
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
- package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
- package/templates/skills/orc-analyze/SKILL.md +4 -2
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
- package/templates/skills/orc-diy/SKILL.md +5 -5
- package/templates/skills/orc-fast/SKILL.md +210 -208
- package/templates/skills/orc-fix/SKILL.md +110 -0
- package/templates/skills/orc-grill/SKILL.md +232 -232
- package/templates/skills/orc-grill/references/grill-doc.md +1 -1
- package/templates/skills/orc-mini/SKILL.md +4 -4
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
- package/templates/skills/orc-quick/README.md +536 -536
- package/templates/skills/orc-quick/SKILL.md +2 -2
- package/templates/skills/orc-quick/references/context-doc.md +145 -145
- package/templates/skills/orc-quick/references/dispatch-gate.md +220 -219
- package/templates/skills/orc-quick/references/gh-mode.md +4 -2
- package/templates/skills/orc-retro/SKILL.md +1 -1
- package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
- package/templates/skills/orc-wiki/README.md +325 -325
- package/templates/skills/orc-wiki/SKILL.md +195 -195
- package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
- package/templates/skills/orc-wiki/references/extra.md +2 -2
- package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
- package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
- package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
|
@@ -401,6 +401,23 @@ const SCAN = {
|
|
|
401
401
|
trace_age_min: null, trace_state: null, last_agent: null, retries: null,
|
|
402
402
|
};
|
|
403
403
|
|
|
404
|
+
// v2.1.0 (DE-23) — is a `statusLine.refreshInterval` set? The local file wins
|
|
405
|
+
// over the project file, as in Claude Code. One small read per run, fail-silent.
|
|
406
|
+
function refreshIntervalSet(projectDir) {
|
|
407
|
+
const fs = require("fs");
|
|
408
|
+
const path = require("path");
|
|
409
|
+
for (const f of ["settings.local.json", "settings.json"]) {
|
|
410
|
+
try {
|
|
411
|
+
const j = JSON.parse(fs.readFileSync(path.join(projectDir, ".claude", f), "utf8"));
|
|
412
|
+
if (j && j.statusLine && Object.prototype.hasOwnProperty.call(j.statusLine, "refreshInterval")) {
|
|
413
|
+
const n = Number(j.statusLine.refreshInterval);
|
|
414
|
+
return Number.isFinite(n) && n >= 1;
|
|
415
|
+
}
|
|
416
|
+
} catch (_) {}
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
404
421
|
function ledger(projectDir, sid) {
|
|
405
422
|
if (LED) return LED;
|
|
406
423
|
const fs = require("fs");
|
|
@@ -473,7 +490,7 @@ function readPlan(d) {
|
|
|
473
490
|
const projectDir =
|
|
474
491
|
(d.workspace && d.workspace.project_dir) || d.cwd || process.cwd();
|
|
475
492
|
const raw = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
476
|
-
if (!/^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]
|
|
493
|
+
if (!/^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(raw)) return null;
|
|
477
494
|
const lock = JSON.parse(
|
|
478
495
|
fs.readFileSync(path.join(projectDir, ".claude", "orc", "statusline.lock.json"), "utf8")
|
|
479
496
|
);
|
|
@@ -572,16 +589,27 @@ process.stdin.on("end", () => {
|
|
|
572
589
|
? { used_percentage: o.used_percentage, resets_at: o.resets_at == null ? null : o.resets_at }
|
|
573
590
|
: null;
|
|
574
591
|
fs.mkdirSync(orcDir, { recursive: true });
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
592
|
+
// v2.1.0 (DE-24) — rewrite ONLY when the numbers change. With a refresh
|
|
593
|
+
// timer every tick re-ran this, and a fresh `written_at` on old numbers
|
|
594
|
+
// made them look new to `orc usage check`. `written_at` is now the time
|
|
595
|
+
// the reading last MOVED.
|
|
596
|
+
const usageFile = path.join(orcDir, "usage.json");
|
|
597
|
+
const reading = {
|
|
598
|
+
five_hour: win(rl0 && rl0.five_hour),
|
|
599
|
+
seven_day: win(rl0 && rl0.seven_day),
|
|
600
|
+
context_used_percentage:
|
|
601
|
+
cw0 && typeof cw0.used_percentage === "number" ? cw0.used_percentage : null,
|
|
602
|
+
};
|
|
603
|
+
let prev = null;
|
|
604
|
+
try {
|
|
605
|
+
prev = JSON.parse(fs.readFileSync(usageFile, "utf8"));
|
|
606
|
+
} catch (_) {}
|
|
607
|
+
const same =
|
|
608
|
+
prev &&
|
|
609
|
+
prev.written_at &&
|
|
610
|
+
JSON.stringify([prev.five_hour, prev.seven_day, prev.context_used_percentage]) ===
|
|
611
|
+
JSON.stringify([reading.five_hour, reading.seven_day, reading.context_used_percentage]);
|
|
612
|
+
if (!same) fs.writeFileSync(usageFile, JSON.stringify(Object.assign(reading, { written_at: Date.now() })) + "\n");
|
|
585
613
|
// -- Session consumption (v1.2.0) -------------------------------------
|
|
586
614
|
// `usage.json` is a SNAPSHOT of the window. It cannot answer "how much
|
|
587
615
|
// has THIS session eaten", which is the question a user actually asks
|
|
@@ -1085,6 +1113,18 @@ process.stdin.on("end", () => {
|
|
|
1085
1113
|
line2 = "";
|
|
1086
1114
|
}
|
|
1087
1115
|
|
|
1116
|
+
// v2.1.0 (DE-23) — when a refresh timer is set, a motion part advances ONE
|
|
1117
|
+
// step per run (the frame after the last frame shown), not the frame for this
|
|
1118
|
+
// millisecond: at 2-10 s the wall clock skipped most frames. No timer → the
|
|
1119
|
+
// wall-clock rule stays, so `step` is null.
|
|
1120
|
+
let step = null;
|
|
1121
|
+
try {
|
|
1122
|
+
if (LED && refreshIntervalSet((d.workspace && d.workspace.project_dir) || d.cwd || process.cwd())) {
|
|
1123
|
+
LED.frame = (Number(LED.frame) || 0) + 1;
|
|
1124
|
+
step = LED.frame;
|
|
1125
|
+
}
|
|
1126
|
+
} catch (_) {}
|
|
1127
|
+
|
|
1088
1128
|
// ONE write, after every block that touches the ledger has had its say. It is
|
|
1089
1129
|
// last on purpose: a render that throws half way through still prints, and a
|
|
1090
1130
|
// ledger that could not be written never takes the status line down with it.
|
|
@@ -1102,6 +1142,7 @@ process.stdin.on("end", () => {
|
|
|
1102
1142
|
scan: SCAN,
|
|
1103
1143
|
derived: { verdict, reasons, version: ver },
|
|
1104
1144
|
now: Date.now(),
|
|
1145
|
+
step,
|
|
1105
1146
|
});
|
|
1106
1147
|
if (composed != null) {
|
|
1107
1148
|
process.stdout.write(composed);
|
|
@@ -1117,7 +1158,8 @@ process.stdin.on("end", () => {
|
|
|
1117
1158
|
// 1 statusline_custom is not `on` the shipped lines. BYTE-IDENTICAL.
|
|
1118
1159
|
// 2 statusline-compiled.json missing / default + statusline-layout-unreadable
|
|
1119
1160
|
// unparseable / schema mismatch
|
|
1120
|
-
// 3 orc_version
|
|
1161
|
+
// 3 catalog_hash moved (orc_version when default + statusline-layout-stale
|
|
1162
|
+
// the install has no catalog_hash)
|
|
1121
1163
|
// 4 an unknown op or an unknown binding default + statusline-layout-skew
|
|
1122
1164
|
// 5 the cheap shape guard fails default + statusline-layout-invalid
|
|
1123
1165
|
// 6 otherwise run the program
|
|
@@ -1145,7 +1187,7 @@ function custom(d, ctx) {
|
|
|
1145
1187
|
// The trailing \r is tolerated on purpose: a config file written on
|
|
1146
1188
|
// Windows carries CRLF, and a $-anchored match silently never fires
|
|
1147
1189
|
// there — which is a feature that is ON in the file and OFF on the bar.
|
|
1148
|
-
on = /^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]
|
|
1190
|
+
on = /^[ \t]*statusline_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(raw);
|
|
1149
1191
|
} catch (_) {}
|
|
1150
1192
|
if (!on) return null;
|
|
1151
1193
|
|
|
@@ -1164,11 +1206,13 @@ function custom(d, ctx) {
|
|
|
1164
1206
|
try {
|
|
1165
1207
|
lock = JSON.parse(fs.readFileSync(path.join(orcDir, "statusline.lock.json"), "utf8"));
|
|
1166
1208
|
} catch (_) {}
|
|
1167
|
-
|
|
1209
|
+
// The catalogue hash decides when the install carries one: a CLI version
|
|
1210
|
+
// bump that changed no component must not drop the user's design.
|
|
1211
|
+
let inst = {};
|
|
1168
1212
|
try {
|
|
1169
|
-
|
|
1213
|
+
inst = JSON.parse(fs.readFileSync(path.join(__dirname, "orc-version.json"), "utf8")) || {};
|
|
1170
1214
|
} catch (_) {}
|
|
1171
|
-
if (!lock || (
|
|
1215
|
+
if (!lock || (inst.catalog_hash ? lock.catalog_hash !== inst.catalog_hash : (inst.version && lock.orc_version !== inst.version)))
|
|
1172
1216
|
return slFallback(orcDir, "statusline-layout-stale");
|
|
1173
1217
|
|
|
1174
1218
|
const engine = require("./orc-statusline-render.js");
|
|
@@ -1178,6 +1222,7 @@ function custom(d, ctx) {
|
|
|
1178
1222
|
for (const b of lock.bindings || []) {
|
|
1179
1223
|
if (!engine.BINDINGS[b]) return slFallback(orcDir, "statusline-layout-skew");
|
|
1180
1224
|
}
|
|
1225
|
+
if (engine.OPS && !opsKnown(prog.lines, engine.OPS)) return slFallback(orcDir, "statusline-layout-skew");
|
|
1181
1226
|
|
|
1182
1227
|
// Rung 5. The cheap shape guard: at most three lines, at most `max_per_line`
|
|
1183
1228
|
// components on any of them, and the dense-prefix rule —
|
|
@@ -1206,16 +1251,25 @@ function custom(d, ctx) {
|
|
|
1206
1251
|
// Rung 6. Run the program. Failure isolation lives inside the engine: one
|
|
1207
1252
|
// throwing item emits its unknown form and the rest of the line survives.
|
|
1208
1253
|
SCAN.preset = prog.preset || null;
|
|
1254
|
+
if ((lock.providers || []).indexOf("scan.weather") >= 0) weather(orcDir, prog.lines);
|
|
1209
1255
|
const out = engine.render(prog, {
|
|
1210
1256
|
payload: ctx.payload,
|
|
1211
1257
|
ledger: ctx.ledger,
|
|
1212
1258
|
scan: ctx.scan,
|
|
1213
1259
|
derived: ctx.derived,
|
|
1214
1260
|
now: ctx.now,
|
|
1215
|
-
|
|
1261
|
+
step: ctx.step,
|
|
1262
|
+
cols: Number(process.env.COLUMNS) || Number(d.columns) || 0,
|
|
1216
1263
|
env: process.env,
|
|
1217
1264
|
});
|
|
1218
1265
|
if (out.errors && out.errors.length) slNote(orcDir, "statusline-item-failed", out.errors[0]);
|
|
1266
|
+
else {
|
|
1267
|
+
// A working bar clears the old fallback, so `orc doctor` stops naming it.
|
|
1268
|
+
try {
|
|
1269
|
+
const st = path.join(orcDir, "statusline-state.json");
|
|
1270
|
+
if (fs.existsSync(st)) fs.unlinkSync(st);
|
|
1271
|
+
} catch (_) {}
|
|
1272
|
+
}
|
|
1219
1273
|
return out.text;
|
|
1220
1274
|
} catch (_) {
|
|
1221
1275
|
// Even the ladder must not throw. A status line that crashes is a status
|
|
@@ -1224,6 +1278,67 @@ function custom(d, ctx) {
|
|
|
1224
1278
|
}
|
|
1225
1279
|
}
|
|
1226
1280
|
|
|
1281
|
+
// Every compiled op must be one the engine walks. A plain stack, no copies.
|
|
1282
|
+
function opsKnown(lines, OPS) {
|
|
1283
|
+
const st = [];
|
|
1284
|
+
for (const l of lines || []) st.push(l.ops || []);
|
|
1285
|
+
while (st.length) {
|
|
1286
|
+
const list = st.pop();
|
|
1287
|
+
for (let i = 0; i < list.length; i++) {
|
|
1288
|
+
const o = list[i];
|
|
1289
|
+
if (!o || !OPS[o.op]) return false;
|
|
1290
|
+
if (o.children) st.push(o.children);
|
|
1291
|
+
if (o.cap && o.cap.ops) st.push(o.cap.ops);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return true;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// ── WEATHER (v2.0.4) ───────────────────────────────────────────────────────
|
|
1298
|
+
// ONE small file read. The network belongs to a DETACHED fetcher that this
|
|
1299
|
+
// hook starts and never waits for; the bar shows the last cached answer, or an
|
|
1300
|
+
// em dash. A lock file keeps a slow network from starting a fetch per render.
|
|
1301
|
+
function weather(orcDir, lines) {
|
|
1302
|
+
try {
|
|
1303
|
+
const fs = require("fs");
|
|
1304
|
+
const path = require("path");
|
|
1305
|
+
const now = Date.now();
|
|
1306
|
+
let j = null;
|
|
1307
|
+
try {
|
|
1308
|
+
j = JSON.parse(fs.readFileSync(path.join(orcDir, "weather.json"), "utf8"));
|
|
1309
|
+
} catch (_) {}
|
|
1310
|
+
SCAN.weather = j && typeof j === "object" ? j : undefined;
|
|
1311
|
+
if (j && typeof j.at === "number" && now - j.at < 30 * 60 * 1000) return;
|
|
1312
|
+
if (process.env.ORC_STATUSLINE_NO_NET === "1") return;
|
|
1313
|
+
const lockFile = path.join(orcDir, "weather.lock");
|
|
1314
|
+
try {
|
|
1315
|
+
if (now - fs.statSync(lockFile).mtimeMs < 2 * 60 * 1000) return;
|
|
1316
|
+
} catch (_) {}
|
|
1317
|
+
// Location and units come from the first op that binds `weather.*`.
|
|
1318
|
+
let p = null;
|
|
1319
|
+
const st = (lines || []).map((l) => l.ops || []);
|
|
1320
|
+
while (st.length && !p) {
|
|
1321
|
+
for (const o of st.pop()) {
|
|
1322
|
+
if (!o) continue;
|
|
1323
|
+
if (typeof o.b === "string" && o.b.indexOf("weather.") === 0 && o.p) {
|
|
1324
|
+
p = o.p;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
if (o.children) st.push(o.children);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
p = p || {};
|
|
1331
|
+
fs.writeFileSync(lockFile, String(now));
|
|
1332
|
+
require("child_process")
|
|
1333
|
+
.spawn(process.execPath, [path.join(__dirname, "orc-weather-fetch.js"), orcDir, String(p.location || ""), p.units === "us" ? "us" : "metric"], {
|
|
1334
|
+
detached: true,
|
|
1335
|
+
stdio: "ignore",
|
|
1336
|
+
windowsHide: true,
|
|
1337
|
+
})
|
|
1338
|
+
.unref();
|
|
1339
|
+
} catch (_) {}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1227
1342
|
// A fallback RECORDS ITSELF. `orc doctor` reads this file and names the finding
|
|
1228
1343
|
// with the exact command that clears it.
|
|
1229
1344
|
function slFallback(orcDir, finding) {
|
|
@@ -1548,7 +1663,15 @@ function extendedScan(d, wants, wantsProvider) {
|
|
|
1548
1663
|
// planner again. A series nothing binds is not kept, so the ledger does not
|
|
1549
1664
|
// grow for a user whose layout has no sparkline on it.
|
|
1550
1665
|
const SERIES_MAX = 16;
|
|
1551
|
-
const SERIES_MIN_GAP_MS =
|
|
1666
|
+
const SERIES_MIN_GAP_MS = 10000;
|
|
1667
|
+
// Series name → the binding whose number it stores. The stored sample IS the
|
|
1668
|
+
// binding's value, so a spark and the number beside it can never disagree.
|
|
1669
|
+
const SERIES_BINDING = {
|
|
1670
|
+
agents: "run.agents", burn: "burn.rate", cost: "cost.usd", cachehit: "cache.hit_ratio",
|
|
1671
|
+
cachewrite: "cache.write_tokens", lines: "cost.lines_net", extraspend: "extra.spend",
|
|
1672
|
+
speed: "mtok.speed", mtok: "mtok.total", mtokkind: "mtok.cache_read",
|
|
1673
|
+
quota5h: "quota.5h.pct", quotawk: "quota.week.pct", ucs: "ucs.pct",
|
|
1674
|
+
};
|
|
1552
1675
|
|
|
1553
1676
|
function sampleSeries(d, plan) {
|
|
1554
1677
|
try {
|
|
@@ -1556,42 +1679,32 @@ function sampleSeries(d, plan) {
|
|
|
1556
1679
|
const led = LED;
|
|
1557
1680
|
if (!led) return;
|
|
1558
1681
|
const now = Date.now();
|
|
1559
|
-
// A sample every 20 seconds, not every render. Sixteen samples at the
|
|
1560
|
-
// render rate would be five seconds of history, which is not history — it
|
|
1561
|
-
// is the same number sixteen times.
|
|
1562
|
-
if (led.series_at && now - led.series_at < SERIES_MIN_GAP_MS) return;
|
|
1563
|
-
led.series_at = now;
|
|
1564
1682
|
led.series = led.series || {};
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1683
|
+
// A sample every 10 seconds, not every render. Sixteen samples at the
|
|
1684
|
+
// render rate would be a few seconds of history, which is not history — it
|
|
1685
|
+
// is the same number sixteen times. While no series has a point yet, it is
|
|
1686
|
+
// sampled at once, so a new spark has a point on its first render.
|
|
1687
|
+
const empty = [...plan.series].every((k) => !(led.series[k] && led.series[k].length));
|
|
1688
|
+
if (!empty && led.series_at && now - led.series_at < SERIES_MIN_GAP_MS) return;
|
|
1689
|
+
led.series_at = now;
|
|
1690
|
+
// The engine is required by custom() on this same render, so this costs no
|
|
1691
|
+
// extra file read.
|
|
1692
|
+
const B = require("./orc-statusline-render.js").BINDINGS;
|
|
1693
|
+
const c = { payload: d, ledger: led, scan: SCAN, now, derived: {}, task: null };
|
|
1694
|
+
for (const key of plan.series) {
|
|
1695
|
+
const b = SERIES_BINDING[key];
|
|
1696
|
+
if (!b || !B[b]) continue;
|
|
1697
|
+
let v = null;
|
|
1698
|
+
try {
|
|
1699
|
+
v = B[b](c);
|
|
1700
|
+
} catch (_) {}
|
|
1701
|
+
if (typeof v !== "number" || !Number.isFinite(v)) continue;
|
|
1568
1702
|
const arr = led.series[key] || [];
|
|
1569
|
-
|
|
1703
|
+
// Two decimals: Math.round turned every dollar amount below 1 into 0.
|
|
1704
|
+
arr.push(Number(v.toFixed(2)));
|
|
1570
1705
|
while (arr.length > SERIES_MAX) arr.shift();
|
|
1571
1706
|
led.series[key] = arr;
|
|
1572
|
-
};
|
|
1573
|
-
const rl = d.rate_limits || {};
|
|
1574
|
-
const w = led.five_hour;
|
|
1575
|
-
push("quota5h", rl.five_hour && rl.five_hour.used_percentage);
|
|
1576
|
-
push("quotawk", rl.seven_day && rl.seven_day.used_percentage);
|
|
1577
|
-
push("ucs", w && typeof w.last === "number" && typeof w.baseline === "number"
|
|
1578
|
-
? Math.max(0, (w.accumulated || 0) + Math.max(0, w.last - w.baseline))
|
|
1579
|
-
: null);
|
|
1580
|
-
const tok = led.tok;
|
|
1581
|
-
if (tok) {
|
|
1582
|
-
push("mtok", (tok.input || 0) + (tok.cache_write || 0) + (tok.cache_read || 0) + (tok.output || 0));
|
|
1583
|
-
push("mtokkind", tok.cache_read || 0);
|
|
1584
1707
|
}
|
|
1585
|
-
push("agents", SCAN.spawns);
|
|
1586
|
-
push("cost", d.cost && d.cost.total_cost_usd != null ? d.cost.total_cost_usd * 100 : null);
|
|
1587
|
-
push("cachehit", d.prompt_cache && d.prompt_cache.hit_ratio != null
|
|
1588
|
-
? (d.prompt_cache.hit_ratio <= 1 ? d.prompt_cache.hit_ratio * 100 : d.prompt_cache.hit_ratio)
|
|
1589
|
-
: null);
|
|
1590
|
-
push("cachewrite", d.prompt_cache && d.prompt_cache.cache_write_tokens);
|
|
1591
|
-
push("lines", d.cost && d.cost.total_lines_added != null
|
|
1592
|
-
? (d.cost.total_lines_added || 0) - (d.cost.total_lines_removed || 0)
|
|
1593
|
-
: null);
|
|
1594
|
-
push("extraspend", SCAN.extra_spend && SCAN.extra_spend.usd != null ? SCAN.extra_spend.usd * 100 : null);
|
|
1595
1708
|
} catch (_) {
|
|
1596
1709
|
// A series is a nicety. It never takes the status line down with it.
|
|
1597
1710
|
}
|
|
@@ -151,7 +151,7 @@ function render(d, tasks) {
|
|
|
151
151
|
let on = false;
|
|
152
152
|
try {
|
|
153
153
|
const cfg = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
154
|
-
on = /^[ \t]*subagent_line_custom:[ \t]*["']?on["']?[ \t]
|
|
154
|
+
on = /^[ \t]*subagent_line_custom:[ \t]*["']?on["']?[ \t]*(?:#.*)?\r?$/m.test(cfg);
|
|
155
155
|
} catch (_) {}
|
|
156
156
|
if (!on) return null;
|
|
157
157
|
|
|
@@ -167,11 +167,12 @@ function render(d, tasks) {
|
|
|
167
167
|
try {
|
|
168
168
|
lock = JSON.parse(fs.readFileSync(path.join(orcDir, "subagent.lock.json"), "utf8"));
|
|
169
169
|
} catch (_) {}
|
|
170
|
-
|
|
170
|
+
// The catalogue hash decides when the install carries one.
|
|
171
|
+
let inst = {};
|
|
171
172
|
try {
|
|
172
|
-
|
|
173
|
+
inst = JSON.parse(fs.readFileSync(path.join(__dirname, "orc-version.json"), "utf8")) || {};
|
|
173
174
|
} catch (_) {}
|
|
174
|
-
if (!lock || (
|
|
175
|
+
if (!lock || (inst.catalog_hash ? lock.catalog_hash !== inst.catalog_hash : (inst.version && lock.orc_version !== inst.version))) return null;
|
|
175
176
|
|
|
176
177
|
const engine = require("./orc-statusline-render.js");
|
|
177
178
|
|
|
@@ -179,6 +180,7 @@ function render(d, tasks) {
|
|
|
179
180
|
for (const b of lock.bindings || []) {
|
|
180
181
|
if (!engine.BINDINGS[b]) return null;
|
|
181
182
|
}
|
|
183
|
+
if (engine.OPS && !opsKnown(prog.lines, engine.OPS)) return null;
|
|
182
184
|
|
|
183
185
|
// Rung 5. A subagent row is ONE line by construction — Claude Code renders
|
|
184
186
|
// one row per task — so the board's three-line shape does not apply and the
|
|
@@ -202,7 +204,7 @@ function render(d, tasks) {
|
|
|
202
204
|
payload: d,
|
|
203
205
|
ledger: {},
|
|
204
206
|
scan: {},
|
|
205
|
-
derived: { verdict: null, reasons: [], version:
|
|
207
|
+
derived: { verdict: null, reasons: [], version: inst.version || null },
|
|
206
208
|
task,
|
|
207
209
|
now,
|
|
208
210
|
cols: Number(process.env.COLUMNS) || 0,
|
|
@@ -220,3 +222,19 @@ function render(d, tasks) {
|
|
|
220
222
|
return null;
|
|
221
223
|
}
|
|
222
224
|
}
|
|
225
|
+
|
|
226
|
+
// Every compiled op must be one the engine walks. A plain stack, no copies.
|
|
227
|
+
function opsKnown(lines, OPS) {
|
|
228
|
+
const st = [];
|
|
229
|
+
for (const l of lines || []) st.push(l.ops || []);
|
|
230
|
+
while (st.length) {
|
|
231
|
+
const list = st.pop();
|
|
232
|
+
for (let i = 0; i < list.length; i++) {
|
|
233
|
+
const o = list[i];
|
|
234
|
+
if (!o || !OPS[o.op]) return false;
|
|
235
|
+
if (o.children) st.push(o.children);
|
|
236
|
+
if (o.cap && o.cap.ops) st.push(o.cap.ops);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ORC statusline — THE WEATHER FETCHER. (v2.0.4)
|
|
6
|
+
*
|
|
7
|
+
* The status-line hook starts this DETACHED and never waits for it:
|
|
8
|
+
* node orc-weather-fetch.js <orcDir> <location> <metric|us>
|
|
9
|
+
* It asks wttr.in once, writes `<orcDir>/weather.json` atomically (a temp file,
|
|
10
|
+
* then a rename) and deletes `<orcDir>/weather.lock`. Any error deletes the
|
|
11
|
+
* lock and exits quietly: the old cache stays as it was. It never prints.
|
|
12
|
+
* Zero dependencies.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SUN = [113];
|
|
16
|
+
const PARTLY = [116];
|
|
17
|
+
const CLOUD = [119, 122];
|
|
18
|
+
const FOG = [143, 248, 260];
|
|
19
|
+
const STORM = [200, 386, 389, 392, 395];
|
|
20
|
+
const RAIN = [176, 263, 266, 281, 284, 293, 296, 299, 302, 305, 308, 311, 314, 353, 356, 359];
|
|
21
|
+
const SNOW = [179, 182, 185, 227, 230, 317, 320, 323, 326, 329, 332, 335, 338, 350, 362, 365, 368, 371, 374, 377];
|
|
22
|
+
|
|
23
|
+
// wttr.in weatherCode → one of the `weather-icon` states. An unknown code is
|
|
24
|
+
// fog when the description names haze/smoke/mist/fog/dust/sand/ash, else cloud.
|
|
25
|
+
function stateOf(code, desc) {
|
|
26
|
+
const n = Number(code);
|
|
27
|
+
if (!Number.isFinite(n)) return null;
|
|
28
|
+
if (SUN.includes(n)) return "sun";
|
|
29
|
+
if (PARTLY.includes(n)) return "partly";
|
|
30
|
+
if (CLOUD.includes(n)) return "cloud";
|
|
31
|
+
if (FOG.includes(n)) return "fog";
|
|
32
|
+
if (STORM.includes(n)) return "storm";
|
|
33
|
+
if (SNOW.includes(n)) return "snow";
|
|
34
|
+
if (RAIN.includes(n)) return "rain";
|
|
35
|
+
if (/haze|smoke|mist|fog|dust|sand|ash/i.test(String(desc || ""))) return "fog";
|
|
36
|
+
return "cloud";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A provider string is printed to a terminal: no control characters (an ESC
|
|
40
|
+
// is a terminal-escape injection), at most 24 characters.
|
|
41
|
+
function clean(s) {
|
|
42
|
+
if (s == null) return null;
|
|
43
|
+
const t = [...String(s).replace(/[\u0000-\u001f\u007f-\u009f]/g, "")].slice(0, 24).join("").trim();
|
|
44
|
+
return t || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function numOr(v) {
|
|
48
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
49
|
+
return Number.isFinite(n) ? n : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The pure half: a parsed wttr.in `j1` body → the cache record, or null.
|
|
53
|
+
function record(body, now) {
|
|
54
|
+
const cur = body && Array.isArray(body["current_condition"]) ? body["current_condition"][0] : null;
|
|
55
|
+
if (!cur) return null;
|
|
56
|
+
const desc = Array.isArray(cur.weatherDesc) && cur.weatherDesc[0] ? cur.weatherDesc[0].value : null;
|
|
57
|
+
return {
|
|
58
|
+
at: now,
|
|
59
|
+
temp_c: numOr(cur.temp_C),
|
|
60
|
+
temp_f: numOr(cur.temp_F),
|
|
61
|
+
state: stateOf(cur.weatherCode, desc),
|
|
62
|
+
desc: clean(desc),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main(argv) {
|
|
67
|
+
const fs = require("fs");
|
|
68
|
+
const path = require("path");
|
|
69
|
+
const orcDir = argv[0];
|
|
70
|
+
if (!orcDir) return;
|
|
71
|
+
const lockFile = path.join(orcDir, "weather.lock");
|
|
72
|
+
const drop = () => {
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(lockFile);
|
|
75
|
+
} catch (_) {}
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const loc = String(argv[1] || "").replace(/[\u0000-\u001f\u007f-\u009f]/g, "").slice(0, 40);
|
|
79
|
+
const url = "https://wttr.in/" + encodeURIComponent(loc) + "?format=j1";
|
|
80
|
+
const req = require("https").get(url, { headers: { "User-Agent": "orc-statusline" }, timeout: 6000 }, (res) => {
|
|
81
|
+
if (res.statusCode !== 200) {
|
|
82
|
+
res.resume();
|
|
83
|
+
drop();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let raw = "";
|
|
87
|
+
res.setEncoding("utf8");
|
|
88
|
+
res.on("data", (c) => {
|
|
89
|
+
raw += c;
|
|
90
|
+
if (raw.length > 1e6) req.destroy();
|
|
91
|
+
});
|
|
92
|
+
res.on("end", () => {
|
|
93
|
+
try {
|
|
94
|
+
const rec = record(JSON.parse(raw), Date.now());
|
|
95
|
+
if (rec) {
|
|
96
|
+
const out = path.join(orcDir, "weather.json");
|
|
97
|
+
const tmp = out + "." + process.pid + ".tmp";
|
|
98
|
+
fs.writeFileSync(tmp, JSON.stringify(rec) + "\n");
|
|
99
|
+
fs.renameSync(tmp, out);
|
|
100
|
+
}
|
|
101
|
+
} catch (_) {}
|
|
102
|
+
drop();
|
|
103
|
+
});
|
|
104
|
+
res.on("error", drop);
|
|
105
|
+
});
|
|
106
|
+
req.on("timeout", () => req.destroy());
|
|
107
|
+
req.on("error", drop);
|
|
108
|
+
} catch (_) {
|
|
109
|
+
drop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (require.main === module) {
|
|
114
|
+
process.on("uncaughtException", () => {
|
|
115
|
+
try {
|
|
116
|
+
require("fs").unlinkSync(require("path").join(process.argv[2] || ".", "weather.lock"));
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
process.exit(0);
|
|
119
|
+
});
|
|
120
|
+
main(process.argv.slice(2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { stateOf, clean, record };
|