@inteeka/task-cli 0.2.32 → 0.2.34
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 +631 -52
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1763,8 +1763,8 @@ async function runAgent(args) {
|
|
|
1763
1763
|
stdoutBuffer = "";
|
|
1764
1764
|
}
|
|
1765
1765
|
logHandle?.end();
|
|
1766
|
-
const
|
|
1767
|
-
resolve2({ exitCode, ok:
|
|
1766
|
+
const exitCode2 = code ?? 0;
|
|
1767
|
+
resolve2({ exitCode: exitCode2, ok: exitCode2 === 0, outputLogPath, stderrTail: stderrBuffer });
|
|
1768
1768
|
});
|
|
1769
1769
|
});
|
|
1770
1770
|
}
|
|
@@ -1952,7 +1952,7 @@ async function runPrReview(args) {
|
|
|
1952
1952
|
const logHandle = createWriteStream(logPath, { flags: "a" });
|
|
1953
1953
|
let stdoutBuffer = "";
|
|
1954
1954
|
let stderrTail = "";
|
|
1955
|
-
const
|
|
1955
|
+
const exitCode2 = await new Promise((resolve2, reject) => {
|
|
1956
1956
|
const child = spawn2(claude, cliArgs, {
|
|
1957
1957
|
cwd: args.cwd,
|
|
1958
1958
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1985,10 +1985,10 @@ async function runPrReview(args) {
|
|
|
1985
1985
|
resolve2(code ?? 0);
|
|
1986
1986
|
});
|
|
1987
1987
|
});
|
|
1988
|
-
if (
|
|
1988
|
+
if (exitCode2 !== 0) {
|
|
1989
1989
|
throw new PrReviewError(
|
|
1990
1990
|
"nonzero_exit",
|
|
1991
|
-
`claude review subprocess exited with code ${
|
|
1991
|
+
`claude review subprocess exited with code ${exitCode2}`,
|
|
1992
1992
|
stderrTail
|
|
1993
1993
|
);
|
|
1994
1994
|
}
|
|
@@ -4704,9 +4704,587 @@ function clampInt(raw, min, max, fallback) {
|
|
|
4704
4704
|
return Math.min(v, max);
|
|
4705
4705
|
}
|
|
4706
4706
|
|
|
4707
|
-
// src/commands/
|
|
4708
|
-
import {
|
|
4707
|
+
// src/commands/seo-feedback.ts
|
|
4708
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4709
|
+
import ora3 from "ora";
|
|
4710
|
+
|
|
4711
|
+
// src/seo-feedback/api.ts
|
|
4709
4712
|
import { request as request5 } from "undici";
|
|
4713
|
+
async function jsonRequest2(url, init) {
|
|
4714
|
+
const res = await request5(url, {
|
|
4715
|
+
method: init.method,
|
|
4716
|
+
headers: init.headers,
|
|
4717
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
4718
|
+
bodyTimeout: 12e4,
|
|
4719
|
+
headersTimeout: 12e4
|
|
4720
|
+
});
|
|
4721
|
+
let body;
|
|
4722
|
+
try {
|
|
4723
|
+
body = await res.body.json();
|
|
4724
|
+
} catch {
|
|
4725
|
+
body = void 0;
|
|
4726
|
+
}
|
|
4727
|
+
const nonce = res.headers["x-prepare-nonce"];
|
|
4728
|
+
const nonceStr = typeof nonce === "string" ? nonce : Array.isArray(nonce) ? nonce[0] ?? null : null;
|
|
4729
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
4730
|
+
const env = body;
|
|
4731
|
+
return { ok: true, status: res.statusCode, data: env?.data ?? body, nonce: nonceStr };
|
|
4732
|
+
}
|
|
4733
|
+
const errBody = body;
|
|
4734
|
+
return {
|
|
4735
|
+
ok: false,
|
|
4736
|
+
status: res.statusCode,
|
|
4737
|
+
code: errBody?.error?.code ?? `HTTP_${res.statusCode}`,
|
|
4738
|
+
message: errBody?.error?.message ?? `Request failed with status ${res.statusCode}`
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
var FRESH_CRED_CACHE_MS2 = 5e3;
|
|
4742
|
+
var SeoFeedbackApi = class {
|
|
4743
|
+
initialCreds;
|
|
4744
|
+
cachedCreds = null;
|
|
4745
|
+
cachedAt = 0;
|
|
4746
|
+
apiUrl;
|
|
4747
|
+
constructor(opts) {
|
|
4748
|
+
this.initialCreds = opts.creds;
|
|
4749
|
+
this.cachedCreds = opts.creds;
|
|
4750
|
+
this.cachedAt = Date.now();
|
|
4751
|
+
this.apiUrl = opts.apiUrl;
|
|
4752
|
+
}
|
|
4753
|
+
async getFreshCreds() {
|
|
4754
|
+
if (this.cachedCreds && Date.now() - this.cachedAt < FRESH_CRED_CACHE_MS2) {
|
|
4755
|
+
return this.cachedCreds;
|
|
4756
|
+
}
|
|
4757
|
+
const onDisk = await readCredentials();
|
|
4758
|
+
const base = onDisk ?? this.cachedCreds ?? this.initialCreds;
|
|
4759
|
+
const fresh = await ensureFreshAccessToken(base);
|
|
4760
|
+
this.cachedCreds = fresh;
|
|
4761
|
+
this.cachedAt = Date.now();
|
|
4762
|
+
return fresh;
|
|
4763
|
+
}
|
|
4764
|
+
async userHeaders() {
|
|
4765
|
+
const creds = await this.getFreshCreds();
|
|
4766
|
+
return {
|
|
4767
|
+
"Content-Type": "application/json",
|
|
4768
|
+
Authorization: `Bearer ${creds.access_token}`,
|
|
4769
|
+
"User-Agent": "task-cli/seo-feedback"
|
|
4770
|
+
};
|
|
4771
|
+
}
|
|
4772
|
+
skillHeaders(skillToken, extra = {}) {
|
|
4773
|
+
return {
|
|
4774
|
+
"Content-Type": "application/json",
|
|
4775
|
+
Authorization: `Bearer ${skillToken}`,
|
|
4776
|
+
"User-Agent": "task-cli/seo-feedback",
|
|
4777
|
+
...extra
|
|
4778
|
+
};
|
|
4779
|
+
}
|
|
4780
|
+
async issueSkillToken(args) {
|
|
4781
|
+
const result = await jsonRequest2(
|
|
4782
|
+
`${this.apiUrl}/api/v1/cli/issue-skill-token`,
|
|
4783
|
+
{
|
|
4784
|
+
method: "POST",
|
|
4785
|
+
headers: await this.userHeaders(),
|
|
4786
|
+
body: {
|
|
4787
|
+
project_id: args.project_id,
|
|
4788
|
+
scope: "seo_feedback_sync",
|
|
4789
|
+
max_submits: args.max_submits,
|
|
4790
|
+
ttl_minutes: args.ttl_minutes ?? 30
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
);
|
|
4794
|
+
if (!result.ok) {
|
|
4795
|
+
await handleUserAuthFailure2(result.code, result.status);
|
|
4796
|
+
throw new CliError(exitCode(result.code, result.status), `${result.code}: ${result.message}`);
|
|
4797
|
+
}
|
|
4798
|
+
return result.data;
|
|
4799
|
+
}
|
|
4800
|
+
async prepare(skillToken, batchSize, idempotencyKey) {
|
|
4801
|
+
const result = await jsonRequest2(
|
|
4802
|
+
`${this.apiUrl}/api/v1/cli/seo-feedback-sync/prepare`,
|
|
4803
|
+
{
|
|
4804
|
+
method: "POST",
|
|
4805
|
+
headers: this.skillHeaders(skillToken, { "Idempotency-Key": idempotencyKey }),
|
|
4806
|
+
body: { batch_size: batchSize }
|
|
4807
|
+
}
|
|
4808
|
+
);
|
|
4809
|
+
if (!result.ok) {
|
|
4810
|
+
throw new CliError(exitCode(result.code, result.status), `${result.code}: ${result.message}`);
|
|
4811
|
+
}
|
|
4812
|
+
if (!result.data.prepare_nonce && result.nonce) {
|
|
4813
|
+
result.data.prepare_nonce = result.nonce;
|
|
4814
|
+
}
|
|
4815
|
+
return result.data;
|
|
4816
|
+
}
|
|
4817
|
+
async submit(args) {
|
|
4818
|
+
const result = await jsonRequest2(
|
|
4819
|
+
`${this.apiUrl}/api/v1/cli/seo-feedback-sync/submit`,
|
|
4820
|
+
{
|
|
4821
|
+
method: "POST",
|
|
4822
|
+
headers: this.skillHeaders(args.skillToken, { "X-Prepare-Nonce": args.nonce }),
|
|
4823
|
+
body: {
|
|
4824
|
+
ticket_id: args.ticketId,
|
|
4825
|
+
report: args.report,
|
|
4826
|
+
input_tokens: args.inputTokens,
|
|
4827
|
+
output_tokens: args.outputTokens,
|
|
4828
|
+
model: args.model
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
);
|
|
4832
|
+
if (result.ok) return { status: "ready" };
|
|
4833
|
+
if (result.code === "CLAIM_MISMATCH" || result.code === "BAD_STATUS" || result.code === "WRONG_SCOPE" || result.code === "SCAN_CONTEXT_FAILED" || result.code === "FINALISE_RACE") {
|
|
4834
|
+
return { status: "skip", reason: result.code };
|
|
4835
|
+
}
|
|
4836
|
+
throw new CliError(exitCode(result.code, result.status), `${result.code}: ${result.message}`);
|
|
4837
|
+
}
|
|
4838
|
+
async abort(skillToken, ticketIds) {
|
|
4839
|
+
if (ticketIds.length === 0) return;
|
|
4840
|
+
await jsonRequest2(`${this.apiUrl}/api/v1/cli/seo-feedback-sync/abort`, {
|
|
4841
|
+
method: "POST",
|
|
4842
|
+
headers: this.skillHeaders(skillToken),
|
|
4843
|
+
body: { ticket_ids: ticketIds }
|
|
4844
|
+
}).catch(() => void 0);
|
|
4845
|
+
}
|
|
4846
|
+
async runSummary(skillToken, summary) {
|
|
4847
|
+
await jsonRequest2(`${this.apiUrl}/api/v1/cli/seo-feedback-sync/run-summary`, {
|
|
4848
|
+
method: "POST",
|
|
4849
|
+
headers: this.skillHeaders(skillToken),
|
|
4850
|
+
body: summary
|
|
4851
|
+
}).catch(() => void 0);
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
async function handleUserAuthFailure2(code, status) {
|
|
4855
|
+
if (status === 401 && (code === "UNAUTHORIZED" || code === "TOKEN_EXPIRED")) {
|
|
4856
|
+
await clearCredentials();
|
|
4857
|
+
throw new CliError(
|
|
4858
|
+
CLI_EXIT_CODES.UNAUTHORISED,
|
|
4859
|
+
"Your CLI session is no longer valid",
|
|
4860
|
+
"Run 'task login' to authenticate again."
|
|
4861
|
+
);
|
|
4862
|
+
}
|
|
4863
|
+
if (status === 403 && code === "CLI_ACCESS_REVOKED") {
|
|
4864
|
+
await clearCredentials();
|
|
4865
|
+
throw new CliError(
|
|
4866
|
+
CLI_EXIT_CODES.UNAUTHORISED,
|
|
4867
|
+
"CLI access has been revoked",
|
|
4868
|
+
"Ask a project admin to re-grant access from the Agentic CLI page."
|
|
4869
|
+
);
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
function exitCode(code, status) {
|
|
4873
|
+
if (status === 401 || status === 403) return CLI_EXIT_CODES.UNAUTHORISED;
|
|
4874
|
+
if (code === "TIER_LIMIT_EXCEEDED") return CLI_EXIT_CODES.MISCONFIGURATION;
|
|
4875
|
+
if (status >= 500) return CLI_EXIT_CODES.NETWORK_UNREACHABLE;
|
|
4876
|
+
return CLI_EXIT_CODES.GENERIC_ERROR;
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
// src/seo-feedback/llm.ts
|
|
4880
|
+
import { spawn as spawn6 } from "child_process";
|
|
4881
|
+
import { mkdir as mkdir10, writeFile as writeFile11 } from "fs/promises";
|
|
4882
|
+
import { homedir as homedir7 } from "os";
|
|
4883
|
+
import { join as join12 } from "path";
|
|
4884
|
+
var SEO_FEEDBACK_JSON_SCHEMA = {
|
|
4885
|
+
type: "object",
|
|
4886
|
+
required: [
|
|
4887
|
+
"overall_score",
|
|
4888
|
+
"headline",
|
|
4889
|
+
"overall_assessment",
|
|
4890
|
+
"score_interpretation",
|
|
4891
|
+
"strengths",
|
|
4892
|
+
"prioritised_actions",
|
|
4893
|
+
"quick_wins",
|
|
4894
|
+
"summary",
|
|
4895
|
+
"generated_for_url"
|
|
4896
|
+
],
|
|
4897
|
+
additionalProperties: false,
|
|
4898
|
+
properties: {
|
|
4899
|
+
overall_score: { type: "integer", minimum: 0, maximum: 100 },
|
|
4900
|
+
headline: { type: "string", minLength: 1, maxLength: 200 },
|
|
4901
|
+
overall_assessment: { type: "string", minLength: 1, maxLength: 1500 },
|
|
4902
|
+
score_interpretation: { type: "string", minLength: 1, maxLength: 800 },
|
|
4903
|
+
strengths: {
|
|
4904
|
+
type: "array",
|
|
4905
|
+
maxItems: 8,
|
|
4906
|
+
items: { type: "string", minLength: 1, maxLength: 300 }
|
|
4907
|
+
},
|
|
4908
|
+
prioritised_actions: {
|
|
4909
|
+
type: "array",
|
|
4910
|
+
maxItems: 12,
|
|
4911
|
+
items: {
|
|
4912
|
+
type: "object",
|
|
4913
|
+
required: ["title", "detail", "priority", "impact", "effort"],
|
|
4914
|
+
additionalProperties: false,
|
|
4915
|
+
properties: {
|
|
4916
|
+
title: { type: "string", minLength: 1, maxLength: 160 },
|
|
4917
|
+
detail: { type: "string", minLength: 1, maxLength: 1200 },
|
|
4918
|
+
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
4919
|
+
impact: { type: "string", minLength: 1, maxLength: 400 },
|
|
4920
|
+
effort: { type: "string", enum: ["quick", "moderate", "involved"] }
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
},
|
|
4924
|
+
quick_wins: {
|
|
4925
|
+
type: "array",
|
|
4926
|
+
maxItems: 6,
|
|
4927
|
+
items: { type: "string", minLength: 1, maxLength: 300 }
|
|
4928
|
+
},
|
|
4929
|
+
summary: { type: "string", minLength: 1, maxLength: 600 },
|
|
4930
|
+
generated_for_url: { type: "string", minLength: 1, maxLength: 2e3 }
|
|
4931
|
+
}
|
|
4932
|
+
};
|
|
4933
|
+
var SeoFeedbackLlmError = class extends Error {
|
|
4934
|
+
constructor(reason, message, debugLogPath) {
|
|
4935
|
+
super(message);
|
|
4936
|
+
this.reason = reason;
|
|
4937
|
+
if (debugLogPath !== void 0) this.debugLogPath = debugLogPath;
|
|
4938
|
+
}
|
|
4939
|
+
debugLogPath;
|
|
4940
|
+
};
|
|
4941
|
+
var DEBUG2 = process.env["TASK_SCAN_DEBUG"] === "1";
|
|
4942
|
+
async function generateSeoFeedbackJson(args) {
|
|
4943
|
+
const claude = args.claudePath ?? "claude";
|
|
4944
|
+
const userPrompt = [
|
|
4945
|
+
args.userMessage,
|
|
4946
|
+
"",
|
|
4947
|
+
"Return JSON only matching the supplied schema. Do not include explanatory prose, markdown fences, or commentary."
|
|
4948
|
+
].join("\n");
|
|
4949
|
+
const cliArgs = [
|
|
4950
|
+
"--print",
|
|
4951
|
+
"--output-format",
|
|
4952
|
+
"json",
|
|
4953
|
+
"--tools",
|
|
4954
|
+
"",
|
|
4955
|
+
"--system-prompt",
|
|
4956
|
+
args.systemPrompt,
|
|
4957
|
+
"--model",
|
|
4958
|
+
args.modelId,
|
|
4959
|
+
"--json-schema",
|
|
4960
|
+
JSON.stringify(SEO_FEEDBACK_JSON_SCHEMA)
|
|
4961
|
+
];
|
|
4962
|
+
return new Promise((resolve2, reject) => {
|
|
4963
|
+
let child;
|
|
4964
|
+
try {
|
|
4965
|
+
child = spawn6(claude, cliArgs, { stdio: ["pipe", "pipe", "pipe"], signal: args.signal });
|
|
4966
|
+
} catch (err) {
|
|
4967
|
+
reject(
|
|
4968
|
+
new SeoFeedbackLlmError(
|
|
4969
|
+
"spawn_failed",
|
|
4970
|
+
`Could not invoke claude: ${err.message}`
|
|
4971
|
+
)
|
|
4972
|
+
);
|
|
4973
|
+
return;
|
|
4974
|
+
}
|
|
4975
|
+
let stdoutBuf = "";
|
|
4976
|
+
let stderrBuf = "";
|
|
4977
|
+
child.stdout?.on("data", (c2) => stdoutBuf += c2.toString("utf8"));
|
|
4978
|
+
child.stderr?.on("data", (c2) => stderrBuf += c2.toString("utf8"));
|
|
4979
|
+
child.on("error", (err) => reject(new SeoFeedbackLlmError("spawn_failed", err.message)));
|
|
4980
|
+
child.on("close", async (code, signal) => {
|
|
4981
|
+
if (signal === "SIGTERM" || signal === "SIGKILL") {
|
|
4982
|
+
reject(new SeoFeedbackLlmError("aborted", "claude was aborted"));
|
|
4983
|
+
return;
|
|
4984
|
+
}
|
|
4985
|
+
if (detectAuthFailure2(stdoutBuf)) {
|
|
4986
|
+
const dump = await maybeDumpDebug2(args.ticketId, stdoutBuf, stderrBuf);
|
|
4987
|
+
reject(
|
|
4988
|
+
new SeoFeedbackLlmError(
|
|
4989
|
+
"non_zero_exit",
|
|
4990
|
+
`Claude is not logged in. Run \`claude /login\` once on this machine, then re-run \`task seo-feedback\`.`,
|
|
4991
|
+
dump ?? void 0
|
|
4992
|
+
)
|
|
4993
|
+
);
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
if (code !== 0) {
|
|
4997
|
+
const dump = await maybeDumpDebug2(args.ticketId, stdoutBuf, stderrBuf);
|
|
4998
|
+
reject(
|
|
4999
|
+
new SeoFeedbackLlmError(
|
|
5000
|
+
"non_zero_exit",
|
|
5001
|
+
`claude exited with code ${code}: ${stderrBuf.trim().slice(0, 600)}`,
|
|
5002
|
+
dump ?? void 0
|
|
5003
|
+
)
|
|
5004
|
+
);
|
|
5005
|
+
return;
|
|
5006
|
+
}
|
|
5007
|
+
const structuredFromEnvelope = extractStructuredOutput2(stdoutBuf);
|
|
5008
|
+
const innerText = extractEnvelopeText2(stdoutBuf);
|
|
5009
|
+
const parsed = structuredFromEnvelope ?? parseStructuredJson2(innerText);
|
|
5010
|
+
if (!parsed) {
|
|
5011
|
+
const dump = await maybeDumpDebug2(args.ticketId, stdoutBuf, stderrBuf);
|
|
5012
|
+
reject(
|
|
5013
|
+
new SeoFeedbackLlmError(
|
|
5014
|
+
"no_json",
|
|
5015
|
+
`No JSON object in claude output${dump ? ` (raw output saved to ${dump})` : ""}`,
|
|
5016
|
+
dump ?? void 0
|
|
5017
|
+
)
|
|
5018
|
+
);
|
|
5019
|
+
return;
|
|
5020
|
+
}
|
|
5021
|
+
const tokens = readEnvelopeTokens2(stdoutBuf, userPrompt, innerText);
|
|
5022
|
+
resolve2({
|
|
5023
|
+
report: parsed,
|
|
5024
|
+
rawText: stdoutBuf,
|
|
5025
|
+
inputTokens: tokens.input,
|
|
5026
|
+
outputTokens: tokens.output
|
|
5027
|
+
});
|
|
5028
|
+
});
|
|
5029
|
+
child.stdin?.write(userPrompt);
|
|
5030
|
+
child.stdin?.end();
|
|
5031
|
+
});
|
|
5032
|
+
}
|
|
5033
|
+
function detectAuthFailure2(raw) {
|
|
5034
|
+
const trimmed = raw.trim();
|
|
5035
|
+
if (!trimmed) return false;
|
|
5036
|
+
try {
|
|
5037
|
+
const env = JSON.parse(trimmed);
|
|
5038
|
+
if (env.is_error === true && typeof env.result === "string") {
|
|
5039
|
+
const msg = env.result.toLowerCase();
|
|
5040
|
+
return msg.includes("not logged in") || msg.includes("please run /login") || msg.includes("please log in");
|
|
5041
|
+
}
|
|
5042
|
+
} catch {
|
|
5043
|
+
}
|
|
5044
|
+
return false;
|
|
5045
|
+
}
|
|
5046
|
+
function extractStructuredOutput2(raw) {
|
|
5047
|
+
const trimmed = raw.trim();
|
|
5048
|
+
if (!trimmed) return null;
|
|
5049
|
+
try {
|
|
5050
|
+
const env = JSON.parse(trimmed);
|
|
5051
|
+
const so = env.structured_output;
|
|
5052
|
+
if (so && typeof so === "object") return so;
|
|
5053
|
+
} catch {
|
|
5054
|
+
}
|
|
5055
|
+
return null;
|
|
5056
|
+
}
|
|
5057
|
+
function extractEnvelopeText2(raw) {
|
|
5058
|
+
const trimmed = raw.trim();
|
|
5059
|
+
if (!trimmed) return raw;
|
|
5060
|
+
try {
|
|
5061
|
+
const env = JSON.parse(trimmed);
|
|
5062
|
+
if (typeof env.result === "string") return env.result;
|
|
5063
|
+
} catch {
|
|
5064
|
+
}
|
|
5065
|
+
return raw;
|
|
5066
|
+
}
|
|
5067
|
+
function readEnvelopeTokens2(raw, userPrompt, innerText) {
|
|
5068
|
+
try {
|
|
5069
|
+
const env = JSON.parse(raw.trim());
|
|
5070
|
+
const inTok = env.input_tokens ?? env.usage?.input_tokens;
|
|
5071
|
+
const outTok = env.output_tokens ?? env.usage?.output_tokens;
|
|
5072
|
+
if (typeof inTok === "number" && typeof outTok === "number") {
|
|
5073
|
+
return { input: inTok, output: outTok };
|
|
5074
|
+
}
|
|
5075
|
+
} catch {
|
|
5076
|
+
}
|
|
5077
|
+
return {
|
|
5078
|
+
input: Math.max(1, Math.round(userPrompt.length / 4)),
|
|
5079
|
+
output: Math.max(1, Math.round(innerText.length / 4))
|
|
5080
|
+
};
|
|
5081
|
+
}
|
|
5082
|
+
async function maybeDumpDebug2(ticketId, stdout, stderr) {
|
|
5083
|
+
if (!DEBUG2 && stdout.length === 0 && stderr.length === 0) return null;
|
|
5084
|
+
try {
|
|
5085
|
+
const dir = join12(homedir7(), ".cache", "task", "seo-feedback-debug");
|
|
5086
|
+
await mkdir10(dir, { recursive: true });
|
|
5087
|
+
const path = join12(dir, `${ticketId}-${Date.now()}.log`);
|
|
5088
|
+
await writeFile11(
|
|
5089
|
+
path,
|
|
5090
|
+
["## ticket_id", ticketId, "", "## stdout", stdout, "", "## stderr", stderr].join("\n")
|
|
5091
|
+
);
|
|
5092
|
+
return path;
|
|
5093
|
+
} catch {
|
|
5094
|
+
return null;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
function parseStructuredJson2(raw) {
|
|
5098
|
+
const trimmed = raw.trim();
|
|
5099
|
+
if (!trimmed) return null;
|
|
5100
|
+
try {
|
|
5101
|
+
const direct = JSON.parse(trimmed);
|
|
5102
|
+
if (direct && typeof direct === "object") return direct;
|
|
5103
|
+
} catch {
|
|
5104
|
+
}
|
|
5105
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
5106
|
+
if (fenced && fenced[1]) {
|
|
5107
|
+
try {
|
|
5108
|
+
const obj = JSON.parse(fenced[1].trim());
|
|
5109
|
+
if (obj && typeof obj === "object") return obj;
|
|
5110
|
+
} catch {
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
const start = trimmed.indexOf("{");
|
|
5114
|
+
if (start === -1) return null;
|
|
5115
|
+
let depth = 0;
|
|
5116
|
+
let inString = false;
|
|
5117
|
+
let escape = false;
|
|
5118
|
+
for (let i = start; i < trimmed.length; i++) {
|
|
5119
|
+
const ch = trimmed[i];
|
|
5120
|
+
if (inString) {
|
|
5121
|
+
if (escape) escape = false;
|
|
5122
|
+
else if (ch === "\\") escape = true;
|
|
5123
|
+
else if (ch === '"') inString = false;
|
|
5124
|
+
continue;
|
|
5125
|
+
}
|
|
5126
|
+
if (ch === '"') {
|
|
5127
|
+
inString = true;
|
|
5128
|
+
continue;
|
|
5129
|
+
}
|
|
5130
|
+
if (ch === "{") depth += 1;
|
|
5131
|
+
else if (ch === "}") {
|
|
5132
|
+
depth -= 1;
|
|
5133
|
+
if (depth === 0) {
|
|
5134
|
+
try {
|
|
5135
|
+
const obj = JSON.parse(trimmed.slice(start, i + 1));
|
|
5136
|
+
if (obj && typeof obj === "object") return obj;
|
|
5137
|
+
} catch {
|
|
5138
|
+
return null;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
return null;
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
// src/commands/seo-feedback.ts
|
|
5147
|
+
function registerSeoFeedback(program2) {
|
|
5148
|
+
program2.command("seo-feedback").description("Generate AI Feedback SEO reports for queued tickets in the linked project").option("--project <id>", "Project id (default: the linked project from .task/config.json)").option("--max <n>", "Max submissions", "50").option("--batch <n>", "Tickets per /prepare batch (1-10)", "5").option("--api-url <url>", "Override TASK_API_URL").option("--silent", "Suppress per-ticket progress chrome").action(async (opts) => {
|
|
5149
|
+
await runSeoFeedback(opts);
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
async function runSeoFeedback(opts) {
|
|
5153
|
+
let creds = await readCredentials();
|
|
5154
|
+
if (!creds) {
|
|
5155
|
+
throw new CliError(
|
|
5156
|
+
CLI_EXIT_CODES.MISCONFIGURATION,
|
|
5157
|
+
"Not signed in",
|
|
5158
|
+
"Run 'task login' to authenticate."
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
creds = await ensureFreshAccessToken(creds);
|
|
5162
|
+
const localCfg = await readLocalConfig();
|
|
5163
|
+
const linkedProject = await readProjectConfig(findRepoRoot());
|
|
5164
|
+
const apiUrl = (opts.apiUrl ?? process.env["TASK_API_URL"] ?? creds.api_url ?? localCfg.api_url ?? linkedProject?.api_url ?? "http://localhost:3400").replace(/\/$/, "");
|
|
5165
|
+
const projectId = opts.project ?? linkedProject?.project_id ?? null;
|
|
5166
|
+
if (!projectId) {
|
|
5167
|
+
throw new CliError(
|
|
5168
|
+
CLI_EXIT_CODES.MISCONFIGURATION,
|
|
5169
|
+
"No project to scan",
|
|
5170
|
+
"Run `task link` inside the repo, or pass --project <id>."
|
|
5171
|
+
);
|
|
5172
|
+
}
|
|
5173
|
+
const max = clampInt2(opts.max, 1, 500, 50);
|
|
5174
|
+
const batchSize = clampInt2(opts.batch, 1, 10, 5);
|
|
5175
|
+
const silent = !!opts.silent || localCfg.silent;
|
|
5176
|
+
const claudePath = localCfg.claude_path ?? void 0;
|
|
5177
|
+
const api = new SeoFeedbackApi({ apiUrl, creds });
|
|
5178
|
+
const issued = await api.issueSkillToken({ project_id: projectId, max_submits: max });
|
|
5179
|
+
const skillToken = issued.token;
|
|
5180
|
+
let prepared = 0;
|
|
5181
|
+
let submitted = 0;
|
|
5182
|
+
let failed = 0;
|
|
5183
|
+
let skipped = 0;
|
|
5184
|
+
const startedAt = Date.now();
|
|
5185
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
5186
|
+
let fatal = null;
|
|
5187
|
+
try {
|
|
5188
|
+
while (submitted < max) {
|
|
5189
|
+
let batch;
|
|
5190
|
+
try {
|
|
5191
|
+
batch = await api.prepare(skillToken, batchSize, randomUUID3());
|
|
5192
|
+
} catch (err) {
|
|
5193
|
+
fatal = err;
|
|
5194
|
+
break;
|
|
5195
|
+
}
|
|
5196
|
+
if (batch.tickets.length === 0) break;
|
|
5197
|
+
prepared += batch.tickets.length;
|
|
5198
|
+
const nonce = batch.prepare_nonce;
|
|
5199
|
+
for (const ticket of batch.tickets) {
|
|
5200
|
+
inFlight.add(ticket.ticket_id);
|
|
5201
|
+
const spinner = silent ? null : ora3(`Analysing ${ticket.ticket_id.slice(0, 8)}\u2026`).start();
|
|
5202
|
+
try {
|
|
5203
|
+
const gen = await safeGenerate2(ticket, claudePath);
|
|
5204
|
+
if (!gen.ok) {
|
|
5205
|
+
skipped += 1;
|
|
5206
|
+
spinner?.warn(`${ticket.ticket_id.slice(0, 8)} skipped (${gen.reason})`);
|
|
5207
|
+
await api.abort(skillToken, [ticket.ticket_id]).catch(() => void 0);
|
|
5208
|
+
inFlight.delete(ticket.ticket_id);
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
const result = await api.submit({
|
|
5212
|
+
skillToken,
|
|
5213
|
+
nonce,
|
|
5214
|
+
ticketId: ticket.ticket_id,
|
|
5215
|
+
report: gen.report,
|
|
5216
|
+
inputTokens: gen.inputTokens,
|
|
5217
|
+
outputTokens: gen.outputTokens,
|
|
5218
|
+
model: ticket.model_id
|
|
5219
|
+
});
|
|
5220
|
+
if (result.status === "skip") {
|
|
5221
|
+
skipped += 1;
|
|
5222
|
+
spinner?.warn(`${ticket.ticket_id.slice(0, 8)} skipped (${result.reason})`);
|
|
5223
|
+
} else {
|
|
5224
|
+
submitted += 1;
|
|
5225
|
+
spinner?.succeed(`${ticket.ticket_id.slice(0, 8)} report ready`);
|
|
5226
|
+
}
|
|
5227
|
+
inFlight.delete(ticket.ticket_id);
|
|
5228
|
+
if (submitted >= max) break;
|
|
5229
|
+
} catch (err) {
|
|
5230
|
+
failed += 1;
|
|
5231
|
+
spinner?.fail(`${ticket.ticket_id.slice(0, 8)} ${err.message.slice(0, 200)}`);
|
|
5232
|
+
if (err instanceof CliError && err.code === CLI_EXIT_CODES.UNAUTHORISED) {
|
|
5233
|
+
fatal = err;
|
|
5234
|
+
break;
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
5238
|
+
if (fatal) break;
|
|
5239
|
+
}
|
|
5240
|
+
} finally {
|
|
5241
|
+
const leftover = Array.from(inFlight);
|
|
5242
|
+
if (leftover.length > 0) await api.abort(skillToken, leftover).catch(() => void 0);
|
|
5243
|
+
await api.runSummary(skillToken, {
|
|
5244
|
+
prepared,
|
|
5245
|
+
submitted,
|
|
5246
|
+
denylist_hits: 0,
|
|
5247
|
+
failed,
|
|
5248
|
+
duration_ms: Date.now() - startedAt
|
|
5249
|
+
}).catch(() => void 0);
|
|
5250
|
+
}
|
|
5251
|
+
process.stdout.write(
|
|
5252
|
+
`${c.bold("\nAI Feedback")} \u2014 ${c.ok(String(submitted))} report(s) ready, ${c.err(String(failed))} failed, ${c.dim(String(skipped) + " skipped")}.
|
|
5253
|
+
`
|
|
5254
|
+
);
|
|
5255
|
+
if (fatal) throw fatal;
|
|
5256
|
+
}
|
|
5257
|
+
async function safeGenerate2(ticket, claudePath) {
|
|
5258
|
+
try {
|
|
5259
|
+
const out = await generateSeoFeedbackJson({
|
|
5260
|
+
systemPrompt: ticket.system_prompt,
|
|
5261
|
+
userMessage: ticket.user_message,
|
|
5262
|
+
modelId: ticket.model_id,
|
|
5263
|
+
ticketId: ticket.ticket_id,
|
|
5264
|
+
...claudePath ? { claudePath } : {}
|
|
5265
|
+
});
|
|
5266
|
+
return {
|
|
5267
|
+
ok: true,
|
|
5268
|
+
report: out.report,
|
|
5269
|
+
inputTokens: out.inputTokens,
|
|
5270
|
+
outputTokens: out.outputTokens
|
|
5271
|
+
};
|
|
5272
|
+
} catch (err) {
|
|
5273
|
+
if (err instanceof SeoFeedbackLlmError) {
|
|
5274
|
+
return { ok: false, reason: `${err.reason}: ${err.message.slice(0, 150)}` };
|
|
5275
|
+
}
|
|
5276
|
+
throw err;
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
function clampInt2(raw, min, max, fallback) {
|
|
5280
|
+
const v = parseInt(raw, 10);
|
|
5281
|
+
if (!Number.isFinite(v) || v < min) return fallback;
|
|
5282
|
+
return Math.min(v, max);
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
// src/commands/slack-import.ts
|
|
5286
|
+
import { spawn as spawn7 } from "child_process";
|
|
5287
|
+
import { request as request6 } from "undici";
|
|
4710
5288
|
var BULLET_TYPES = ["bug", "feature", "task", "question", "improvement"];
|
|
4711
5289
|
var BULLET_PRIORITIES = ["critical", "high", "medium", "low", "none"];
|
|
4712
5290
|
var CLASSIFICATIONS = ["code", "physical"];
|
|
@@ -4842,7 +5420,7 @@ async function generateBullets(args) {
|
|
|
4842
5420
|
return new Promise((resolve2, reject) => {
|
|
4843
5421
|
let child;
|
|
4844
5422
|
try {
|
|
4845
|
-
child =
|
|
5423
|
+
child = spawn7(claude, cliArgs, { stdio: ["pipe", "pipe", "pipe"] });
|
|
4846
5424
|
} catch (err) {
|
|
4847
5425
|
reject(new Error(`Could not invoke claude: ${err.message}`));
|
|
4848
5426
|
return;
|
|
@@ -4917,7 +5495,7 @@ function extractStructured(raw) {
|
|
|
4917
5495
|
return { ok: false, error: "claude returned no parseable JSON" };
|
|
4918
5496
|
}
|
|
4919
5497
|
async function patchStatus(url, bearer, body) {
|
|
4920
|
-
const res = await
|
|
5498
|
+
const res = await request6(url, {
|
|
4921
5499
|
method: "PATCH",
|
|
4922
5500
|
headers: {
|
|
4923
5501
|
"Content-Type": "application/json",
|
|
@@ -4938,8 +5516,8 @@ async function patchStatus(url, bearer, body) {
|
|
|
4938
5516
|
}
|
|
4939
5517
|
|
|
4940
5518
|
// src/commands/fast-track.ts
|
|
4941
|
-
import { randomUUID as
|
|
4942
|
-
import
|
|
5519
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
5520
|
+
import ora4 from "ora";
|
|
4943
5521
|
function registerFastTrack(program2) {
|
|
4944
5522
|
program2.command("fast-track").description(
|
|
4945
5523
|
"End-to-end: scan + auto-approve + work on the next CLI-eligible ticket(s) in the linked project \u2014 no admin review step"
|
|
@@ -5041,13 +5619,13 @@ async function fastTrackOneTicket(args) {
|
|
|
5041
5619
|
max_submits: 1
|
|
5042
5620
|
});
|
|
5043
5621
|
const skillToken = issued.token;
|
|
5044
|
-
const prepared = await api.prepare(skillToken, 1,
|
|
5622
|
+
const prepared = await api.prepare(skillToken, 1, randomUUID4());
|
|
5045
5623
|
if (prepared.tickets.length === 0) {
|
|
5046
5624
|
return { kind: "no_eligible" };
|
|
5047
5625
|
}
|
|
5048
5626
|
const ticket = prepared.tickets[0];
|
|
5049
5627
|
const nonce = prepared.prepare_nonce;
|
|
5050
|
-
const spinner = silent ? null :
|
|
5628
|
+
const spinner = silent ? null : ora4(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)} \u2014 scanning`).start();
|
|
5051
5629
|
let generated;
|
|
5052
5630
|
try {
|
|
5053
5631
|
generated = await generateFixPromptJson({
|
|
@@ -5340,16 +5918,16 @@ ${c.err("\u2717 pr-test failed")}: ${err.message}
|
|
|
5340
5918
|
}
|
|
5341
5919
|
|
|
5342
5920
|
// src/commands/scheduled-task.ts
|
|
5343
|
-
import { randomUUID as
|
|
5921
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
5344
5922
|
|
|
5345
5923
|
// src/scheduler/index.ts
|
|
5346
5924
|
import { platform as platform2 } from "os";
|
|
5347
5925
|
|
|
5348
5926
|
// src/scheduler/launchd.ts
|
|
5349
|
-
import { mkdir as
|
|
5350
|
-
import { homedir as
|
|
5351
|
-
import { join as
|
|
5352
|
-
import { execFileSync as execFileSync9, spawn as
|
|
5927
|
+
import { mkdir as mkdir11, readFile as readFile5, writeFile as writeFile12, unlink as unlink4, readdir as readdir2 } from "fs/promises";
|
|
5928
|
+
import { homedir as homedir8 } from "os";
|
|
5929
|
+
import { join as join13 } from "path";
|
|
5930
|
+
import { execFileSync as execFileSync9, spawn as spawn8 } from "child_process";
|
|
5353
5931
|
|
|
5354
5932
|
// src/scheduler/cron-translate.ts
|
|
5355
5933
|
function translateToLaunchd(cron) {
|
|
@@ -5450,14 +6028,14 @@ function expandField(field, min, max) {
|
|
|
5450
6028
|
}
|
|
5451
6029
|
|
|
5452
6030
|
// src/scheduler/launchd.ts
|
|
5453
|
-
var PLIST_DIR =
|
|
6031
|
+
var PLIST_DIR = join13(homedir8(), "Library", "LaunchAgents");
|
|
5454
6032
|
var LABEL_PREFIX = "com.inteeka.task.cli.";
|
|
5455
6033
|
var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
|
|
5456
6034
|
function plistPath(id) {
|
|
5457
6035
|
if (!SAFE_ID_RE.test(id) || id.includes("..")) {
|
|
5458
6036
|
throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
|
|
5459
6037
|
}
|
|
5460
|
-
return
|
|
6038
|
+
return join13(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
|
|
5461
6039
|
}
|
|
5462
6040
|
function buildPlist(entry) {
|
|
5463
6041
|
const calendars = translateToLaunchd(entry.cron);
|
|
@@ -5493,9 +6071,9 @@ ${fields}
|
|
|
5493
6071
|
` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
|
|
5494
6072
|
` </dict>`,
|
|
5495
6073
|
` <key>StandardOutPath</key>`,
|
|
5496
|
-
` <string>${escapeXml(
|
|
6074
|
+
` <string>${escapeXml(join13(homedir8(), ".cache", "task", "launchd-stdout.log"))}</string>`,
|
|
5497
6075
|
` <key>StandardErrorPath</key>`,
|
|
5498
|
-
` <string>${escapeXml(
|
|
6076
|
+
` <string>${escapeXml(join13(homedir8(), ".cache", "task", "launchd-stderr.log"))}</string>`,
|
|
5499
6077
|
!entry.enabled ? ` <key>Disabled</key>
|
|
5500
6078
|
<true/>` : "",
|
|
5501
6079
|
"</dict>",
|
|
@@ -5513,9 +6091,9 @@ function bootstrapDomain() {
|
|
|
5513
6091
|
}
|
|
5514
6092
|
var launchdAdapter = {
|
|
5515
6093
|
async upsert(entry) {
|
|
5516
|
-
await
|
|
6094
|
+
await mkdir11(PLIST_DIR, { recursive: true });
|
|
5517
6095
|
const path = plistPath(entry.id);
|
|
5518
|
-
await
|
|
6096
|
+
await writeFile12(path, buildPlist(entry));
|
|
5519
6097
|
try {
|
|
5520
6098
|
execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
5521
6099
|
} catch {
|
|
@@ -5544,7 +6122,7 @@ var launchdAdapter = {
|
|
|
5544
6122
|
for (const file of ours) {
|
|
5545
6123
|
const id = file.slice(LABEL_PREFIX.length, -".plist".length);
|
|
5546
6124
|
try {
|
|
5547
|
-
const xml = await readFile5(
|
|
6125
|
+
const xml = await readFile5(join13(PLIST_DIR, file), "utf8");
|
|
5548
6126
|
const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
|
|
5549
6127
|
const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
|
|
5550
6128
|
const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
|
|
@@ -5567,7 +6145,7 @@ var launchdAdapter = {
|
|
|
5567
6145
|
return new Promise((resolve2) => {
|
|
5568
6146
|
const args = entry.command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [entry.command];
|
|
5569
6147
|
const cmd = args.shift() ?? entry.command;
|
|
5570
|
-
const child =
|
|
6148
|
+
const child = spawn8(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5571
6149
|
let stdoutTail = "";
|
|
5572
6150
|
let stderrTail = "";
|
|
5573
6151
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -5590,7 +6168,7 @@ var launchdAdapter = {
|
|
|
5590
6168
|
}
|
|
5591
6169
|
if (enabled) {
|
|
5592
6170
|
xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
|
|
5593
|
-
await
|
|
6171
|
+
await writeFile12(path, xml);
|
|
5594
6172
|
try {
|
|
5595
6173
|
execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
5596
6174
|
} catch {
|
|
@@ -5602,7 +6180,7 @@ var launchdAdapter = {
|
|
|
5602
6180
|
"</dict>\n</plist>",
|
|
5603
6181
|
" <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
|
|
5604
6182
|
);
|
|
5605
|
-
await
|
|
6183
|
+
await writeFile12(path, xml);
|
|
5606
6184
|
}
|
|
5607
6185
|
try {
|
|
5608
6186
|
execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
@@ -5613,7 +6191,7 @@ var launchdAdapter = {
|
|
|
5613
6191
|
};
|
|
5614
6192
|
|
|
5615
6193
|
// src/scheduler/cron.ts
|
|
5616
|
-
import { execFileSync as execFileSync10, spawn as
|
|
6194
|
+
import { execFileSync as execFileSync10, spawn as spawn9 } from "child_process";
|
|
5617
6195
|
|
|
5618
6196
|
// src/scheduler/safe-command.ts
|
|
5619
6197
|
var FORBIDDEN = /[;&|`$()<>\\]/;
|
|
@@ -5674,7 +6252,7 @@ function readCrontab() {
|
|
|
5674
6252
|
}
|
|
5675
6253
|
}
|
|
5676
6254
|
function writeCrontab(text) {
|
|
5677
|
-
const child =
|
|
6255
|
+
const child = spawn9("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
5678
6256
|
child.stdin.write(text);
|
|
5679
6257
|
child.stdin.end();
|
|
5680
6258
|
}
|
|
@@ -5755,7 +6333,7 @@ var cronAdapter = {
|
|
|
5755
6333
|
return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
|
|
5756
6334
|
}
|
|
5757
6335
|
return new Promise((resolve2) => {
|
|
5758
|
-
const child =
|
|
6336
|
+
const child = spawn9(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5759
6337
|
let stdoutTail = "";
|
|
5760
6338
|
let stderrTail = "";
|
|
5761
6339
|
child.stdout?.on(
|
|
@@ -5783,7 +6361,7 @@ var cronAdapter = {
|
|
|
5783
6361
|
};
|
|
5784
6362
|
|
|
5785
6363
|
// src/scheduler/windows.ts
|
|
5786
|
-
import { execFileSync as execFileSync11, spawn as
|
|
6364
|
+
import { execFileSync as execFileSync11, spawn as spawn10 } from "child_process";
|
|
5787
6365
|
var TASK_PREFIX = "TaskCLI_";
|
|
5788
6366
|
function taskName(id) {
|
|
5789
6367
|
return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
|
|
@@ -5896,7 +6474,7 @@ var windowsAdapter = {
|
|
|
5896
6474
|
return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
|
|
5897
6475
|
}
|
|
5898
6476
|
return new Promise((resolve2) => {
|
|
5899
|
-
const child =
|
|
6477
|
+
const child = spawn10(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5900
6478
|
let stdoutTail = "";
|
|
5901
6479
|
let stderrTail = "";
|
|
5902
6480
|
child.stdout?.on(
|
|
@@ -5955,10 +6533,10 @@ var unsupportedAdapter = {
|
|
|
5955
6533
|
};
|
|
5956
6534
|
|
|
5957
6535
|
// src/scheduler/registry.ts
|
|
5958
|
-
import { mkdir as
|
|
5959
|
-
import { homedir as
|
|
5960
|
-
import { dirname as dirname5, join as
|
|
5961
|
-
var REGISTRY_PATH =
|
|
6536
|
+
import { mkdir as mkdir12, readFile as readFile6, writeFile as writeFile13 } from "fs/promises";
|
|
6537
|
+
import { homedir as homedir9 } from "os";
|
|
6538
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
6539
|
+
var REGISTRY_PATH = join14(homedir9(), ".config", "task", "schedules.json");
|
|
5962
6540
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5963
6541
|
function looksLikeRegistryRow(value) {
|
|
5964
6542
|
if (!value || typeof value !== "object") return false;
|
|
@@ -5978,8 +6556,8 @@ async function readRegistry() {
|
|
|
5978
6556
|
}
|
|
5979
6557
|
}
|
|
5980
6558
|
async function writeRegistry(rows) {
|
|
5981
|
-
await
|
|
5982
|
-
await
|
|
6559
|
+
await mkdir12(dirname5(REGISTRY_PATH), { recursive: true });
|
|
6560
|
+
await writeFile13(REGISTRY_PATH, JSON.stringify(rows, null, 2));
|
|
5983
6561
|
}
|
|
5984
6562
|
async function upsertRegistry(row) {
|
|
5985
6563
|
if (!UUID_RE.test(row.id)) {
|
|
@@ -6067,7 +6645,7 @@ function registerScheduledTask(program2) {
|
|
|
6067
6645
|
const max = Math.min(100, Math.max(1, parseInt(opts.max, 10) || 5));
|
|
6068
6646
|
const command = opts.command ?? `task work --auto --silent --max ${max}`;
|
|
6069
6647
|
const { hostId, hostLabel } = getHostInfo();
|
|
6070
|
-
const id =
|
|
6648
|
+
const id = randomUUID5();
|
|
6071
6649
|
const created = await apiCall("POST", "/api/v1/cli/schedules", {
|
|
6072
6650
|
body: {
|
|
6073
6651
|
name,
|
|
@@ -6219,8 +6797,8 @@ function stripAnsi(s) {
|
|
|
6219
6797
|
|
|
6220
6798
|
// src/commands/runs.ts
|
|
6221
6799
|
import { readFile as readFile7 } from "fs/promises";
|
|
6222
|
-
import { homedir as
|
|
6223
|
-
import { join as
|
|
6800
|
+
import { homedir as homedir10 } from "os";
|
|
6801
|
+
import { join as join15 } from "path";
|
|
6224
6802
|
function registerRuns(program2) {
|
|
6225
6803
|
const cmd = program2.command("runs").description("Inspect agentic CLI run history");
|
|
6226
6804
|
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) => {
|
|
@@ -6249,7 +6827,7 @@ function registerRuns(program2) {
|
|
|
6249
6827
|
process.stdout.write(JSON.stringify(row, null, 2) + "\n");
|
|
6250
6828
|
});
|
|
6251
6829
|
cmd.command("logs <id>").description("Show captured agent output for a run, if available").action(async (id) => {
|
|
6252
|
-
const localPath =
|
|
6830
|
+
const localPath = join15(homedir10(), ".cache", "task", "runs", `${id}.log`);
|
|
6253
6831
|
try {
|
|
6254
6832
|
const text = await readFile7(localPath, "utf8");
|
|
6255
6833
|
process.stdout.write(text);
|
|
@@ -6323,9 +6901,9 @@ function registerConfig(program2) {
|
|
|
6323
6901
|
// src/commands/doctor.ts
|
|
6324
6902
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
6325
6903
|
import { existsSync } from "fs";
|
|
6326
|
-
import { readFile as readFile8, writeFile as
|
|
6327
|
-
import { isAbsolute, join as
|
|
6328
|
-
import { request as
|
|
6904
|
+
import { readFile as readFile8, writeFile as writeFile14 } from "fs/promises";
|
|
6905
|
+
import { isAbsolute, join as join16 } from "path";
|
|
6906
|
+
import { request as request7 } from "undici";
|
|
6329
6907
|
var ALLOWED_TEST_EXECUTABLES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "node", "npx"]);
|
|
6330
6908
|
var PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun"]);
|
|
6331
6909
|
var DEFAULT_TEST_COMMAND = "pnpm typecheck";
|
|
@@ -6371,7 +6949,7 @@ function registerDoctor(program2) {
|
|
|
6371
6949
|
});
|
|
6372
6950
|
const apiUrl = creds?.api_url ?? cfg.api_url;
|
|
6373
6951
|
try {
|
|
6374
|
-
const res = await
|
|
6952
|
+
const res = await request7(apiUrl, {
|
|
6375
6953
|
method: "GET",
|
|
6376
6954
|
headersTimeout: 5e3,
|
|
6377
6955
|
bodyTimeout: 5e3
|
|
@@ -6544,11 +7122,11 @@ async function checkPrePushTest(root, configuredCommand, fix) {
|
|
|
6544
7122
|
};
|
|
6545
7123
|
}
|
|
6546
7124
|
const { scriptName, subdir } = resolveTestTarget(argv);
|
|
6547
|
-
const targetDir = subdir ?
|
|
7125
|
+
const targetDir = subdir ? join16(root, subdir) : root;
|
|
6548
7126
|
const where = subdir ? `${subdir}/` : "repo root";
|
|
6549
7127
|
const inSubdir = subdir ? ` in ${subdir}/` : "";
|
|
6550
7128
|
if (PACKAGE_MANAGERS.has(exe)) {
|
|
6551
|
-
const nodeModules =
|
|
7129
|
+
const nodeModules = join16(targetDir, "node_modules");
|
|
6552
7130
|
if (!existsSync(nodeModules)) {
|
|
6553
7131
|
if (fix) {
|
|
6554
7132
|
try {
|
|
@@ -6583,7 +7161,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
|
|
|
6583
7161
|
detail: PACKAGE_MANAGERS.has(exe) ? `${command} (dependencies present; script not statically verifiable)` : `${command} (non-script executable, not statically verifiable)`
|
|
6584
7162
|
};
|
|
6585
7163
|
}
|
|
6586
|
-
const pkgPath =
|
|
7164
|
+
const pkgPath = join16(targetDir, "package.json");
|
|
6587
7165
|
let pkgRaw;
|
|
6588
7166
|
try {
|
|
6589
7167
|
pkgRaw = await readFile8(pkgPath, "utf8");
|
|
@@ -6619,7 +7197,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
|
|
|
6619
7197
|
pkg.scripts = { ...scripts, typecheck: "tsc --noEmit" };
|
|
6620
7198
|
const indent = detectIndent(pkgRaw);
|
|
6621
7199
|
const trailingNewline = pkgRaw.endsWith("\n") ? "\n" : "";
|
|
6622
|
-
await
|
|
7200
|
+
await writeFile14(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
|
|
6623
7201
|
return {
|
|
6624
7202
|
name: "pre-push test",
|
|
6625
7203
|
ok: true,
|
|
@@ -6696,7 +7274,7 @@ function checkBinary(name, command) {
|
|
|
6696
7274
|
}
|
|
6697
7275
|
|
|
6698
7276
|
// src/commands/version.ts
|
|
6699
|
-
var CLI_VERSION = true ? "0.2.
|
|
7277
|
+
var CLI_VERSION = true ? "0.2.34" : "0.0.0-dev";
|
|
6700
7278
|
function registerVersion(program2) {
|
|
6701
7279
|
program2.command("version").description("Print the CLI version").action(() => {
|
|
6702
7280
|
process.stdout.write(CLI_VERSION + "\n");
|
|
@@ -6723,6 +7301,7 @@ registerMultiWork(program);
|
|
|
6723
7301
|
registerResume(program);
|
|
6724
7302
|
registerReset(program);
|
|
6725
7303
|
registerScan(program);
|
|
7304
|
+
registerSeoFeedback(program);
|
|
6726
7305
|
registerSlackImport(program);
|
|
6727
7306
|
registerFastTrack(program);
|
|
6728
7307
|
registerPrTest(program);
|