@sireai/optimus 0.1.1 → 0.1.2

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.
@@ -33,6 +33,7 @@ const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."
33
33
  const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
34
34
  const execFileAsync = promisify(execFile);
35
35
  const PACKAGE_NAME = "@sireai/optimus";
36
+ const JIRA_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "jira", "jira-cli.js");
36
37
  function renderSetupResult(result) {
37
38
  const lines = [];
38
39
  lines.push("Setup Complete");
@@ -162,7 +163,7 @@ function renderCliHelp() {
162
163
  "",
163
164
  "Integration commands:",
164
165
  " intake-status|intake-run|intake-disable|intake-enable|intake-reset-checkpoint",
165
- " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-poll-once|jira-poll-daemon",
166
+ " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-add-comment|jira-poll-once|jira-poll-daemon",
166
167
  "",
167
168
  "Global options:",
168
169
  " -h, --help Show this help",
@@ -173,24 +174,125 @@ function renderCliHelp() {
173
174
  ` - Run \`optimus setup\` once; Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`
174
175
  ].join("\n");
175
176
  }
177
+ function mapJiraCommand(command) {
178
+ switch (command) {
179
+ case "jira-search":
180
+ return "search";
181
+ case "jira-get-issue":
182
+ return "get-issue";
183
+ case "jira-search-assigned-open":
184
+ return "search-assigned-open";
185
+ case "jira-submit-issue":
186
+ return "submit-issue";
187
+ case "jira-submit-assigned-open":
188
+ return "submit-assigned-open";
189
+ case "jira-add-comment":
190
+ return "add-comment";
191
+ case "jira-poll-once":
192
+ return "poll-once";
193
+ case "jira-poll-daemon":
194
+ return "poll-daemon";
195
+ default:
196
+ return undefined;
197
+ }
198
+ }
199
+ function resolveJiraQuickDoctorIssues(config) {
200
+ const issues = [];
201
+ if (!config.baseUrl?.trim() || /jira\.example\.com/i.test(config.baseUrl)) {
202
+ issues.push({
203
+ code: "jira_base_url_missing",
204
+ message: "Jira integration is enabled, but Jira base URL is missing or still using the placeholder value.",
205
+ fix: "Rerun `optimus setup` and provide a real Jira base URL."
206
+ });
207
+ }
208
+ if (config.authType === "bearer") {
209
+ if (!config.bearerToken?.trim()) {
210
+ issues.push({
211
+ code: "jira_token_missing",
212
+ message: "Jira integration is enabled, but Jira bearer token is missing.",
213
+ fix: "Rerun `optimus setup` and provide a Jira token."
214
+ });
215
+ }
216
+ return issues;
217
+ }
218
+ if (config.authType === "basic") {
219
+ if (!config.username?.trim() || !(config.apiToken?.trim() || config.password?.trim())) {
220
+ issues.push({
221
+ code: "jira_basic_auth_missing",
222
+ message: "Jira integration is enabled, but Jira basic auth credentials are incomplete.",
223
+ fix: "Provide Jira username plus api token or password, then rerun `optimus doctor --quick`."
224
+ });
225
+ }
226
+ return issues;
227
+ }
228
+ if (!config.mcpUrl?.trim() || /jira-mcp\.example\.com/i.test(config.mcpUrl)) {
229
+ issues.push({
230
+ code: "jira_mcp_url_missing",
231
+ message: "Jira integration is enabled, but Jira MCP URL is missing or still using the placeholder value.",
232
+ fix: "Provide a real Jira MCP URL, then rerun `optimus doctor --quick`."
233
+ });
234
+ }
235
+ const requiredHeaders = [
236
+ "X-Atlassian-Jira-Url",
237
+ "X-Atlassian-Username",
238
+ "X-Atlassian-Jira-Personal-Token"
239
+ ];
240
+ const missingHeaders = requiredHeaders.filter((name) => !config.httpHeaders[name]?.trim());
241
+ if (missingHeaders.length > 0) {
242
+ issues.push({
243
+ code: "jira_mcp_headers_missing",
244
+ message: `Jira integration is enabled, but Jira MCP headers are incomplete: ${missingHeaders.join(", ")}.`,
245
+ fix: "Provide the required Jira MCP headers, then rerun `optimus doctor --quick`."
246
+ });
247
+ }
248
+ return issues;
249
+ }
250
+ async function forwardJiraCliCommand(command, commandArgs) {
251
+ const mappedCommand = mapJiraCommand(command);
252
+ if (!mappedCommand) {
253
+ throw new Error(`Unsupported Jira command forwarding target: ${command}`);
254
+ }
255
+ const jiraCliEntry = process.env.OPTIMUS_JIRA_CLI_ENTRY?.trim() || JIRA_CLI_ENTRY;
256
+ return await new Promise((resolvePromise, rejectPromise) => {
257
+ const child = spawn(process.execPath, [jiraCliEntry, mappedCommand, ...commandArgs], {
258
+ cwd: process.cwd(),
259
+ env: process.env,
260
+ stdio: "inherit"
261
+ });
262
+ child.on("error", rejectPromise);
263
+ child.on("close", (code) => {
264
+ resolvePromise(code ?? 1);
265
+ });
266
+ });
267
+ }
176
268
  async function readPackageVersion() {
177
269
  const raw = await readFile(PACKAGE_JSON_PATH, "utf8");
178
270
  const parsed = JSON.parse(raw);
179
271
  return parsed.version?.trim() || "0.0.0";
180
272
  }
181
- async function rerunCurrentCommand() {
182
- const scriptPath = process.argv[1];
183
- if (!scriptPath) {
184
- throw new Error("Unable to resolve current Optimus entry script.");
185
- }
186
- return new Promise((resolve, reject) => {
187
- const child = spawn(process.execPath, [scriptPath, ...process.argv.slice(2)], {
188
- stdio: "inherit",
189
- env: process.env
190
- });
191
- child.on("error", reject);
192
- child.on("close", (code) => resolve(code ?? 1));
193
- });
273
+ function isInteractiveSelfUpdatePromptAllowed() {
274
+ return Boolean(input.isTTY && output.isTTY);
275
+ }
276
+ async function promptForStartupSelfUpdate(currentVersion, latestVersion) {
277
+ const rl = createInterface({ input, output });
278
+ try {
279
+ console.log(`[optimus] update available ${currentVersion} -> ${latestVersion}.`);
280
+ console.log("1. Upgrade now and exit");
281
+ console.log("2. Continue with current version");
282
+ while (true) {
283
+ const answer = (await rl.question("Select [1/2] (default 2): ")).trim().toLowerCase();
284
+ if (!answer || answer === "2" || answer === "c" || answer === "continue") {
285
+ return "continue";
286
+ }
287
+ if (answer === "1" || answer === "u" || answer === "upgrade") {
288
+ return "upgrade";
289
+ }
290
+ console.log("Invalid selection. Enter 1 to upgrade or 2 to continue.");
291
+ }
292
+ }
293
+ finally {
294
+ rl.close();
295
+ }
194
296
  }
195
297
  async function maybeHandleStartupSelfUpdate(input) {
196
298
  const check = await checkForSelfUpdate({
@@ -222,7 +324,28 @@ async function maybeHandleStartupSelfUpdate(input) {
222
324
  if (!check.updateAvailable || !check.latestVersion) {
223
325
  return { handled: false };
224
326
  }
225
- if ((input.config.selfUpdate ?? buildDefaultConfig().selfUpdate).mode !== "auto" || input.command !== "start") {
327
+ const selfUpdateConfig = input.config.selfUpdate ?? buildDefaultConfig().selfUpdate;
328
+ if (input.command !== "start") {
329
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
330
+ return { handled: false };
331
+ }
332
+ if (selfUpdateConfig.mode === "prompt") {
333
+ if (!isInteractiveSelfUpdatePromptAllowed()) {
334
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
335
+ return { handled: false };
336
+ }
337
+ const choice = await promptForStartupSelfUpdate(check.currentVersion, check.latestVersion);
338
+ await input.logger.info("self_update.prompt_resolved", {
339
+ command: input.command,
340
+ currentVersion: check.currentVersion,
341
+ latestVersion: check.latestVersion,
342
+ choice
343
+ });
344
+ if (choice === "continue") {
345
+ return { handled: false };
346
+ }
347
+ }
348
+ else if (selfUpdateConfig.mode !== "auto") {
226
349
  console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
227
350
  return { handled: false };
228
351
  }
@@ -245,7 +368,10 @@ async function maybeHandleStartupSelfUpdate(input) {
245
368
  latestVersion: check.latestVersion,
246
369
  reason: install.state.lastError ?? install.reason ?? "unknown"
247
370
  });
248
- console.log(`[optimus] upgrade failed, continuing with ${check.currentVersion}.`);
371
+ console.log(`[optimus] upgrade failed. Continue with ${check.currentVersion}, or run \`optimus upgrade\` after fixing npm access.`);
372
+ if (selfUpdateConfig.mode === "auto") {
373
+ return { handled: true, exitCode: 1 };
374
+ }
249
375
  return { handled: false };
250
376
  }
251
377
  await input.logger.info("self_update.install_succeeded", {
@@ -253,10 +379,10 @@ async function maybeHandleStartupSelfUpdate(input) {
253
379
  currentVersion: check.currentVersion,
254
380
  latestVersion: check.latestVersion
255
381
  });
256
- console.log(`[optimus] upgrade succeeded, restarting ${input.command}...`);
382
+ console.log(`[optimus] upgrade succeeded. Restart with \`optimus ${input.command}\`.`);
257
383
  return {
258
384
  handled: true,
259
- exitCode: await rerunCurrentCommand()
385
+ exitCode: 0
260
386
  };
261
387
  }
262
388
  async function pathExists(path) {
@@ -839,20 +965,9 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
839
965
  });
840
966
  }
841
967
  if (config.jira.enabled) {
842
- if (!config.jira.baseUrl?.trim() || /jira\.example\.com/i.test(config.jira.baseUrl)) {
843
- blocking.push({
844
- code: "jira_base_url_missing",
845
- message: "Jira integration is enabled, but Jira base URL is missing or still using the placeholder value.",
846
- fix: "Rerun `optimus setup` and provide a real Jira base URL."
847
- });
848
- next.add("optimus setup");
849
- }
850
- if (!config.jira.bearerToken?.trim()) {
851
- blocking.push({
852
- code: "jira_token_missing",
853
- message: "Jira integration is enabled, but Jira token is missing.",
854
- fix: "Rerun `optimus setup` and provide a Jira token."
855
- });
968
+ const jiraIssues = resolveJiraQuickDoctorIssues(config.jira);
969
+ blocking.push(...jiraIssues);
970
+ if (jiraIssues.length > 0) {
856
971
  next.add("optimus setup");
857
972
  }
858
973
  }
@@ -890,7 +1005,18 @@ async function runSetup(args) {
890
1005
  if (!inspection.ok) {
891
1006
  throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
892
1007
  }
893
- const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection);
1008
+ const preflightConfig = configExists ? await loadConfig(configPath) : buildDefaultConfig();
1009
+ const preflightStore = new SQLiteTaskStore(preflightConfig.storage.rootDir);
1010
+ await preflightStore.init();
1011
+ const existingRepositories = await preflightStore.listRepositoryRoots();
1012
+ const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
1013
+ if (aliasConflict) {
1014
+ throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
1015
+ + "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
1016
+ }
1017
+ const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
1018
+ const resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
1019
+ const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
894
1020
  const reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
895
1021
  await mkdir(dirname(configPath), { recursive: true });
896
1022
  await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath })}\n`, "utf8");
@@ -902,14 +1028,10 @@ async function runSetup(args) {
902
1028
  const config = await loadConfig(configPath);
903
1029
  const store = new SQLiteTaskStore(config.storage.rootDir);
904
1030
  await store.init();
905
- const existingRepositories = await store.listRepositoryRoots();
906
- for (const repository of existingRepositories) {
907
- await store.removeRepositoryRoot(repository.path);
908
- }
909
1031
  await store.addRepositoryRoot({
910
1032
  path: normalizedRepoPath,
911
1033
  alias: answers.repoAlias,
912
- executionMode: previewExecutionPlan.resolvedDefaultMode
1034
+ executionMode: resolvedExecutionMode
913
1035
  });
914
1036
  const doctorQuick = await runQuickDoctor(configPath);
915
1037
  const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
@@ -922,7 +1044,7 @@ async function runSetup(args) {
922
1044
  path: normalizedRepoPath,
923
1045
  alias: answers.repoAlias,
924
1046
  workspaceKind: previewExecutionPlan.workspaceKind,
925
- executionMode: previewExecutionPlan.resolvedDefaultMode,
1047
+ executionMode: resolvedExecutionMode,
926
1048
  reviewSystem
927
1049
  },
928
1050
  deliveryChannels: config.delivery.channels,
@@ -1187,6 +1309,10 @@ async function main() {
1187
1309
  process.exitCode = result.doctorQuick.summary === "blocked" ? 1 : 0;
1188
1310
  return;
1189
1311
  }
1312
+ if (mapJiraCommand(command)) {
1313
+ process.exitCode = await forwardJiraCliCommand(command, commandArgs);
1314
+ return;
1315
+ }
1190
1316
  const parsedCommandArgs = parseArgs(commandArgs);
1191
1317
  if (command === "doctor" && parsedCommandArgs.quick === "true") {
1192
1318
  const result = await runQuickDoctor();
@@ -1205,7 +1331,7 @@ async function main() {
1205
1331
  }
1206
1332
  catch (error) {
1207
1333
  if (error?.code === "ENOENT") {
1208
- console.error(`Config file not found at ${resolveDefaultConfigPath()}. Run \`optimus setup\` first.`);
1334
+ console.error("Configuration is missing. Run `optimus setup` first.");
1209
1335
  process.exitCode = 1;
1210
1336
  return;
1211
1337
  }