@clue-ai/cli 0.0.18 → 0.0.19

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.
@@ -39,7 +39,9 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
39
39
  "For Next.js browser/client code, use only `NEXT_PUBLIC_CLUE_PROJECT_KEY`",
40
40
  "Do not read `process.env.CLUE_PROJECT_KEY`",
41
41
  "CLUE_API_BASE_URL` is not part of backend SDK initialization",
42
- "Do not add `@clue-ai/browser-sdk` or backend SDK dependencies with `*` or `latest`",
42
+ "Install Clue SDK dependencies through the latest channel",
43
+ "`@clue-ai/browser-sdk` must use `latest`",
44
+ "Python backend SDK dependencies must not be pinned",
43
45
  "Whitespace-only changes are allowed only on lines directly changed for Clue SDK wiring",
44
46
  "The local backend token endpoint is part of the customer app, not the Clue API",
45
47
  "send the same service key used by `ClueInit`",
@@ -60,6 +62,7 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
60
62
  "Reject frontend browser token providers that derive the Clue proxy URL from `NEXT_PUBLIC_API_URL`",
61
63
  "Reject frontend adapters that mix stale browser-token paths",
62
64
  "Reject frontend adapters that set `initialized = true` before calling `ClueInit`",
65
+ "Reject Clue SDK dependency entries that pin stale fixed versions",
63
66
  "Audit the setup diff against the Clue setup contract even when the code was written by another agent",
64
67
  "Reject whitespace-only edits, import sorting, formatter churn",
65
68
  "Reject unrelated refactors, renames, file moves",
@@ -147,6 +150,12 @@ const DEPENDENCY_FILE_CANDIDATES = [
147
150
  "poetry.lock",
148
151
  "Pipfile",
149
152
  ];
153
+ const PYTHON_BACKEND_DEPENDENCY_DECLARATION_FILES = [
154
+ "requirements.txt",
155
+ "requirements-dev.txt",
156
+ "pyproject.toml",
157
+ "Pipfile",
158
+ ];
150
159
  const exists = async (path) => {
151
160
  try {
152
161
  await access(path);
@@ -394,6 +403,20 @@ const setupSourcePaths = async ({ repoRoot, request, includeFrontend }) => {
394
403
  return existing.length ? existing : requested;
395
404
  };
396
405
 
406
+ const setupBackendRootPaths = (request) => {
407
+ const configuredRoots = [
408
+ ...(Array.isArray(request?.backend_root_paths)
409
+ ? request.backend_root_paths
410
+ : []),
411
+ request?.backend_root_path,
412
+ ]
413
+ .filter((root) => typeof root === "string" && root.trim())
414
+ .map((root) => root.trim());
415
+ return configuredRoots.length
416
+ ? [...new Set(configuredRoots)]
417
+ : (request?.allowed_source_paths ?? []);
418
+ };
419
+
397
420
  const secretLeakPatterns = [
398
421
  /pk_(live|test)_[A-Za-z0-9_-]+/,
399
422
  /sk_(live|test)_[A-Za-z0-9_-]+/,
@@ -776,17 +799,71 @@ const findNextLifecycleNonPublicEnvFiles = ({
776
799
  .map((source) => source.file_path);
777
800
  };
778
801
 
779
- const findWildcardFrontendSdkDependencyFiles = (dependencySources) =>
802
+ const findNonLatestFrontendSdkDependencyFiles = (dependencySources) =>
780
803
  packageJsonSourcesWithDependency(dependencySources, FRONTEND_SDK_PACKAGE)
781
804
  .filter((source) => {
782
805
  const version = packageJsonDependencyVersion(
783
806
  source.text,
784
807
  FRONTEND_SDK_PACKAGE,
785
808
  );
786
- return version === "*" || version === "latest";
809
+ return version !== "latest";
787
810
  })
788
811
  .map((source) => source.file_path);
789
812
 
813
+ const sourceIsPythonBackendDependencyDeclaration = (source) =>
814
+ PYTHON_BACKEND_DEPENDENCY_DECLARATION_FILES.some((fileName) =>
815
+ source.file_path.endsWith(fileName),
816
+ );
817
+
818
+ const backendSdkDependencyLineHasPinnedVersion = (line, packageName) => {
819
+ const stripped = line.trim();
820
+ if (!stripped || stripped.startsWith("#")) return false;
821
+ const withoutInlineComment = stripped.replace(/\s+#.*$/, "");
822
+ const packagePattern = new RegExp(
823
+ `(^|[\\s"'=,{\\[]+)${escapeRegex(packageName)}($|[\\s"'=<>~!@,;)}\\]]+)`,
824
+ "i",
825
+ );
826
+ if (!packagePattern.test(withoutInlineComment)) return false;
827
+
828
+ const requirementPattern = new RegExp(
829
+ `${escapeRegex(packageName)}(?:\\[[^\\]]+\\])?([^\\s"',;}\\]]*)`,
830
+ "i",
831
+ );
832
+ const requirementMatch = requirementPattern.exec(withoutInlineComment);
833
+ const suffix = requirementMatch?.[1] ?? "";
834
+ if (suffix && suffix !== ";") {
835
+ return true;
836
+ }
837
+
838
+ const keyPattern = new RegExp(
839
+ `^\\s*["']?${escapeRegex(packageName)}["']?\\s*=\\s*(.+)$`,
840
+ "i",
841
+ );
842
+ const keyMatch = keyPattern.exec(withoutInlineComment);
843
+ if (!keyMatch) return false;
844
+ const value = keyMatch[1].trim();
845
+ if (/^["']\*["']$/.test(value)) return false;
846
+ if (/version\s*=\s*["']\*["']/.test(value)) return false;
847
+ return true;
848
+ };
849
+
850
+ const findPinnedBackendSdkDependencyFiles = (
851
+ dependencySources,
852
+ packageNames,
853
+ ) =>
854
+ dependencySources
855
+ .filter(sourceIsPythonBackendDependencyDeclaration)
856
+ .filter((source) =>
857
+ source.text
858
+ .split(/\r?\n/)
859
+ .some((line) =>
860
+ packageNames.some((packageName) =>
861
+ backendSdkDependencyLineHasPinnedVersion(line, packageName),
862
+ ),
863
+ ),
864
+ )
865
+ .map((source) => source.file_path);
866
+
790
867
  const sourceLooksLikeBrowserTokenProvider = (source) => {
791
868
  const text = stripSourceNoise(source.text);
792
869
  return (
@@ -1053,6 +1130,10 @@ const checkSdkLifecycle = ({
1053
1130
  const backendSdkDependencyPresent =
1054
1131
  !backendPresent ||
1055
1132
  dependencyHasAnyPackage(dependencySources, backendSpec.packages);
1133
+ const pinnedBackendSdkDependencyFiles = findPinnedBackendSdkDependencyFiles(
1134
+ dependencySources,
1135
+ backendSpec.packages,
1136
+ );
1056
1137
  const backendSdkImportPresent =
1057
1138
  !backendPresent ||
1058
1139
  sourcesHavePythonImport(backendSources, backendSpec.imports);
@@ -1079,8 +1160,8 @@ const checkSdkLifecycle = ({
1079
1160
  dependencySources,
1080
1161
  frontendSources,
1081
1162
  });
1082
- const wildcardFrontendSdkDependencyFiles =
1083
- findWildcardFrontendSdkDependencyFiles(dependencySources);
1163
+ const nonLatestFrontendSdkDependencyFiles =
1164
+ findNonLatestFrontendSdkDependencyFiles(dependencySources);
1084
1165
  const browserTokenProviderMissingServiceKeyFiles =
1085
1166
  findBrowserTokenProviderMissingServiceKeyFiles(frontendSources);
1086
1167
  const browserTokenProviderWrongProxyPathFiles =
@@ -1143,6 +1224,7 @@ const checkSdkLifecycle = ({
1143
1224
  ? null
1144
1225
  : backendSpec.blocker,
1145
1226
  sdk_dependency_present: backendSdkDependencyPresent,
1227
+ pinned_sdk_dependency_files: pinnedBackendSdkDependencyFiles,
1146
1228
  sdk_import_present: backendSdkImportPresent,
1147
1229
  sdk_dependency_or_import_present: backendSdkPresent,
1148
1230
  sdk_init_present: backendInitPresent,
@@ -1164,7 +1246,7 @@ const checkSdkLifecycle = ({
1164
1246
  frontendLifecycleFilesWithoutVerifiedSdk,
1165
1247
  wrong_sdk_packages: wrongFrontendSdkPackages,
1166
1248
  next_lifecycle_non_public_env_files: nextLifecycleNonPublicEnvFiles,
1167
- wildcard_sdk_dependency_files: wildcardFrontendSdkDependencyFiles,
1249
+ non_latest_sdk_dependency_files: nonLatestFrontendSdkDependencyFiles,
1168
1250
  browser_token_provider_missing_service_key_files:
1169
1251
  browserTokenProviderMissingServiceKeyFiles,
1170
1252
  browser_token_provider_wrong_proxy_path_files:
@@ -1194,13 +1276,14 @@ const checkSdkLifecycle = ({
1194
1276
  missingApis.length === 0 &&
1195
1277
  backendSdkPresent &&
1196
1278
  backendSdkInstallabilityVerified &&
1279
+ pinnedBackendSdkDependencyFiles.length === 0 &&
1197
1280
  backendInitPresent &&
1198
1281
  backendMissingApis.length === 0 &&
1199
1282
  frontendSdkPresent &&
1200
1283
  frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
1201
1284
  wrongFrontendSdkPackages.length === 0 &&
1202
1285
  nextLifecycleNonPublicEnvFiles.length === 0 &&
1203
- wildcardFrontendSdkDependencyFiles.length === 0 &&
1286
+ nonLatestFrontendSdkDependencyFiles.length === 0 &&
1204
1287
  browserTokenProviderMissingServiceKeyFiles.length === 0 &&
1205
1288
  browserTokenProviderWrongProxyPathFiles.length === 0 &&
1206
1289
  browserTokenProviderNonClueEndpointEnvFiles.length === 0 &&
@@ -1414,7 +1497,7 @@ export const runSetupCheck = async ({
1414
1497
 
1415
1498
  if (requireSdkLifecycle) {
1416
1499
  const sdkLifecycle = checkSdkLifecycle({
1417
- backendRootPaths: request?.allowed_source_paths ?? [],
1500
+ backendRootPaths: setupBackendRootPaths(request),
1418
1501
  dependencySources,
1419
1502
  framework: request?.framework,
1420
1503
  sources,
@@ -55,6 +55,21 @@ export const buildAiSetupHelp = () => ({
55
55
  "whitespace-only edits, import sorting, formatter churn, or comment/style cleanup outside the exact Clue SDK wiring lines",
56
56
  ],
57
57
  },
58
+ sdk_dependency_contract: {
59
+ frontend: {
60
+ package: "@clue-ai/browser-sdk",
61
+ install: "package-manager add @clue-ai/browser-sdk@latest",
62
+ rule:
63
+ "Frontend package.json must declare @clue-ai/browser-sdk as latest. Fixed versions and * are rejected by setup-check because stale SDKs break setup contracts.",
64
+ },
65
+ backend: {
66
+ fastapi_package: "clue-fastapi-sdk",
67
+ django_package: "clue-django-sdk",
68
+ install: "pip install --upgrade <backend-sdk-package>",
69
+ rule:
70
+ "Python backend SDK dependency declarations must be unpinned so pip resolves the latest release. Fixed ==, ~=, <=, >=, <, >, local path, and exact-version declarations are rejected by setup-check.",
71
+ },
72
+ },
58
73
  environment_contract: {
59
74
  nextjs_frontend_client_env: {
60
75
  variables: [
@@ -86,9 +101,21 @@ export const buildAiSetupHelp = () => ({
86
101
  owner: "ai_or_user",
87
102
  ai_agent_may_run: true,
88
103
  command: clueCliCommand("setup-doctor --local"),
89
- rule: "Run setup-doctor after local frontend/backend services and required env are available. It checks API connectivity only and does not replace user-operated setup-watch.",
104
+ rule: "Run setup-doctor after local frontend/backend services and required env are available. setup-agent blocks when required setup-doctor inputs are missing or connectivity fails, unless setup-doctor is explicitly skipped. It checks API connectivity only and does not replace user-operated setup-watch.",
90
105
  checked_hops: Object.keys(API_CONNECTIVITY_CONTRACT.hops),
91
106
  },
107
+ setup_agent: {
108
+ owner: "clue_cli",
109
+ command: clueCliCommand("setup-agent --repo ."),
110
+ providers: ["openai", "anthropic"],
111
+ env:
112
+ "Set exactly CLUE_AI_PROVIDER, CLUE_AI_PROVIDER_API_KEY, and CLUE_AI_MODEL. setup-agent uses the same AI provider configuration as the rest of the Clue CLI. Optional CLI overrides are --ai-provider, --ai-provider-api-key, and --ai-model; do not use ambiguous --api_key flags.",
113
+ model_role:
114
+ "Return a strict structured lifecycle plan only; the CLI owns scanning, edit application, checks, doctor preflight, retry, and final status.",
115
+ tool_calling:
116
+ "OpenAI uses Responses API forced function tools with strict schema and parallel_tool_calls false. Anthropic uses Messages API tool_choice with the same input schema.",
117
+ setup_watch_auto_run: false,
118
+ },
92
119
  completion_boundary: {
93
120
  ai_may_claim: [
94
121
  "Clue setup code changes were applied",
@@ -316,7 +316,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
316
316
  "Do not add per-call try/catch, try/except, `.catch`, or custom safe wrappers solely around official Clue SDK public lifecycle calls.",
317
317
  "Never await a Clue lifecycle call in a way that can block login, logout, account selection, request handling, page rendering, or API responses.",
318
318
  "Add or report the required SDK dependency instead of fabricating lifecycle APIs.",
319
- "For frontend code, add the real `@clue-ai/browser-sdk` dependency when missing. Do not invent `clue-js-sdk`, `@clue/browser-sdk`, local placeholder modules, or dynamic imports that hide a missing SDK.",
319
+ "For frontend code, add the real `@clue-ai/browser-sdk` dependency when missing. Use the latest channel (`@clue-ai/browser-sdk@latest`) so setup receives current SDK fixes. Do not invent `clue-js-sdk`, `@clue/browser-sdk`, local placeholder modules, or dynamic imports that hide a missing SDK.",
320
320
  `When lifecycle edits are clear, write an exact replacement plan to a temporary local JSON file and apply it with \`${clueCliCommand("lifecycle-apply --plan <plan-file> --repo .")}\`.`,
321
321
  "If npm/npx cannot fetch the Clue CLI package, report a blocker with the exact command and error instead of manually applying replacement plans.",
322
322
  "Delete the temporary lifecycle plan file after applying it unless the user explicitly asks to keep it for review.",
@@ -339,9 +339,11 @@ const skillBody = (name, { documentsUrl } = {}) => {
339
339
  "The backend browser token proxy must derive origin from trusted request headers or server request metadata. Do not forward `origin`, `projectKey`, or `environment` from JSON/body payload fields under server `CLUE_API_KEY`.",
340
340
  "For browser token proxy code, the service key sent to Clue must be the frontend `ClueInit` serviceKey from the browser request, not the backend service's `CLUE_SERVICE_KEY`.",
341
341
  "If a backend-owned browser token endpoint is implemented, read `CLUE_API_BASE_URL` from the backend env block and normalize it so values with or without a trailing `/api/v1` do not produce duplicate paths.",
342
- "For FastAPI code, add `clue-fastapi-sdk` to the backend dependency file when missing, import `clue_init_fastapi` plus `ClueIdentify`, `ClueSetAccount`, and `ClueLogout` where needed, and use `CLUE_PROJECT_KEY`, `CLUE_ENVIRONMENT`, `CLUE_API_KEY`, and `CLUE_INGEST_ENDPOINT` from the backend env block.",
342
+ "For FastAPI code, add unpinned `clue-fastapi-sdk` to the backend dependency file when missing so pip resolves the latest release, import `clue_init_fastapi` plus `ClueIdentify`, `ClueSetAccount`, and `ClueLogout` where needed, and use `CLUE_PROJECT_KEY`, `CLUE_ENVIRONMENT`, `CLUE_API_KEY`, and `CLUE_INGEST_ENDPOINT` from the backend env block.",
343
343
  "`CLUE_API_BASE_URL` is not part of backend SDK initialization. Use it only when the application backend owns a browser-token proxy for a frontend service.",
344
- "Do not add `@clue-ai/browser-sdk` or backend SDK dependencies with `*` or `latest`; use a concrete published version or package-manager-resolved semver range and update the repository lockfile when one exists.",
344
+ "Python backend SDK dependencies must not be pinned.",
345
+ "`@clue-ai/browser-sdk` must use `latest`.",
346
+ "Install Clue SDK dependencies through the latest channel. Frontend package managers must use `@clue-ai/browser-sdk@latest`; Python backend dependency declarations must not pin `clue-fastapi-sdk` or `clue-django-sdk` to a fixed version.",
345
347
  "Use `CLUE_SERVICE_KEY` as the canonical local service identifier. Do not ask the user to manage a separate producer id; SDKs should send producer id as the service key for setup verification compatibility.",
346
348
  "For frontend code, pass `serviceKey` from the frontend service env name written by `.env.clue` to `ClueInit`; in Next.js browser/client code that name is `NEXT_PUBLIC_CLUE_SERVICE_KEY`.",
347
349
  "For Django code, use `clue-django-sdk` only after package-manager or registry verification confirms it is installable; if it is not published or cannot be verified, report a blocker instead of adding a guessed dependency or import.",
@@ -381,7 +383,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
381
383
  "Reject broad ClueTrack instrumentation and DOM clue tags.",
382
384
  "Reject ClueTrack instrumentation unless the user explicitly requested product event tracking.",
383
385
  "Reject Next.js browser/client code that reads non-public `process.env.CLUE_*` variables.",
384
- "Reject Clue SDK dependency entries that use `*` or `latest` instead of a concrete published version or package-manager-resolved semver range.",
386
+ "Reject Clue SDK dependency entries that pin stale fixed versions. Frontend package.json must use `@clue-ai/browser-sdk: latest`; Python backend SDK dependency declarations must be unpinned or package-manager wildcard-latest.",
385
387
  "Reject Next.js browser token providers that do not read `NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT`.",
386
388
  "Confirm no project key, API key, secret, or env value appears in diff or report.",
387
389
  "Confirm lifecycle insertions are minimal and reviewable.",
@@ -402,7 +404,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
402
404
  "Confirm the semantic workflow does not send GitHub actor, triggering_actor, sender, repository owner, repository name, or default branch to Clue.",
403
405
  "Confirm `.clue/semantic-request.runtime.json` is not created, committed, or staged.",
404
406
  `Run \`${clueCliCommand("setup-check --framework <framework> --backend-root-path <path> --repo . --target <codex|claude_code> --require-sdk-lifecycle")}\` when possible.`,
405
- `Run \`${clueCliCommand("setup-doctor --local")}\` when local frontend/backend services are running and required env values are available. Report skipped_setup_doctor with the missing service URL or env name when it cannot run.`,
407
+ `Run \`${clueCliCommand("setup-doctor --local")}\` when local frontend/backend services are running. Missing setup-doctor inputs or failed API hops are blockers unless the user explicitly skipped setup-doctor.`,
406
408
  `Do not run \`${clueCliCommand("setup-watch --local")}\` automatically. setup-watch requires the user to operate real local frontend/backend services and login/logout/account flows.`,
407
409
  "If the user has not provided setup-watch or setup-screen evidence, report event delivery verification as `user_verification_pending` and do not state `setup completed`.",
408
410
  "Local static verification passed does not mean setup complete unless dependency install, SDK imports, app startup, and user-provided setup-watch or setup-screen event delivery were all verified.",
@@ -475,7 +477,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
475
477
  `- Setup API connectivity has four distinct hops: ${Object.entries(API_CONNECTIVITY_CONTRACT.hops)
476
478
  .map(([name, hop]) => `${name}=${hop.method} ${hop.path}`)
477
479
  .join(", ")}.`,
478
- `- Run \`${clueCliCommand("setup-doctor --local")}\` when local services and required env are available. This checks API connectivity only; it does not replace user-operated setup-watch.`,
480
+ `- Run \`${clueCliCommand("setup-doctor --local")}\` when local services are running. Missing setup-doctor inputs or failed API hops are blockers unless the user explicitly skipped setup-doctor. This checks API connectivity only; it does not replace user-operated setup-watch.`,
479
481
  "- Do not implement or refresh semantic snapshot CI during lifecycle placement; report a blocker if generated semantic artifacts are missing or stale.",
480
482
  `- Do not run \`${clueCliCommand("setup-watch --local")}\` automatically. setup-watch and the Clue setup screen are user-operated verification steps, not implementation-agent responsibility.`,
481
483
  "- The full setup must start with `clue-setup-orchestrator`.",