@sireai/optimus 0.1.21 → 0.1.23

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.
@@ -292,12 +292,16 @@ function renderCommandHelp(command) {
292
292
  "",
293
293
  "Usage:",
294
294
  " optimus submit --title <title> --description <text> [options]",
295
+ " optimus submit --title <title> --description-file <path> [options]",
296
+ " printf '%s' '<text>' | optimus submit --title <title> --description-stdin [options]",
295
297
  "",
296
298
  "Required:",
297
299
  " --title <title> Task title",
298
300
  " --description <text> Task description",
299
301
  "",
300
302
  "Common options:",
303
+ " --description-file <path> Read task description from a file",
304
+ " --description-stdin Read task description from stdin",
301
305
  " --repo <alias> Target registered repository alias",
302
306
  " --task-type <type> Task type, default bugfix",
303
307
  " --source-ref <id> External issue key or source reference",
@@ -311,11 +315,17 @@ function renderCommandHelp(command) {
311
315
  "",
312
316
  "Usage:",
313
317
  " optimus feedback --title <title> --description <text>",
318
+ " optimus feedback --title <title> --description-file <path>",
319
+ " printf '%s' '<text>' | optimus feedback --title <title> --description-stdin",
314
320
  "",
315
321
  "Required:",
316
322
  " --title <title> Feedback title",
317
323
  " --description <text> Feedback description",
318
324
  "",
325
+ "Optional text sources:",
326
+ " --description-file <path> Read feedback description from a file",
327
+ " --description-stdin Read feedback description from stdin",
328
+ "",
319
329
  "Behavior:",
320
330
  " - writes a local feedback bundle under ~/.optimus/runtime/feedback",
321
331
  " - creates a .tar.gz archive for the bundle",
@@ -325,7 +335,8 @@ function renderCommandHelp(command) {
325
335
  " - sends a Feishu app card message with the feedback document link",
326
336
  "",
327
337
  "Example:",
328
- " optimus feedback --title \"Upgrade prompt missing\" --description \"upgrade --check found 0.1.14 but start did not open the upgrade prompt.\""
338
+ " optimus feedback --title \"Upgrade prompt missing\" --description \"upgrade --check found 0.1.14 but start did not open the upgrade prompt.\"",
339
+ " optimus feedback --title \"Shell quoting issue\" --description-file ./feedback.txt"
329
340
  ].join("\n"),
330
341
  "retry-task": [
331
342
  "optimus retry-task",
@@ -675,13 +686,6 @@ function resolveJiraQuickDoctorIssues(config, options) {
675
686
  fix: "Rerun `optimus setup` and provide a Jira personal token."
676
687
  });
677
688
  }
678
- if (!config.casCookieName?.trim()) {
679
- issues.push({
680
- code: "jira_cas_cookie_name_missing",
681
- message: "Jira integration is enabled, but Jira CAS cookie name is missing.",
682
- fix: "Rerun `optimus setup` and provide the Jira CAS cookie name."
683
- });
684
- }
685
689
  return issues;
686
690
  }
687
691
  function resolveSentryQuickDoctorIssues(config) {
@@ -1185,9 +1189,6 @@ async function promptSetupAnswers(defaults) {
1185
1189
  const jiraPersonalToken = enableJira
1186
1190
  ? (printSetupHint("Jira personal token: required for Jira reads, comments, and issue submission commands."), (await ask(renderSetupPrompt("Required", "Jira personal token", defaults.jiraPersonalToken ? "configured" : ""))).trim() || defaults.jiraPersonalToken)
1187
1191
  : undefined;
1188
- const jiraCasCookieName = enableJira
1189
- ? (printSetupHint("Jira CAS cookie name: optional helper for environments that need refreshed CAS login cookies."), (await ask(renderSetupPrompt("Optional", "Jira CAS cookie name", defaults.jiraCasCookieName ?? "_aegis_cas_p"))).trim() || defaults.jiraCasCookieName || "_aegis_cas_p")
1190
- : undefined;
1191
1192
  printSetupSection("Sentry", "Optional. Turn this on only if you want this machine to read Sentry events and create tasks from them.");
1192
1193
  printSetupHint("If disabled, Optimus still works; Sentry commands just stay unavailable on this machine.");
1193
1194
  const enableSentryInput = (await ask(renderSetupPrompt("Optional integration", "Enable Sentry integration", defaults.enableSentry ? "Y/n" : "y/N"))).trim().toLowerCase();
@@ -1216,7 +1217,6 @@ async function promptSetupAnswers(defaults) {
1216
1217
  enableJira,
1217
1218
  ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
1218
1219
  ...(jiraPersonalToken ? { jiraPersonalToken } : {}),
1219
- ...(jiraCasCookieName ? { jiraCasCookieName } : {}),
1220
1220
  ...(defaults.jiraCustomHeaders ? { jiraCustomHeaders: defaults.jiraCustomHeaders } : {}),
1221
1221
  enableSentry,
1222
1222
  ...(sentryBaseUrl ? { sentryBaseUrl } : {}),
@@ -1266,7 +1266,6 @@ async function resolveDefaultSetupAnswers() {
1266
1266
  enableJira: config.jira.enabled,
1267
1267
  ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
1268
1268
  ...(config.jira.personalToken ? { jiraPersonalToken: config.jira.personalToken } : {}),
1269
- ...(config.jira.casCookieName ? { jiraCasCookieName: config.jira.casCookieName } : {}),
1270
1269
  ...(config.jira.customHeaders ? { jiraCustomHeaders: config.jira.customHeaders } : {}),
1271
1270
  enableSentry: config.sentry.enabled,
1272
1271
  ...(config.sentry.baseUrl ? { sentryBaseUrl: config.sentry.baseUrl } : {}),
@@ -1464,7 +1463,7 @@ function buildSetupConfig(answers, rawConfig) {
1464
1463
  if (answers.jiraPersonalToken) {
1465
1464
  config.jira.personalToken = answers.jiraPersonalToken;
1466
1465
  }
1467
- config.jira.casCookieName = answers.jiraCasCookieName?.trim() || "_aegis_cas_p";
1466
+ config.jira.casCookieName = "_aegis_cas_p";
1468
1467
  if (answers.jiraCustomHeaders?.trim()) {
1469
1468
  config.jira.customHeaders = answers.jiraCustomHeaders.trim();
1470
1469
  }
@@ -1710,19 +1709,70 @@ function summarizeIssue(issue, fallback) {
1710
1709
  }
1711
1710
  return issue.message.replace(/\.$/u, "");
1712
1711
  }
1712
+ function isCodexModelSelectionFailure(healthCheck) {
1713
+ const normalized = healthCheck.summary.toLowerCase();
1714
+ return normalized.includes("model_not_found")
1715
+ || normalized.includes("unsupported model")
1716
+ || normalized.includes("invalid model")
1717
+ || (normalized.includes("requested model") && normalized.includes("does not exist"))
1718
+ || (normalized.includes("model") && normalized.includes("not supported"));
1719
+ }
1720
+ function isCodexQuotaFailure(healthCheck) {
1721
+ const normalized = healthCheck.summary.toLowerCase();
1722
+ return normalized.includes("quota exceeded")
1723
+ || normalized.includes("billing details")
1724
+ || normalized.includes("insufficient_quota");
1725
+ }
1726
+ function resolveCodexHealthFailurePresentation(healthCheck) {
1727
+ if (isCodexModelSelectionFailure(healthCheck)) {
1728
+ const model = healthCheck.diagnostics.model?.trim();
1729
+ return {
1730
+ reason: model
1731
+ ? `Configured Codex model is invalid or unsupported: ${model}.`
1732
+ : "Configured Codex model is invalid or unsupported.",
1733
+ fix: "Set `codex.model` to a supported model, then rerun `optimus doctor`."
1734
+ };
1735
+ }
1736
+ if (isCodexQuotaFailure(healthCheck)) {
1737
+ return {
1738
+ reason: "Codex quota is exhausted or billing is unavailable.",
1739
+ fix: "Check the configured OpenAI API key quota and billing status, then rerun `optimus doctor`."
1740
+ };
1741
+ }
1742
+ if (healthCheck.diagnostics.failureCategory === "provider_error") {
1743
+ return {
1744
+ reason: "model provider is unreachable.",
1745
+ fix: "Retry `optimus doctor` after fixing the Codex connection."
1746
+ };
1747
+ }
1748
+ if (healthCheck.diagnostics.failureCategory === "network_error") {
1749
+ return {
1750
+ reason: "network connectivity to Codex is unavailable.",
1751
+ fix: "Retry `optimus doctor` after fixing the Codex connection."
1752
+ };
1753
+ }
1754
+ if (healthCheck.diagnostics.failureCategory === "config_error") {
1755
+ return {
1756
+ reason: "Codex configuration is invalid.",
1757
+ fix: "Review `codex.model`, auth mode, and provider settings, then rerun `optimus doctor`."
1758
+ };
1759
+ }
1760
+ const authFixHint = healthCheck.diagnostics.auth?.fixHint?.trim();
1761
+ const summary = healthCheck.summary.trim().replace(/\.$/u, "");
1762
+ return {
1763
+ reason: summary || "Codex request could not complete.",
1764
+ fix: authFixHint && !healthCheck.diagnostics.auth?.authenticated
1765
+ ? authFixHint
1766
+ : "Inspect the Codex failure reason above, fix it, then rerun `optimus doctor`."
1767
+ };
1768
+ }
1713
1769
  function doctorNextActions(quick, healthCheck, delivery) {
1714
1770
  const actions = new Set();
1715
1771
  for (const step of quick.next) {
1716
1772
  actions.add(step);
1717
1773
  }
1718
1774
  if (!healthCheck.ok) {
1719
- const fixHint = healthCheck.diagnostics.auth?.fixHint?.trim();
1720
- if (fixHint) {
1721
- actions.add(fixHint);
1722
- }
1723
- else {
1724
- actions.add("Rerun `optimus setup`.");
1725
- }
1775
+ actions.add(resolveCodexHealthFailurePresentation(healthCheck).fix);
1726
1776
  }
1727
1777
  if (delivery.status === "not_ok" && delivery.fix) {
1728
1778
  actions.add(delivery.fix);
@@ -1801,6 +1851,7 @@ async function runDoctor(configPath = resolveDefaultConfigPath()) {
1801
1851
  };
1802
1852
  const authDiagnostics = healthCheck.diagnostics.auth;
1803
1853
  const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
1854
+ const codexHealthFailure = resolveCodexHealthFailurePresentation(healthCheck);
1804
1855
  const codexAuth = authDiagnostics?.authenticated
1805
1856
  ? { status: "ok" }
1806
1857
  : {
@@ -1819,12 +1870,8 @@ async function runDoctor(configPath = resolveDefaultConfigPath()) {
1819
1870
  ? { status: "ok" }
1820
1871
  : {
1821
1872
  status: "not_ok",
1822
- reason: healthCheck.diagnostics.failureCategory === "provider_error"
1823
- ? "model provider is unreachable."
1824
- : healthCheck.diagnostics.failureCategory === "network_error"
1825
- ? "network connectivity to Codex is unavailable."
1826
- : "Codex request could not complete.",
1827
- fix: authDiagnostics.fixHint?.trim() || "Retry `optimus doctor` after fixing the Codex connection."
1873
+ reason: codexHealthFailure.reason,
1874
+ fix: codexHealthFailure.fix
1828
1875
  };
1829
1876
  const codex = healthCheck.ok
1830
1877
  ? {
@@ -1836,10 +1883,10 @@ async function runDoctor(configPath = resolveDefaultConfigPath()) {
1836
1883
  status: "not_ok",
1837
1884
  reason: summarizeIssue(codexIssue, !authDiagnostics?.authenticated
1838
1885
  ? "authentication is not usable."
1839
- : healthCheck.diagnostics.failureCategory === "provider_error"
1840
- ? "model provider is unreachable."
1841
- : "Codex is not ready."),
1842
- fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`.",
1886
+ : codexHealthFailure.reason),
1887
+ fix: !authDiagnostics?.authenticated
1888
+ ? authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`."
1889
+ : codexHealthFailure.fix,
1843
1890
  auth: codexAuth,
1844
1891
  connectivity: codexConnectivity
1845
1892
  };
@@ -2168,6 +2215,44 @@ function parseArgs(argv) {
2168
2215
  function parsedCommandHasHelp(argv) {
2169
2216
  return argv.some((token) => token === "-h" || token === "--help");
2170
2217
  }
2218
+ async function readTextFromStdin() {
2219
+ const chunks = [];
2220
+ for await (const chunk of input) {
2221
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
2222
+ }
2223
+ return chunks.join("");
2224
+ }
2225
+ async function resolveLongTextArg(args, keys) {
2226
+ for (const key of keys.inline) {
2227
+ const value = args[key]?.trim();
2228
+ if (value) {
2229
+ return value;
2230
+ }
2231
+ }
2232
+ const filePath = keys.file ? args[keys.file]?.trim() : undefined;
2233
+ if (filePath) {
2234
+ const content = (await readFile(filePath, "utf8")).trim();
2235
+ if (content) {
2236
+ return content;
2237
+ }
2238
+ }
2239
+ if (keys.stdin && args[keys.stdin] === "true") {
2240
+ const content = (await readTextFromStdin()).trim();
2241
+ if (content) {
2242
+ return content;
2243
+ }
2244
+ }
2245
+ return undefined;
2246
+ }
2247
+ function renderShellQuotingHint(command) {
2248
+ return [
2249
+ `${command} requires both --title and --description.`,
2250
+ "Tip: shell metacharacters like `...`, $(...), and $VAR may be expanded before Optimus starts.",
2251
+ "Use single quotes, or pass the text with --description-file / --description-stdin.",
2252
+ `Example: optimus ${command} --title "..." --description 'literal text with \`optimus setup\` inside'`,
2253
+ `Example: printf '%s' 'literal text with \`optimus setup\` inside' | optimus ${command} --title "..." --description-stdin`
2254
+ ].join("\n");
2255
+ }
2171
2256
  async function main() {
2172
2257
  const argv = process.argv.slice(2);
2173
2258
  const [firstArg, ...rest] = argv;
@@ -2358,9 +2443,13 @@ async function main() {
2358
2443
  if (command === "submit") {
2359
2444
  const args = parseArgs(commandArgs);
2360
2445
  const title = args.title?.trim();
2361
- const description = (args.desc ?? args.description)?.trim();
2446
+ const description = await resolveLongTextArg(args, {
2447
+ inline: ["desc", "description"],
2448
+ file: "description-file",
2449
+ stdin: "description-stdin"
2450
+ });
2362
2451
  if (!title || !description) {
2363
- console.error("submit requires both --title and --description");
2452
+ console.error(renderShellQuotingHint("submit"));
2364
2453
  process.exitCode = 1;
2365
2454
  return;
2366
2455
  }
@@ -2383,9 +2472,13 @@ async function main() {
2383
2472
  if (command === "feedback") {
2384
2473
  const args = parseArgs(commandArgs);
2385
2474
  const title = args.title?.trim();
2386
- const description = (args.desc ?? args.description)?.trim();
2475
+ const description = await resolveLongTextArg(args, {
2476
+ inline: ["desc", "description"],
2477
+ file: "description-file",
2478
+ stdin: "description-stdin"
2479
+ });
2387
2480
  if (!title || !description) {
2388
- console.error("feedback requires both --title and --description");
2481
+ console.error(renderShellQuotingHint("feedback"));
2389
2482
  process.exitCode = 1;
2390
2483
  return;
2391
2484
  }