clauder-hub 0.1.14 → 0.1.16

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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/server/dist/index.js +72 -14
  3. package/web/dist/assets/{MonacoEditor-CbpiSq9q.js → MonacoEditor-rtQRWVYN.js} +3 -3
  4. package/web/dist/assets/{cssMode-CN9bc7nj.js → cssMode-n1rt6kgo.js} +1 -1
  5. package/web/dist/assets/{freemarker2-CRnvTAds.js → freemarker2-wNWHsgjv.js} +1 -1
  6. package/web/dist/assets/{handlebars-C2kgmAWI.js → handlebars-DFf8L9Df.js} +1 -1
  7. package/web/dist/assets/{html-BDYl1She.js → html-CrDTS1ce.js} +1 -1
  8. package/web/dist/assets/{htmlMode-Cx5zCPn6.js → htmlMode-DY2rVVP8.js} +1 -1
  9. package/web/dist/assets/{index-zwtldSlq.js → index-BeiyXxTM.js} +63 -63
  10. package/web/dist/assets/{index-m9eVnt__.js → index-DjilKjML.js} +1 -1
  11. package/web/dist/assets/index-mLv9JUIK.css +32 -0
  12. package/web/dist/assets/{javascript-vSl2Hjmn.js → javascript-7XefT4TE.js} +1 -1
  13. package/web/dist/assets/{jsonMode-DzNsYGk_.js → jsonMode-CbGU4wwk.js} +1 -1
  14. package/web/dist/assets/{liquid-kBEtoK3z.js → liquid-Bzc0sREQ.js} +1 -1
  15. package/web/dist/assets/{lspLanguageFeatures-FKrlDCvN.js → lspLanguageFeatures-BHAUOAfW.js} +1 -1
  16. package/web/dist/assets/{mdx-DlYho8un.js → mdx-CWLpB9fb.js} +1 -1
  17. package/web/dist/assets/{python-CscaxAmN.js → python-B5FO5i6L.js} +1 -1
  18. package/web/dist/assets/{razor-ML1yBys3.js → razor-CWWty3wH.js} +1 -1
  19. package/web/dist/assets/{tsMode-BMrsa7Gm.js → tsMode-BjWNTcOJ.js} +1 -1
  20. package/web/dist/assets/{typescript-Cs2X5p1b.js → typescript-DPbJAIdT.js} +1 -1
  21. package/web/dist/assets/{workers-B5NJGWxk.js → workers-DV0Cayx_.js} +1 -1
  22. package/web/dist/assets/{xml-CDgfErGy.js → xml-CaOpHlDM.js} +1 -1
  23. package/web/dist/assets/{yaml-Pb8QFqoF.js → yaml-BirwSaTi.js} +1 -1
  24. package/web/dist/index.html +2 -2
  25. package/web/dist/assets/index-R-juGYqF.css +0 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauder-hub",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "A local dashboard that runs, watches and automates the Claude Code sessions of every project on the machine",
5
5
  "license": "MIT",
6
6
  "author": "goldyndevs <goldyndevs@gmail.com>",
@@ -1389,7 +1389,7 @@ var require_ms = __commonJS({
1389
1389
  var require_debug = __commonJS({
1390
1390
  "node_modules/debug/src/debug.js"(exports, module) {
1391
1391
  exports = module.exports = createDebug.debug = createDebug["default"] = createDebug;
1392
- exports.coerce = coerce;
1392
+ exports.coerce = coerce2;
1393
1393
  exports.disable = disable;
1394
1394
  exports.enable = enable;
1395
1395
  exports.enabled = enabled;
@@ -1483,7 +1483,7 @@ var require_debug = __commonJS({
1483
1483
  }
1484
1484
  return false;
1485
1485
  }
1486
- function coerce(val) {
1486
+ function coerce2(val) {
1487
1487
  if (val instanceof Error) return val.stack || val.message;
1488
1488
  return val;
1489
1489
  }
@@ -27216,6 +27216,8 @@ CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status, created_at);
27216
27216
  ensureColumn("channel_threads", "project_id", "TEXT");
27217
27217
  ensureColumn("runs", "resume", "INTEGER NOT NULL DEFAULT 0");
27218
27218
  ensureColumn("runs", "queued_at", "TEXT");
27219
+ ensureColumn("jobs", "params", "TEXT");
27220
+ ensureColumn("runs", "params", "TEXT");
27219
27221
  }
27220
27222
  function now() {
27221
27223
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -36469,7 +36471,7 @@ function currentValue(purpose) {
36469
36471
  switch (purpose.kind) {
36470
36472
  case "edit-job": {
36471
36473
  const job = getJob(purpose.targetId);
36472
- return job && { name: job.name, kind: job.kind, schedule: job.schedule, prompt: job.prompt, category: job.category, presetId: job.presetId, enabled: job.enabled };
36474
+ return job && { name: job.name, kind: job.kind, schedule: job.schedule, prompt: job.prompt, category: job.category, presetId: job.presetId, params: job.params, enabled: job.enabled };
36473
36475
  }
36474
36476
  case "edit-category": {
36475
36477
  const category = listCategories().find((item) => item.id === purpose.targetId);
@@ -36571,6 +36573,13 @@ var init_purposes = __esm({
36571
36573
  },
36572
36574
  { name: "category", type: "string", required: false, description: '"auto" (Claude picks) or a task category name; decides what the run must deliver before it closes. Default "auto".', example: "bugfix" },
36573
36575
  { name: "presetId", type: "string | null", required: false, description: "Toolset preset id from GET /api/presets; null uses the default preset.", example: "null" },
36576
+ {
36577
+ name: "params",
36578
+ type: "array",
36579
+ required: false,
36580
+ description: 'Values the prompt takes as {{name}}: [{ name, label?, type: "number" | "string" | "boolean", default, description? }]. Run now asks for them (defaults filled in), cron runs use the defaults, POST /api/jobs/<id>/run and the webhook take {"params": {...}}; a wrong type is refused. Left out on a PUT, the job keeps its params; [] clears them.',
36581
+ example: '[{ "name": "count", "label": "Episodes", "type": "number", "default": 5 }]'
36582
+ },
36574
36583
  { name: "enabled", type: "boolean", required: false, description: "Default true.", example: "true" }
36575
36584
  ];
36576
36585
  CATEGORY_PARAMS = [
@@ -36872,6 +36881,47 @@ function getJob(id) {
36872
36881
  const row = db.prepare("SELECT * FROM jobs WHERE id = ?").get(id);
36873
36882
  return row ? toJob(row) : null;
36874
36883
  }
36884
+ function checkParams(input) {
36885
+ if (input == null) return [];
36886
+ if (!Array.isArray(input)) throw new Error("params is a list of { name, label?, type, default, description? }.");
36887
+ const seen = /* @__PURE__ */ new Set();
36888
+ return input.map((item) => {
36889
+ const name = String(item?.name ?? "").trim();
36890
+ if (!/^[A-Za-z_]\w*$/.test(name)) throw new Error(`Parameter name '${name}' must be letters, digits and _, not starting with a digit.`);
36891
+ if (RESERVED2.includes(name)) throw new Error(`Parameter name '${name}' is reserved.`);
36892
+ if (seen.has(name)) throw new Error(`Parameter '${name}' is listed twice.`);
36893
+ seen.add(name);
36894
+ if (!PARAM_TYPES.includes(item.type)) throw new Error(`Parameter '${name}': type is number, string or boolean.`);
36895
+ const fallback = item.type === "number" ? 0 : item.type === "boolean" ? false : "";
36896
+ const param = { name, type: item.type, default: coerce({ name, type: item.type }, item.default ?? fallback) };
36897
+ if (item.label) param.label = String(item.label);
36898
+ if (item.description) param.description = String(item.description);
36899
+ return param;
36900
+ });
36901
+ }
36902
+ function coerce(param, value) {
36903
+ if (param.type === "number") {
36904
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
36905
+ if (!Number.isFinite(number)) throw new Error(`Parameter '${param.name}' must be a number, got ${JSON.stringify(value)}.`);
36906
+ return number;
36907
+ }
36908
+ if (param.type === "boolean") {
36909
+ if (value === true || value === "true") return true;
36910
+ if (value === false || value === "false") return false;
36911
+ throw new Error(`Parameter '${param.name}' must be true or false, got ${JSON.stringify(value)}.`);
36912
+ }
36913
+ if (typeof value === "object" && value !== null) throw new Error(`Parameter '${param.name}' must be a string.`);
36914
+ return String(value);
36915
+ }
36916
+ function resolveParams(job, given) {
36917
+ if (given != null && (typeof given !== "object" || Array.isArray(given))) throw new Error("params is an object of name: value.");
36918
+ const values = given ?? {};
36919
+ const unknown = Object.keys(values).filter((name) => !job.params.some((param) => param.name === name));
36920
+ if (unknown.length) throw new Error(`Unknown parameter${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}. This job takes: ${job.params.map((p2) => p2.name).join(", ") || "none"}.`);
36921
+ const result = {};
36922
+ for (const param of job.params) result[param.name] = values[param.name] == null ? param.default : coerce(param, values[param.name]);
36923
+ return result;
36924
+ }
36875
36925
  function saveJob(input) {
36876
36926
  if (!input.name.trim()) throw new Error("A job needs a name.");
36877
36927
  if (!input.prompt.trim()) throw new Error("A job needs a prompt.");
@@ -36885,12 +36935,14 @@ function saveJob(input) {
36885
36935
  }
36886
36936
  }
36887
36937
  const id = input.id ?? nanoid(10);
36888
- const token = getJob(id)?.token ?? nanoid(32);
36938
+ const existing = getJob(id);
36939
+ const params = input.params === void 0 && existing ? existing.params : checkParams(input.params);
36940
+ const token = existing?.token ?? nanoid(32);
36889
36941
  db.prepare(
36890
- `INSERT INTO jobs (id, name, kind, schedule, prompt, category, preset_id, token, enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
36942
+ `INSERT INTO jobs (id, name, kind, schedule, prompt, category, preset_id, params, token, enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
36891
36943
  ON CONFLICT(id) DO UPDATE SET name = excluded.name, kind = excluded.kind, schedule = excluded.schedule, prompt = excluded.prompt,
36892
- category = excluded.category, preset_id = excluded.preset_id, enabled = excluded.enabled, updated_at = excluded.updated_at`
36893
- ).run(id, input.name.trim(), input.kind, schedule, input.prompt, input.category ?? "auto", input.presetId || null, token, input.enabled === false ? 0 : 1, now(), now());
36944
+ category = excluded.category, preset_id = excluded.preset_id, params = excluded.params, enabled = excluded.enabled, updated_at = excluded.updated_at`
36945
+ ).run(id, input.name.trim(), input.kind, schedule, input.prompt, input.category ?? "auto", input.presetId || null, JSON.stringify(params), token, input.enabled === false ? 0 : 1, now(), now());
36894
36946
  schedule_(id);
36895
36947
  broadcast("jobs", {});
36896
36948
  return getJob(id);
@@ -36935,8 +36987,9 @@ function stopScheduler() {
36935
36987
  crons.delete(key);
36936
36988
  }
36937
36989
  }
36938
- function renderJobPrompt(template, payload) {
36990
+ function renderJobPrompt(template, payload, params) {
36939
36991
  const values = { now: now(), payload: JSON.stringify(payload ?? {}, null, 2) };
36992
+ for (const [name, value] of Object.entries(params)) values[name] = String(value);
36940
36993
  const walk = (value, prefix) => {
36941
36994
  if (value && typeof value === "object" && !Array.isArray(value)) {
36942
36995
  for (const [key, inner] of Object.entries(value)) walk(inner, `${prefix}.${key}`);
@@ -36950,15 +37003,17 @@ function listRuns(limit = 100) {
36950
37003
  return db.prepare("SELECT runs.*, jobs.name AS job_name FROM runs LEFT JOIN jobs ON jobs.id = runs.job_id ORDER BY runs.created_at DESC LIMIT ?").all(limit).map(toRun);
36951
37004
  }
36952
37005
  function enqueue(input) {
36953
- const prompt = input.job ? renderJobPrompt(input.job.prompt, input.payload) : input.prompt ?? "";
37006
+ const params = input.job ? resolveParams(input.job, input.params) : {};
37007
+ const prompt = input.job ? renderJobPrompt(input.job.prompt, input.payload, params) : input.prompt ?? "";
36954
37008
  if (!prompt.trim()) throw new Error("Nothing to run: the prompt is empty.");
36955
37009
  const id = nanoid(10);
36956
- db.prepare("INSERT INTO runs (id, job_id, source, prompt, category, status, created_at) VALUES (?, ?, ?, ?, ?, 'queued', ?)").run(
37010
+ db.prepare("INSERT INTO runs (id, job_id, source, prompt, category, status, params, created_at) VALUES (?, ?, ?, ?, ?, 'queued', ?, ?)").run(
36957
37011
  id,
36958
37012
  input.job?.id ?? null,
36959
37013
  input.source,
36960
37014
  prompt,
36961
37015
  input.category ?? input.job?.category ?? "auto",
37016
+ Object.keys(params).length ? JSON.stringify(params) : null,
36962
37017
  now()
36963
37018
  );
36964
37019
  if (input.job) db.prepare("UPDATE jobs SET last_run_at = ? WHERE id = ?").run(now(), input.job.id);
@@ -37073,7 +37128,7 @@ function recoverRuns() {
37073
37128
  db.prepare("UPDATE interactions SET status = 'closed', answered_by = 'hub restart' WHERE status = 'open'").run();
37074
37129
  return stranded;
37075
37130
  }
37076
- var crons, cronKey, toJob, toRun, IN_LINE, advancing, waitingForClaude;
37131
+ var crons, cronKey, toJob, PARAM_TYPES, RESERVED2, toRun, IN_LINE, advancing, waitingForClaude;
37077
37132
  var init_jobs = __esm({
37078
37133
  "server/src/jobs.ts"() {
37079
37134
  "use strict";
@@ -37098,12 +37153,15 @@ var init_jobs = __esm({
37098
37153
  prompt: row.prompt,
37099
37154
  category: row.category,
37100
37155
  presetId: row.preset_id,
37156
+ params: parseJson(row.params, []),
37101
37157
  token: row.token,
37102
37158
  enabled: Boolean(row.enabled),
37103
37159
  lastRunAt: row.last_run_at,
37104
37160
  nextRunAt: crons.get(cronKey(row.id))?.nextRun()?.toISOString() ?? null,
37105
37161
  createdAt: row.created_at
37106
37162
  });
37163
+ PARAM_TYPES = ["number", "string", "boolean"];
37164
+ RESERVED2 = ["now", "payload"];
37107
37165
  toRun = (row) => ({
37108
37166
  id: row.id,
37109
37167
  jobId: row.job_id,
@@ -37114,6 +37172,7 @@ var init_jobs = __esm({
37114
37172
  status: row.status,
37115
37173
  sessionId: row.session_id,
37116
37174
  resume: Boolean(row.resume),
37175
+ params: parseJson(row.params, null),
37117
37176
  createdAt: row.created_at,
37118
37177
  startedAt: row.started_at,
37119
37178
  finishedAt: row.finished_at
@@ -37887,7 +37946,6 @@ hubApi.get(
37887
37946
  const muted = new Set(mutedProjects());
37888
37947
  return acrossProjects((info) => {
37889
37948
  const sessions2 = listSessions();
37890
- for (const session of sessions2) if (session.live) openSessionsAdd(session.id);
37891
37949
  const byId = new Map(sessions2.map((session) => [session.id, session]));
37892
37950
  const pick = (session) => ({
37893
37951
  id: session.id,
@@ -38177,7 +38235,7 @@ api.post(
38177
38235
  wrap2((req) => {
38178
38236
  const job = getJob(req.params.id);
38179
38237
  if (!job) throw new Error("No such job.");
38180
- return enqueue({ job, source: "manual", payload: req.body?.payload ?? {} });
38238
+ return enqueue({ job, source: "manual", payload: req.body?.payload ?? {}, params: req.body?.params });
38181
38239
  })
38182
38240
  );
38183
38241
  api.post(
@@ -38193,7 +38251,7 @@ api.post(
38193
38251
  res.status(409).json({ error: "This trigger is disabled." });
38194
38252
  return;
38195
38253
  }
38196
- const { run: run4, position } = enqueue({ job, source: "webhook", payload: req.body ?? {} });
38254
+ const { run: run4, position } = enqueue({ job, source: "webhook", payload: req.body ?? {}, params: req.body?.params });
38197
38255
  return { runId: run4.id, status: run4.status, position };
38198
38256
  })
38199
38257
  );