@inteeka/task-cli 0.1.2 → 0.1.4

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,20 @@ 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
+ "--output-format",
1776
+ "json",
1777
+ "--tools",
1728
1778
  "",
1729
1779
  "--system-prompt",
1730
1780
  args.systemPrompt,
1731
1781
  "--model",
1732
1782
  args.modelId,
1733
- "--max-turns",
1734
- "1"
1783
+ "--json-schema",
1784
+ JSON.stringify(FIX_PROMPT_JSON_SCHEMA)
1735
1785
  ];
1736
1786
  return new Promise((resolve2, reject) => {
1737
1787
  let child;
@@ -1756,26 +1806,48 @@ async function generateFixPromptJson(args) {
1756
1806
  child.on("error", (err) => {
1757
1807
  reject(new LlmGenerationError("spawn_failed", err.message));
1758
1808
  });
1759
- child.on("close", (code, signal) => {
1809
+ child.on("close", async (code, signal) => {
1760
1810
  if (signal === "SIGTERM" || signal === "SIGKILL") {
1761
1811
  reject(new LlmGenerationError("aborted", "claude was aborted"));
1762
1812
  return;
1763
1813
  }
1814
+ const authFailure = detectAuthFailure(stdoutBuf);
1815
+ if (authFailure) {
1816
+ const dump = await maybeDumpDebug(args.ticketId, stdoutBuf, stderrBuf);
1817
+ reject(
1818
+ new LlmGenerationError(
1819
+ "non_zero_exit",
1820
+ `Claude is not logged in. Run \`claude /login\` once on this machine, then re-run \`task scan\`.${dump ? ` (raw output: ${dump})` : ""}`,
1821
+ dump ?? void 0
1822
+ )
1823
+ );
1824
+ return;
1825
+ }
1764
1826
  if (code !== 0) {
1827
+ const dump = await maybeDumpDebug(args.ticketId, stdoutBuf, stderrBuf);
1765
1828
  reject(
1766
1829
  new LlmGenerationError(
1767
1830
  "non_zero_exit",
1768
- `claude exited with code ${code}: ${stderrBuf.trim().slice(0, 1e3)}`
1831
+ `claude exited with code ${code}${dump ? ` (raw output saved to ${dump})` : ""}: ${stderrBuf.trim().slice(0, 600)}`,
1832
+ dump ?? void 0
1769
1833
  )
1770
1834
  );
1771
1835
  return;
1772
1836
  }
1773
- const parsed = parseStructuredJson(stdoutBuf);
1837
+ const innerText = extractEnvelopeText(stdoutBuf);
1838
+ const parsed = parseStructuredJson(innerText);
1774
1839
  if (!parsed) {
1775
- reject(new LlmGenerationError("no_json", "No JSON object found in claude output"));
1840
+ const dump = await maybeDumpDebug(args.ticketId, stdoutBuf, stderrBuf);
1841
+ reject(
1842
+ new LlmGenerationError(
1843
+ "no_json",
1844
+ `No JSON object in claude output${dump ? ` (raw output saved to ${dump})` : ""}`,
1845
+ dump ?? void 0
1846
+ )
1847
+ );
1776
1848
  return;
1777
1849
  }
1778
- const tokens = estimateTokens(userPrompt, stdoutBuf);
1850
+ const tokens = readEnvelopeTokens(stdoutBuf, userPrompt, innerText);
1779
1851
  resolve2({
1780
1852
  structured: parsed,
1781
1853
  rawText: stdoutBuf,
@@ -1787,6 +1859,59 @@ async function generateFixPromptJson(args) {
1787
1859
  child.stdin?.end();
1788
1860
  });
1789
1861
  }
1862
+ function detectAuthFailure(raw) {
1863
+ const trimmed = raw.trim();
1864
+ if (!trimmed) return false;
1865
+ try {
1866
+ const env = JSON.parse(trimmed);
1867
+ if (env.is_error === true && typeof env.result === "string") {
1868
+ const msg = env.result.toLowerCase();
1869
+ return msg.includes("not logged in") || msg.includes("please run /login") || msg.includes("please log in");
1870
+ }
1871
+ } catch {
1872
+ }
1873
+ return false;
1874
+ }
1875
+ function extractEnvelopeText(raw) {
1876
+ const trimmed = raw.trim();
1877
+ if (!trimmed) return raw;
1878
+ try {
1879
+ const env = JSON.parse(trimmed);
1880
+ if (typeof env.result === "string") return env.result;
1881
+ } catch {
1882
+ }
1883
+ return raw;
1884
+ }
1885
+ function readEnvelopeTokens(raw, userPrompt, innerText) {
1886
+ try {
1887
+ const env = JSON.parse(raw.trim());
1888
+ const inTok = env.input_tokens ?? env.usage?.input_tokens;
1889
+ const outTok = env.output_tokens ?? env.usage?.output_tokens;
1890
+ if (typeof inTok === "number" && typeof outTok === "number") {
1891
+ return { input: inTok, output: outTok };
1892
+ }
1893
+ } catch {
1894
+ }
1895
+ return {
1896
+ input: Math.max(1, Math.round(userPrompt.length / 4)),
1897
+ output: Math.max(1, Math.round(innerText.length / 4))
1898
+ };
1899
+ }
1900
+ async function maybeDumpDebug(ticketId, stdout, stderr) {
1901
+ if (!DEBUG && stdout.length === 0 && stderr.length === 0) return null;
1902
+ try {
1903
+ const dir = join6(homedir4(), ".cache", "task", "scan-debug");
1904
+ await mkdir5(dir, { recursive: true });
1905
+ const path = join6(dir, `${ticketId}-${Date.now()}.log`);
1906
+ await writeFile6(
1907
+ path,
1908
+ ["## ticket_id", ticketId, "", "## stdout", stdout, "", "## stderr", stderr].join("\n")
1909
+ );
1910
+ return path;
1911
+ } catch {
1912
+ return null;
1913
+ }
1914
+ }
1790
1915
  function parseStructuredJson(raw) {
1791
1916
  const trimmed = raw.trim();
1792
1917
  if (!trimmed) return null;
@@ -1840,12 +1965,6 @@ function parseStructuredJson(raw) {
1840
1965
  }
1841
1966
  return null;
1842
1967
  }
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
1968
 
1850
1969
  // src/commands/scan.ts
1851
1970
  function registerScan(program2) {
@@ -1984,11 +2103,12 @@ ${c.bold(`${project.organisation_slug}/${project.project_slug}`)} ${c.dim(`(${pr
1984
2103
  const spinner = silent ? null : ora2(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)}`).start();
1985
2104
  try {
1986
2105
  const generated = await safeGenerate(ticket, claudePath);
1987
- if (!generated) {
2106
+ if (!generated.ok) {
1988
2107
  agg.skipped += 1;
1989
- spinner?.warn(
1990
- `#${ticket.sequence_number} skipped (no JSON from claude) \u2014 calling abort`
1991
- );
2108
+ const debugSuffix = generated.debugLogPath ? ` \u2014 debug: ${generated.debugLogPath}` : " \u2014 re-run with TASK_SCAN_DEBUG=1 to capture raw output";
2109
+ spinner?.warn(`#${ticket.sequence_number} skipped (${generated.reason})${debugSuffix}`);
2110
+ await api.abort(skillToken, [ticket.ticket_id]).catch(() => void 0);
2111
+ inFlight.delete(ticket.ticket_id);
1992
2112
  continue;
1993
2113
  }
1994
2114
  const result = await api.submit({
@@ -2047,12 +2167,18 @@ async function safeGenerate(ticket, claudePath) {
2047
2167
  ticketBlock: ticket.ticket_block,
2048
2168
  outputSchemaHint: ticket.output_schema_hint,
2049
2169
  modelId: ticket.model_id,
2170
+ ticketId: ticket.ticket_id,
2050
2171
  ...claudePath ? { claudePath } : {}
2051
2172
  });
2052
- return out;
2173
+ return { ok: true, ...out };
2053
2174
  } catch (err) {
2054
2175
  if (err instanceof LlmGenerationError) {
2055
- return null;
2176
+ const result = {
2177
+ ok: false,
2178
+ reason: `${err.reason}: ${err.message.slice(0, 200)}`
2179
+ };
2180
+ if (err.debugLogPath) result.debugLogPath = err.debugLogPath;
2181
+ return result;
2056
2182
  }
2057
2183
  throw err;
2058
2184
  }
@@ -2070,9 +2196,9 @@ import { randomUUID as randomUUID3 } from "crypto";
2070
2196
  import { platform as platform2 } from "os";
2071
2197
 
2072
2198
  // 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";
2199
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile7, unlink as unlink3, readdir } from "fs/promises";
2200
+ import { homedir as homedir5 } from "os";
2201
+ import { join as join7 } from "path";
2076
2202
  import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
2077
2203
 
2078
2204
  // src/scheduler/cron-translate.ts
@@ -2174,14 +2300,14 @@ function expandField(field, min, max) {
2174
2300
  }
2175
2301
 
2176
2302
  // src/scheduler/launchd.ts
2177
- var PLIST_DIR = join6(homedir4(), "Library", "LaunchAgents");
2303
+ var PLIST_DIR = join7(homedir5(), "Library", "LaunchAgents");
2178
2304
  var LABEL_PREFIX = "com.inteeka.task.cli.";
2179
2305
  var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
2180
2306
  function plistPath(id) {
2181
2307
  if (!SAFE_ID_RE.test(id) || id.includes("..")) {
2182
2308
  throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
2183
2309
  }
2184
- return join6(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
2310
+ return join7(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
2185
2311
  }
2186
2312
  function buildPlist(entry) {
2187
2313
  const calendars = translateToLaunchd(entry.cron);
@@ -2217,9 +2343,9 @@ ${fields}
2217
2343
  ` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
2218
2344
  ` </dict>`,
2219
2345
  ` <key>StandardOutPath</key>`,
2220
- ` <string>${escapeXml(join6(homedir4(), ".cache", "task", "launchd-stdout.log"))}</string>`,
2346
+ ` <string>${escapeXml(join7(homedir5(), ".cache", "task", "launchd-stdout.log"))}</string>`,
2221
2347
  ` <key>StandardErrorPath</key>`,
2222
- ` <string>${escapeXml(join6(homedir4(), ".cache", "task", "launchd-stderr.log"))}</string>`,
2348
+ ` <string>${escapeXml(join7(homedir5(), ".cache", "task", "launchd-stderr.log"))}</string>`,
2223
2349
  !entry.enabled ? ` <key>Disabled</key>
2224
2350
  <true/>` : "",
2225
2351
  "</dict>",
@@ -2237,9 +2363,9 @@ function bootstrapDomain() {
2237
2363
  }
2238
2364
  var launchdAdapter = {
2239
2365
  async upsert(entry) {
2240
- await mkdir5(PLIST_DIR, { recursive: true });
2366
+ await mkdir6(PLIST_DIR, { recursive: true });
2241
2367
  const path = plistPath(entry.id);
2242
- await writeFile6(path, buildPlist(entry));
2368
+ await writeFile7(path, buildPlist(entry));
2243
2369
  try {
2244
2370
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2245
2371
  } catch {
@@ -2268,7 +2394,7 @@ var launchdAdapter = {
2268
2394
  for (const file of ours) {
2269
2395
  const id = file.slice(LABEL_PREFIX.length, -".plist".length);
2270
2396
  try {
2271
- const xml = await readFile5(join6(PLIST_DIR, file), "utf8");
2397
+ const xml = await readFile5(join7(PLIST_DIR, file), "utf8");
2272
2398
  const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
2273
2399
  const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
2274
2400
  const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
@@ -2314,7 +2440,7 @@ var launchdAdapter = {
2314
2440
  }
2315
2441
  if (enabled) {
2316
2442
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
2317
- await writeFile6(path, xml);
2443
+ await writeFile7(path, xml);
2318
2444
  try {
2319
2445
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2320
2446
  } catch {
@@ -2326,7 +2452,7 @@ var launchdAdapter = {
2326
2452
  "</dict>\n</plist>",
2327
2453
  " <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
2328
2454
  );
2329
- await writeFile6(path, xml);
2455
+ await writeFile7(path, xml);
2330
2456
  }
2331
2457
  try {
2332
2458
  execFileSync5("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
@@ -2679,10 +2805,10 @@ var unsupportedAdapter = {
2679
2805
  };
2680
2806
 
2681
2807
  // 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");
2808
+ import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile8 } from "fs/promises";
2809
+ import { homedir as homedir6 } from "os";
2810
+ import { dirname as dirname4, join as join8 } from "path";
2811
+ var REGISTRY_PATH = join8(homedir6(), ".config", "task", "schedules.json");
2686
2812
  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
2813
  function looksLikeRegistryRow(value) {
2688
2814
  if (!value || typeof value !== "object") return false;
@@ -2702,8 +2828,8 @@ async function readRegistry() {
2702
2828
  }
2703
2829
  }
2704
2830
  async function writeRegistry(rows) {
2705
- await mkdir6(dirname4(REGISTRY_PATH), { recursive: true });
2706
- await writeFile7(REGISTRY_PATH, JSON.stringify(rows, null, 2));
2831
+ await mkdir7(dirname4(REGISTRY_PATH), { recursive: true });
2832
+ await writeFile8(REGISTRY_PATH, JSON.stringify(rows, null, 2));
2707
2833
  }
2708
2834
  async function upsertRegistry(row) {
2709
2835
  if (!UUID_RE.test(row.id)) {
@@ -2943,8 +3069,8 @@ function stripAnsi(s) {
2943
3069
 
2944
3070
  // src/commands/runs.ts
2945
3071
  import { readFile as readFile7 } from "fs/promises";
2946
- import { homedir as homedir6 } from "os";
2947
- import { join as join8 } from "path";
3072
+ import { homedir as homedir7 } from "os";
3073
+ import { join as join9 } from "path";
2948
3074
  function registerRuns(program2) {
2949
3075
  const cmd = program2.command("runs").description("Inspect agentic CLI run history");
2950
3076
  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 +3099,7 @@ function registerRuns(program2) {
2973
3099
  process.stdout.write(JSON.stringify(row, null, 2) + "\n");
2974
3100
  });
2975
3101
  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`);
3102
+ const localPath = join9(homedir7(), ".cache", "task", "runs", `${id}.log`);
2977
3103
  try {
2978
3104
  const text = await readFile7(localPath, "utf8");
2979
3105
  process.stdout.write(text);
@@ -3125,7 +3251,7 @@ function checkBinary(name, command) {
3125
3251
  }
3126
3252
 
3127
3253
  // src/commands/version.ts
3128
- var CLI_VERSION = true ? "0.1.2" : "0.0.0-dev";
3254
+ var CLI_VERSION = true ? "0.1.4" : "0.0.0-dev";
3129
3255
  function registerVersion(program2) {
3130
3256
  program2.command("version").description("Print the CLI version").action(() => {
3131
3257
  process.stdout.write(CLI_VERSION + "\n");