@awak-app/simy-cli 0.7.3 → 0.7.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/README.md CHANGED
@@ -15,7 +15,7 @@ cached in memory, so restarting the host may retrieve live contexts again.
15
15
 
16
16
  `simy --version` prints the installed CLI version without starting an agent or
17
17
  checking for updates. `simy doctor` performs read-only local connection and
18
- provider login checks and prints a JSON diagnostic report: installed/running
18
+ provider login and SIMY provider-link checks and prints a JSON diagnostic report: installed/running
19
19
  versions, stale discovery records, session expiry, provider readiness, and recent
20
20
  structured SQM failures, plus `host_exit` with the latest native desktop host's
21
21
  abnormal exit time and status. `failures.total_failures` counts scoped SQM
@@ -1095,3 +1095,11 @@ Diagnosis now returns an issue-first worksheet in `analysis.hypothesisSheet.requ
1095
1095
  Default issue selection is the first eight original acceptance criteria (or `objective` when absent); `omittedIssueCount` and `issueDescriptionsTruncated` disclose bounded input. Use an explicit issue list to focus on other original issues, including a prior `rule:*` or `objective` row. The latest retained runtime worksheet is supplied as context; this read-only command neither writes nor replaces the runtime ledger.
1096
1096
 
1097
1097
  Manual diagnosis and delivery reasoning share the scoped SQM knowledge loader. Partial environment overrides cannot mix a token, account, organization or endpoint with saved credentials; unavailable knowledge is reported without blocking the diagnosis. The brief response retains `knowledge.status`, `reason`, `suppliedCount`, and `bundleDigest`, separately from `knowledgeReferenceCount` and test execution (always zero for diagnosis). Truncated evidence is disclosed to both the analyst and the caller.
1098
+
1099
+ Provider diagnostics distinguish `authentication`, `connection`, and `connection_ready`.
1100
+ If both login and SIMY connection are missing, `actions` lists both remedies in one
1101
+ report. Connection readiness does not certify model access or task completion.
1102
+ Diagnostics do not reconnect providers or enable existing tasks. Explicit refresh
1103
+ waits for a fresh probe when a pre-login check is already running. macOS login
1104
+ terminals preserve an explicitly configured `CODEX_HOME` or `CLAUDE_CONFIG_DIR`,
1105
+ without copying credentials.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.7.3";
16
+ export const CLI_VERSION = "0.7.4";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function narrativeImageCapabilityMetadata() {
@@ -39,7 +39,7 @@ import { createCliAutoUpdater, readCliPackageInfo } from "./auto-update.js";
39
39
  import { createAgentShutdown } from "./shutdown.js";
40
40
  import { resolveWebOrigin } from "./web-origin.js";
41
41
  import { openAuthorizationUrl } from "./browser.js";
42
- import { inspectBackendExecutable } from "./backend-executable.js";
42
+ import { inspectBackendExecutable, CODEX_WRITER_LOCK_MINIMUM_VERSION } from "./backend-executable.js";
43
43
  import { inspectSqmAgentSkill } from "./sqm/agent-skill.js";
44
44
  import { startWorkflowUpdates, syncInstalledWorkflows } from "./sqm/workflow-update.js";
45
45
  import { createNativeDevelopmentReportsApi, normalizeNativeDevelopmentReportSave } from "./native-development-reports.js";
@@ -1442,7 +1442,7 @@ export async function openProviderLogin(provider, root, {
1442
1442
  execFileImpl = promisify(execFile), platform = process.platform, env = process.env,
1443
1443
  } = {}) {
1444
1444
  if (provider !== "codex" && provider !== "claude") throw new Error("Unsupported provider.");
1445
- const executable = await inspectExecutable(provider);
1445
+ const executable = await inspectExecutable(provider, provider === "codex" ? { minimumVersion: CODEX_WRITER_LOCK_MINIMUM_VERSION } : {});
1446
1446
  if (!executable.available || !executable.executable) throw Object.assign(new Error("Provider is not installed."), { code: "provider_not_installed" });
1447
1447
  const label = provider === "claude" ? "Claude Code" : "Codex";
1448
1448
  const args = provider === "claude" ? ["auth", "login"] : ["login"];
@@ -1456,7 +1456,11 @@ export async function openProviderLogin(provider, root, {
1456
1456
  const directory = await mkdtemp(join(root, `${provider}-login-`));
1457
1457
  const script = join(directory, `Sign in to ${label}.command`);
1458
1458
  const quote = (s) => `'${s.replaceAll("'", "'\\''")}'`;
1459
- await writeFile(script, `#!/bin/bash\nexport PATH=${quote(env.PATH || "/usr/bin:/bin")}\n${quote(executable.executable)} ${args.join(" ")}\nresult=$?\nif [ "$result" -ne 0 ]; then\n echo "Sign-in failed. Check the error above, then press Return to close."\n read -r _\nfi\nrm -f -- ${quote(script)}\nrmdir -- ${quote(directory)}\nexit "$result"\n`, { mode: 0o700 });
1459
+ // Terminal does not inherit custom provider homes from the desktop host.
1460
+ // Preserve only path settings, never copy tokens or credential contents.
1461
+ const homeKey = provider === "codex" ? "CODEX_HOME" : "CLAUDE_CONFIG_DIR";
1462
+ const providerHome = typeof env[homeKey] === "string" && env[homeKey] ? `export ${homeKey}=${quote(env[homeKey])}\n` : "";
1463
+ await writeFile(script, `#!/bin/bash\n${providerHome}export PATH=${quote(env.PATH || "/usr/bin:/bin")}\n${quote(executable.executable)} ${args.join(" ")}\nresult=$?\nif [ "$result" -ne 0 ]; then\n echo "Sign-in failed. Check the error above, then press Return to close."\n read -r _\nfi\nrm -f -- ${quote(script)}\nrmdir -- ${quote(directory)}\nexit "$result"\n`, { mode: 0o700 });
1460
1464
  await chmod(script, 0o700);
1461
1465
  try { await execFileImpl("/usr/bin/open", ["-a", "Terminal", script], { timeout: 10_000 }); }
1462
1466
  catch (error) { await rm(directory, { recursive: true, force: true }); throw error; }
@@ -137,9 +137,19 @@ export function createProviderStatusInspector(provider, {
137
137
  let cached = null;
138
138
  let cachedAt = 0;
139
139
  let inFlight = null;
140
- return async ({ force = false } = {}) => {
140
+ const refreshAfterFlight = new WeakMap();
141
+ return async function readStatus({ force = false } = {}) {
141
142
  const current = now();
142
143
  if (!force && cached && current - cachedAt < cacheMs) return structuredClone(cached);
144
+ if (inFlight && force) {
145
+ // A login can finish while a previous signed-out probe is in flight.
146
+ // Explicit refresh must observe after that probe, not return its old result.
147
+ const previous = inFlight;
148
+ if (!refreshAfterFlight.has(previous)) {
149
+ refreshAfterFlight.set(previous, previous.catch(() => {}).then(() => readStatus({ force: true })));
150
+ }
151
+ return structuredClone(await refreshAfterFlight.get(previous));
152
+ }
143
153
  if (inFlight) return structuredClone(await inFlight);
144
154
  inFlight = Promise.resolve(inspect({ ...inspectOptions, now: () => new Date(now()) }))
145
155
  .then((result) => {
package/src/doctor.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readProviderLinks } from "./provider-links.js";
1
2
  import { purposeSigningKeyPath, purposeVerificationCapability } from "./purpose-local-proof.js";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { connectionDiscoveryPath } from "./connection-discovery.js";
@@ -7,7 +8,7 @@ import { readDiagnosticLog } from "./diagnostic-log.js";
7
8
  import { readHostExitDiagnostics } from "./host-exit-diagnostics.js";
8
9
 
9
10
  export async function collectDiagnostics({ webOrigin, cliVersion, root = defaultSessionRoot(),
10
- readSessionImpl = readSession, inspectProvider = inspectProviderStatus, fetchImpl = fetch,
11
+ readSessionImpl = readSession, inspectProvider = inspectProviderStatus, readLinks = readProviderLinks, fetchImpl = fetch,
11
12
  readFileImpl = readFile, alive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } }, now = Date.now() } = {}) {
12
13
  const session = await readSessionImpl(webOrigin, root).catch(() => null);
13
14
  const valid = isSessionValid(session, now, webOrigin);
@@ -27,10 +28,14 @@ export async function collectDiagnostics({ webOrigin, cliVersion, root = default
27
28
  background: health.daemon === true };
28
29
  }
29
30
  } catch (error) { if (error.code !== "ENOENT") daemon.state = "unverified"; }
31
+ const links = await readLinks(root).catch(() => ({}));
30
32
  const providers = Object.fromEntries(await Promise.all(["codex", "claude"].map(async provider => {
31
33
  const result = await inspectProvider(provider).catch(() => ({ state: "unknown" }));
32
34
  return [provider, { state: ["ready", "signed_out", "missing", "outdated", "unknown"].includes(result?.state) ? result.state : "unknown",
33
35
  version: typeof result?.version === "string" && /^\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?$/i.test(result.version) ? result.version : null,
36
+ connection: links[provider]?.settingsError || !links[provider] ? "unknown" : links[provider].connected ? "connected" : "disconnected",
37
+ authentication: result?.state === "ready" ? "signed_in" : result?.state === "signed_out" ? "signed_out" : "unknown",
38
+ connection_ready: result?.state === "ready" && links[provider]?.connected === true && !links[provider]?.settingsError,
34
39
  model_access: "unknown" }];
35
40
  })));
36
41
  const scope = session?.account_id && session?.organization_id ? { webOrigin, accountId: session.account_id, organizationId: session.organization_id } : null;
@@ -41,6 +46,11 @@ export async function collectDiagnostics({ webOrigin, cliVersion, root = default
41
46
  providers, failures, host_exit: await readHostExitDiagnostics(root),
42
47
  purpose_verification: await purposeVerificationCapability(purposeSigningKeyPath(root)).then(value => ({ state: value ? "available" : "not_initialized", key_id: value?.key_id || null, execution: "explicit_frozen_recipes_only" })).catch(() => ({ state: "unavailable", key_id: null, execution: "explicit_frozen_recipes_only" })),
43
48
  actions: [
49
+ ...Object.entries(providers).filter(([, provider]) => provider.connection === "disconnected")
50
+ .map(([provider]) => ({ id: `connect_${provider}`, action: "provider_connect", provider,
51
+ instruction: `Open SIMY settings and connect ${provider}. Provider login and SIMY connection are separate; existing tasks remain stopped.` })),
52
+ ...Object.entries(providers).filter(([, provider]) => provider.connection === "unknown")
53
+ .map(([provider]) => ({ id: `check_${provider}_connection`, instruction: "SIMY connection settings could not be read. Refresh connections; do not reset saved consent." })),
44
54
  ...(!valid ? [{ id: "reconnect_cli", command: "simy" }] : []),
45
55
  ...(["stale_record", "not_found", "unverified"].includes(daemon.state) ? [{ id: "inspect_daemon", command: "simy doctor" }] : []),
46
56
  ...(daemon.version_matches === false ? [{ id: "update_when_idle", command: "simy update --check" }] : []),