@inteeka/task-cli 0.1.2 → 0.1.3

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/dist/cli.js CHANGED
@@ -1706,12 +1706,61 @@ function autopilotExitCode(code, status) {
1706
1706
 
1707
1707
  // src/scan/llm.ts
1708
1708
  import { spawn as spawn2 } from "child_process";
1709
+ import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
1710
+ import { homedir as homedir4 } from "os";
1711
+ import { join as join6 } from "path";
1712
+ var FIX_PROMPT_JSON_SCHEMA = {
1713
+ type: "object",
1714
+ required: ["summary", "suspected_files", "proposed_changes", "confidence"],
1715
+ additionalProperties: false,
1716
+ properties: {
1717
+ summary: { type: "string", minLength: 1, maxLength: 2e3 },
1718
+ suspected_files: {
1719
+ type: "array",
1720
+ maxItems: 20,
1721
+ items: { type: "string", minLength: 1, maxLength: 500 }
1722
+ },
1723
+ proposed_changes: {
1724
+ type: "array",
1725
+ maxItems: 20,
1726
+ items: {
1727
+ type: "object",
1728
+ required: ["file", "intent"],
1729
+ additionalProperties: false,
1730
+ properties: {
1731
+ file: { type: "string", minLength: 1, maxLength: 500 },
1732
+ intent: { type: "string", minLength: 1, maxLength: 2e3 },
1733
+ rationale: { type: "string", minLength: 1, maxLength: 2e3 }
1734
+ }
1735
+ }
1736
+ },
1737
+ investigation: {
1738
+ type: "object",
1739
+ additionalProperties: false,
1740
+ properties: {
1741
+ route_match: { type: "string", maxLength: 500 },
1742
+ auth_findings: { type: "string", maxLength: 1500 },
1743
+ middleware_findings: { type: "string", maxLength: 1500 },
1744
+ redirect_findings: { type: "string", maxLength: 1500 },
1745
+ ui_ux_findings: { type: "string", maxLength: 1500 },
1746
+ browser_findings: { type: "string", maxLength: 1500 },
1747
+ api_findings: { type: "string", maxLength: 1500 },
1748
+ data_findings: { type: "string", maxLength: 1500 }
1749
+ }
1750
+ },
1751
+ risk_notes: { type: "string", maxLength: 2e3 },
1752
+ confidence: { type: "string", enum: ["low", "medium", "high"] }
1753
+ }
1754
+ };
1709
1755
  var LlmGenerationError = class extends Error {
1710
- constructor(reason, message) {
1756
+ constructor(reason, message, debugLogPath) {
1711
1757
  super(message);
1712
1758
  this.reason = reason;
1759
+ if (debugLogPath !== void 0) this.debugLogPath = debugLogPath;
1713
1760
  }
1761
+ debugLogPath;
1714
1762
  };
1763
+ var DEBUG = process.env["TASK_SCAN_DEBUG"] === "1";
1715
1764
  async function generateFixPromptJson(args) {
1716
1765
  const claude = args.claudePath ?? "claude";
1717
1766
  const userPrompt = [
@@ -1719,19 +1768,21 @@ async function generateFixPromptJson(args) {
1719
1768
  "",
1720
1769
  args.ticketBlock,
1721
1770
  "",
1722
- `Return JSON only, matching this shape: ${args.outputSchemaHint}`,
1723
- "Do not include explanatory prose, markdown fences, or code commentary \u2014 just the JSON object."
1771
+ "Return JSON only matching the supplied schema. Do not include explanatory prose, markdown fences, or commentary."
1724
1772
  ].join("\n");
1725
1773
  const cliArgs = [
1726
1774
  "--print",
1727
- "--allowedTools",
1775
+ "--bare",
1776
+ "--output-format",
1777
+ "json",
1778
+ "--tools",
1728
1779
  "",
1729
1780
  "--system-prompt",
1730
1781
  args.systemPrompt,
1731
1782
  "--model",
1732
1783
  args.modelId,
1733
- "--max-turns",
1734
- "1"
1784
+ "--json-schema",
1785
+ JSON.stringify(FIX_PROMPT_JSON_SCHEMA)
1735
1786
  ];
1736
1787
  return new Promise((resolve2, reject) => {
1737
1788
  let child;
@@ -1756,26 +1807,36 @@ async function generateFixPromptJson(args) {
1756
1807
  child.on("error", (err) => {
1757
1808
  reject(new LlmGenerationError("spawn_failed", err.message));
1758
1809
  });
1759
- child.on("close", (code, signal) => {
1810
+ child.on("close", async (code, signal) => {
1760
1811
  if (signal === "SIGTERM" || signal === "SIGKILL") {
1761
1812
  reject(new LlmGenerationError("aborted", "claude was aborted"));
1762
1813
  return;
1763
1814
  }
1764
1815
  if (code !== 0) {
1816
+ const dump = await maybeDumpDebug(args.ticketId, stdoutBuf, stderrBuf);
1765
1817
  reject(
1766
1818
  new LlmGenerationError(
1767
1819
  "non_zero_exit",
1768
- `claude exited with code ${code}: ${stderrBuf.trim().slice(0, 1e3)}`
1820
+ `claude exited with code ${code}${dump ? ` (raw output saved to ${dump})` : ""}: ${stderrBuf.trim().slice(0, 600)}`,
1821
+ dump ?? void 0
1769
1822
  )
1770
1823
  );
1771
1824
  return;
1772
1825
  }
1773
- const parsed = parseStructuredJson(stdoutBuf);
1826
+ const innerText = extractEnvelopeText(stdoutBuf);
1827
+ const parsed = parseStructuredJson(innerText);
1774
1828
  if (!parsed) {
1775
- reject(new LlmGenerationError("no_json", "No JSON object found in claude output"));
1829
+ const dump = await maybeDumpDebug(args.ticketId, stdoutBuf, stderrBuf);
1830
+ reject(
1831
+ new LlmGenerationError(
1832
+ "no_json",
1833
+ `No JSON object in claude output${dump ? ` (raw output saved to ${dump})` : ""}`,
1834
+ dump ?? void 0
1835
+ )
1836
+ );
1776
1837
  return;
1777
1838
  }
1778
- const tokens = estimateTokens(userPrompt, stdoutBuf);
1839
+ const tokens = readEnvelopeTokens(stdoutBuf, userPrompt, innerText);
1779
1840
  resolve2({
1780
1841
  structured: parsed,
1781
1842
  rawText: stdoutBuf,
@@ -1787,6 +1848,46 @@ async function generateFixPromptJson(args) {
1787
1848
  child.stdin?.end();
1788
1849
  });
1789
1850
  }
1851
+ function extractEnvelopeText(raw) {
1852
+ const trimmed = raw.trim();
1853
+ if (!trimmed) return raw;
1854
+ try {
1855
+ const env = JSON.parse(trimmed);
1856
+ if (typeof env.result === "string") return env.result;
1857
+ } catch {
1858
+ }
1859
+ return raw;
1860
+ }
1861
+ function readEnvelopeTokens(raw, userPrompt, innerText) {
1862
+ try {
1863
+ const env = JSON.parse(raw.trim());
1864
+ const inTok = env.input_tokens ?? env.usage?.input_tokens;
1865
+ const outTok = env.output_tokens ?? env.usage?.output_tokens;
1866
+ if (typeof inTok === "number" && typeof outTok === "number") {
1867
+ return { input: inTok, output: outTok };
1868
+ }
1869
+ } catch {
1870
+ }
1871
+ return {
1872
+ input: Math.max(1, Math.round(userPrompt.length / 4)),
1873
+ output: Math.max(1, Math.round(innerText.length / 4))
1874
+ };
1875
+ }
1876
+ async function maybeDumpDebug(ticketId, stdout, stderr) {
1877
+ if (!DEBUG && stdout.length === 0 && stderr.length === 0) return null;
1878
+ try {
1879
+ const dir = join6(homedir4(), ".cache", "task", "scan-debug");
1880
+ await mkdir5(dir, { recursive: true });
1881
+ const path = join6(dir, `${ticketId}-${Date.now()}.log`);
1882
+ await writeFile6(
1883
+ path,
1884
+ ["## ticket_id", ticketId, "", "## stdout", stdout, "", "## stderr", stderr].join("\n")
1885
+ );
1886
+ return path;
1887
+ } catch {
1888
+ return null;
1889
+ }
1890
+ }
1790
1891
  function parseStructuredJson(raw) {
1791
1892
  const trimmed = raw.trim();
1792
1893
  if (!trimmed) return null;
@@ -1840,12 +1941,6 @@ function parseStructuredJson(raw) {
1840
1941
  }
1841
1942
  return null;
1842
1943
  }
1843
- function estimateTokens(input, output) {
1844
- return {
1845
- input: Math.max(1, Math.round(input.length / 4)),
1846
- output: Math.max(1, Math.round(output.length / 4))
1847
- };
1848
- }
1849
1944
 
1850
1945
  // src/commands/scan.ts
1851
1946
  function registerScan(program2) {
@@ -1984,11 +2079,12 @@ ${c.bold(`${project.organisation_slug}/${project.project_slug}`)} ${c.dim(`(${pr
1984
2079
  const spinner = silent ? null : ora2(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)}`).start();
1985
2080
  try {
1986
2081
  const generated = await safeGenerate(ticket, claudePath);
1987
- if (!generated) {
2082
+ if (!generated.ok) {
1988
2083
  agg.skipped += 1;
1989
- spinner?.warn(
1990
- `#${ticket.sequence_number} skipped (no JSON from claude) \u2014 calling abort`
1991
- );
2084
+ const debugSuffix = generated.debugLogPath ? ` \u2014 debug: ${generated.debugLogPath}` : " \u2014 re-run with TASK_SCAN_DEBUG=1 to capture raw output";
2085
+ spinner?.warn(`#${ticket.sequence_number} skipped (${generated.reason})${debugSuffix}`);
2086
+ await api.abort(skillToken, [ticket.ticket_id]).catch(() => void 0);
2087
+ inFlight.delete(ticket.ticket_id);
1992
2088
  continue;
1993
2089
  }
1994
2090
  const result = await api.submit({
@@ -2047,12 +2143,18 @@ async function safeGenerate(ticket, claudePath) {
2047
2143
  ticketBlock: ticket.ticket_block,
2048
2144
  outputSchemaHint: ticket.output_schema_hint,
2049
2145
  modelId: ticket.model_id,
2146
+ ticketId: ticket.ticket_id,
2050
2147
  ...claudePath ? { claudePath } : {}
2051
2148
  });
2052
- return out;
2149
+ return { ok: true, ...out };
2053
2150
  } catch (err) {
2054
2151
  if (err instanceof LlmGenerationError) {
2055
- return null;
2152
+ const result = {
2153
+ ok: false,
2154
+ reason: `${err.reason}: ${err.message.slice(0, 200)}`
2155
+ };
2156
+ if (err.debugLogPath) result.debugLogPath = err.debugLogPath;
2157
+ return result;
2056
2158
  }
2057
2159
  throw err;
2058
2160
  }
@@ -2070,9 +2172,9 @@ import { randomUUID as randomUUID3 } from "crypto";
2070
2172
  import { platform as platform2 } from "os";
2071
2173
 
2072
2174
  // src/scheduler/launchd.ts
2073
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile6, unlink as unlink3, readdir } from "fs/promises";
2074
- import { homedir as homedir4 } from "os";
2075
- import { join as join6 } from "path";
2175
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile7, unlink as unlink3, readdir } from "fs/promises";
2176
+ import { homedir as homedir5 } from "os";
2177
+ import { join as join7 } from "path";
2076
2178
  import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
2077
2179
 
2078
2180
  // src/scheduler/cron-translate.ts
@@ -2174,14 +2276,14 @@ function expandField(field, min, max) {
2174
2276
  }
2175
2277
 
2176
2278
  // src/scheduler/launchd.ts
2177
- var PLIST_DIR = join6(homedir4(), "Library", "LaunchAgents");
2279
+ var PLIST_DIR = join7(homedir5(), "Library", "LaunchAgents");
2178
2280
  var LABEL_PREFIX = "com.inteeka.task.cli.";
2179
2281
  var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
2180
2282
  function plistPath(id) {
2181
2283
  if (!SAFE_ID_RE.test(id) || id.includes("..")) {
2182
2284
  throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
2183
2285
  }
2184
- return join6(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
2286
+ return join7(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
2185
2287
  }
2186
2288
  function buildPlist(entry) {
2187
2289
  const calendars = translateToLaunchd(entry.cron);
@@ -2217,9 +2319,9 @@ ${fields}
2217
2319
  ` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
2218
2320
  ` </dict>`,
2219
2321
  ` <key>StandardOutPath</key>`,
2220
- ` <string>${escapeXml(join6(homedir4(), ".cache", "task", "launchd-stdout.log"))}</string>`,
2322
+ ` <string>${escapeXml(join7(homedir5(), ".cache", "task", "launchd-stdout.log"))}</string>`,
2221
2323
  ` <key>StandardErrorPath</key>`,
2222
- ` <string>${escapeXml(join6(homedir4(), ".cache", "task", "launchd-stderr.log"))}</string>`,
2324
+ ` <string>${escapeXml(join7(homedir5(), ".cache", "task", "launchd-stderr.log"))}</string>`,
2223
2325
  !entry.enabled ? ` <key>Disabled</key>
2224
2326
  <true/>` : "",
2225
2327
  "</dict>",
@@ -2237,9 +2339,9 @@ function bootstrapDomain() {
2237
2339
  }
2238
2340
  var launchdAdapter = {
2239
2341
  async upsert(entry) {
2240
- await mkdir5(PLIST_DIR, { recursive: true });
2342
+ await mkdir6(PLIST_DIR, { recursive: true });
2241
2343
  const path = plistPath(entry.id);
2242
- await writeFile6(path, buildPlist(entry));
2344
+ await writeFile7(path, buildPlist(entry));
2243
2345
  try {
2244
2346
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2245
2347
  } catch {
@@ -2268,7 +2370,7 @@ var launchdAdapter = {
2268
2370
  for (const file of ours) {
2269
2371
  const id = file.slice(LABEL_PREFIX.length, -".plist".length);
2270
2372
  try {
2271
- const xml = await readFile5(join6(PLIST_DIR, file), "utf8");
2373
+ const xml = await readFile5(join7(PLIST_DIR, file), "utf8");
2272
2374
  const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
2273
2375
  const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
2274
2376
  const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
@@ -2314,7 +2416,7 @@ var launchdAdapter = {
2314
2416
  }
2315
2417
  if (enabled) {
2316
2418
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
2317
- await writeFile6(path, xml);
2419
+ await writeFile7(path, xml);
2318
2420
  try {
2319
2421
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2320
2422
  } catch {
@@ -2326,7 +2428,7 @@ var launchdAdapter = {
2326
2428
  "</dict>\n</plist>",
2327
2429
  " <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
2328
2430
  );
2329
- await writeFile6(path, xml);
2431
+ await writeFile7(path, xml);
2330
2432
  }
2331
2433
  try {
2332
2434
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
@@ -2679,10 +2781,10 @@ var unsupportedAdapter = {
2679
2781
  };
2680
2782
 
2681
2783
  // src/scheduler/registry.ts
2682
- import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
2683
- import { homedir as homedir5 } from "os";
2684
- import { dirname as dirname4, join as join7 } from "path";
2685
- var REGISTRY_PATH = join7(homedir5(), ".config", "task", "schedules.json");
2784
+ import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile8 } from "fs/promises";
2785
+ import { homedir as homedir6 } from "os";
2786
+ import { dirname as dirname4, join as join8 } from "path";
2787
+ var REGISTRY_PATH = join8(homedir6(), ".config", "task", "schedules.json");
2686
2788
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2687
2789
  function looksLikeRegistryRow(value) {
2688
2790
  if (!value || typeof value !== "object") return false;
@@ -2702,8 +2804,8 @@ async function readRegistry() {
2702
2804
  }
2703
2805
  }
2704
2806
  async function writeRegistry(rows) {
2705
- await mkdir6(dirname4(REGISTRY_PATH), { recursive: true });
2706
- await writeFile7(REGISTRY_PATH, JSON.stringify(rows, null, 2));
2807
+ await mkdir7(dirname4(REGISTRY_PATH), { recursive: true });
2808
+ await writeFile8(REGISTRY_PATH, JSON.stringify(rows, null, 2));
2707
2809
  }
2708
2810
  async function upsertRegistry(row) {
2709
2811
  if (!UUID_RE.test(row.id)) {
@@ -2943,8 +3045,8 @@ function stripAnsi(s) {
2943
3045
 
2944
3046
  // src/commands/runs.ts
2945
3047
  import { readFile as readFile7 } from "fs/promises";
2946
- import { homedir as homedir6 } from "os";
2947
- import { join as join8 } from "path";
3048
+ import { homedir as homedir7 } from "os";
3049
+ import { join as join9 } from "path";
2948
3050
  function registerRuns(program2) {
2949
3051
  const cmd = program2.command("runs").description("Inspect agentic CLI run history");
2950
3052
  cmd.command("list").description("List recent runs").option("--limit <n>", "Max rows", "50").option("--ticket <id>", "Filter by ticket").option("--schedule <id>", "Filter by schedule").action(async (opts) => {
@@ -2973,7 +3075,7 @@ function registerRuns(program2) {
2973
3075
  process.stdout.write(JSON.stringify(row, null, 2) + "\n");
2974
3076
  });
2975
3077
  cmd.command("logs <id>").description("Show captured agent output for a run, if available").action(async (id) => {
2976
- const localPath = join8(homedir6(), ".cache", "task", "runs", `${id}.log`);
3078
+ const localPath = join9(homedir7(), ".cache", "task", "runs", `${id}.log`);
2977
3079
  try {
2978
3080
  const text = await readFile7(localPath, "utf8");
2979
3081
  process.stdout.write(text);
@@ -3125,7 +3227,7 @@ function checkBinary(name, command) {
3125
3227
  }
3126
3228
 
3127
3229
  // src/commands/version.ts
3128
- var CLI_VERSION = true ? "0.1.2" : "0.0.0-dev";
3230
+ var CLI_VERSION = true ? "0.1.3" : "0.0.0-dev";
3129
3231
  function registerVersion(program2) {
3130
3232
  program2.command("version").description("Print the CLI version").action(() => {
3131
3233
  process.stdout.write(CLI_VERSION + "\n");