@enterpriseai/cli 3.19.2 → 3.19.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 +28 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +116 -35
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/eai-managed-deploy-customer-source.d.ts.map +1 -1
- package/dist/commands/eai-managed-deploy-customer-source.js +24 -0
- package/dist/commands/eai-managed-deploy-customer-source.js.map +1 -1
- package/dist/commands/eai-managed-deploy-github-dispatch.d.ts.map +1 -1
- package/dist/commands/eai-managed-deploy-github-dispatch.js +30 -0
- package/dist/commands/eai-managed-deploy-github-dispatch.js.map +1 -1
- package/dist/commands/eai-managed-source-move.js +2 -2
- package/dist/commands/eai-managed-source-move.js.map +1 -1
- package/dist/commands/env.js +15 -2
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/gofer.js +8 -1
- package/dist/commands/gofer.js.map +1 -1
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +187 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +6 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/provision.d.ts.map +1 -1
- package/dist/commands/provision.js +27 -13
- package/dist/commands/provision.js.map +1 -1
- package/dist/commands/publicapi.d.ts.map +1 -1
- package/dist/commands/publicapi.js +14 -2
- package/dist/commands/publicapi.js.map +1 -1
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +41 -20
- package/dist/commands/resources.js.map +1 -1
- package/dist/commands/support.d.ts +18 -0
- package/dist/commands/support.d.ts.map +1 -0
- package/dist/commands/support.js +110 -0
- package/dist/commands/support.js.map +1 -0
- package/dist/commands/tenant.js +39 -3
- package/dist/commands/tenant.js.map +1 -1
- package/dist/commands/types.d.ts +12 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +172 -35
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +89 -44
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/vertical.d.ts.map +1 -1
- package/dist/commands/vertical.js +163 -15
- package/dist/commands/vertical.js.map +1 -1
- package/dist/index.js +54 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-guide.d.ts.map +1 -1
- package/dist/lib/agent-guide.js +25 -4
- package/dist/lib/agent-guide.js.map +1 -1
- package/dist/lib/api.d.ts +25 -4
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +152 -39
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/cloud-env.d.ts +1 -0
- package/dist/lib/cloud-env.d.ts.map +1 -1
- package/dist/lib/cloud-env.js +28 -15
- package/dist/lib/cloud-env.js.map +1 -1
- package/dist/lib/config.d.ts +6 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +13 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/eai-managed-deploy-contract.d.ts +4 -0
- package/dist/lib/eai-managed-deploy-contract.d.ts.map +1 -1
- package/dist/lib/eai-managed-deploy-contract.js.map +1 -1
- package/dist/lib/eai-managed-deploy-operation.d.ts.map +1 -1
- package/dist/lib/eai-managed-deploy-operation.js +14 -0
- package/dist/lib/eai-managed-deploy-operation.js.map +1 -1
- package/dist/lib/eai-managed-deploy-state.d.ts.map +1 -1
- package/dist/lib/eai-managed-deploy-state.js +15 -1
- package/dist/lib/eai-managed-deploy-state.js.map +1 -1
- package/dist/lib/eai-managed-source-client-types.d.ts +3 -0
- package/dist/lib/eai-managed-source-client-types.d.ts.map +1 -1
- package/dist/lib/error-codes.d.ts.map +1 -1
- package/dist/lib/error-codes.js +6 -3
- package/dist/lib/error-codes.js.map +1 -1
- package/dist/lib/object-type-export-filesystem.d.ts +13 -0
- package/dist/lib/object-type-export-filesystem.d.ts.map +1 -0
- package/dist/lib/object-type-export-filesystem.js +112 -0
- package/dist/lib/object-type-export-filesystem.js.map +1 -0
- package/dist/lib/object-type-export.d.ts +13 -0
- package/dist/lib/object-type-export.d.ts.map +1 -0
- package/dist/lib/object-type-export.js +173 -0
- package/dist/lib/object-type-export.js.map +1 -0
- package/dist/lib/output.d.ts +1 -0
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +24 -1
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/platform-diagnostics.d.ts +21 -0
- package/dist/lib/platform-diagnostics.d.ts.map +1 -0
- package/dist/lib/platform-diagnostics.js +50 -0
- package/dist/lib/platform-diagnostics.js.map +1 -0
- package/dist/lib/profile.d.ts +9 -2
- package/dist/lib/profile.d.ts.map +1 -1
- package/dist/lib/profile.js +139 -4
- package/dist/lib/profile.js.map +1 -1
- package/dist/lib/schema-builder.d.ts +2 -0
- package/dist/lib/schema-builder.d.ts.map +1 -1
- package/dist/lib/schema-builder.js +5 -0
- package/dist/lib/schema-builder.js.map +1 -1
- package/dist/lib/support-context.d.ts +31 -0
- package/dist/lib/support-context.d.ts.map +1 -0
- package/dist/lib/support-context.js +193 -0
- package/dist/lib/support-context.js.map +1 -0
- package/dist/lib/support-redaction.d.ts +10 -0
- package/dist/lib/support-redaction.d.ts.map +1 -0
- package/dist/lib/support-redaction.js +44 -0
- package/dist/lib/support-redaction.js.map +1 -0
- package/dist/lib/support-report.d.ts +68 -0
- package/dist/lib/support-report.d.ts.map +1 -0
- package/dist/lib/support-report.js +165 -0
- package/dist/lib/support-report.js.map +1 -0
- package/package.json +3 -2
- package/resources/deploy/eai-app-template/.github/workflows/eai-app.yml +46 -13
- package/resources/deploy/eai-app-template/producer-pin.json +8 -8
- package/resources/deploy/eai-app-template/scripts/source-unknown-deployment-evidence.mjs +29 -2
- package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +99 -1
- package/resources/gofer/agents-skills/eai/SKILL.md +20 -0
- package/resources/gofer/claude-commands/eai.md +20 -0
- package/resources/gofer/claude-skills/eai/SKILL.md +20 -0
- package/resources/gofer/commands/0_gofer_start.md +18 -0
- package/resources/gofer/commands/gofer_eai_first_run.md +18 -0
- package/resources/gofer/copilot-prompts/eai.prompt.md +20 -0
- package/resources/gofer/gemini/commands/gofer/eai.md +20 -0
- package/resources/gofer/github-skills/eai/SKILL.md +20 -0
- package/resources/gofer/grok-skills/eai/SKILL.md +20 -0
- package/resources/gofer/instruction-templates/base/agents-base.md +18 -0
- package/resources/gofer/instruction-templates/base/claude-base.md +18 -0
- package/resources/gofer/instruction-templates/base/copilot-base.md +18 -0
- package/resources/gofer/instruction-templates/gofer/gofer-claude.md +4 -0
- package/resources/gofer/node-scripts/generate-commands.mjs +26 -0
- package/resources/gofer/references/platform/eai-support.md +19 -0
- package/resources/gofer/specify-commands/0_gofer_start.md +18 -0
- package/resources/gofer/specify-commands/gofer_eai_first_run.md +18 -0
- package/resources/gofer/system-skills/eai/SKILL.md +20 -0
- package/resources/linked-sources.json +2 -2
package/dist/commands/init.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export declare function resolveTemplateClonePlan(templateSource: string, version
|
|
|
34
34
|
export declare function buildTemplateInstallArgs(from: string): string[];
|
|
35
35
|
export declare function canRunTemplateScripts(from: string, trustTemplateScripts: boolean): boolean;
|
|
36
36
|
export declare function consumeLastInitBinding(): InitTenantAppBinding | undefined;
|
|
37
|
+
/** Scaffolds an app and its managed guidance, with optional AI tool selection for generated instructions. */
|
|
37
38
|
export declare const initCommand: Command;
|
|
38
39
|
export interface CreateCommandOptions {
|
|
39
40
|
from: string;
|
|
@@ -79,6 +80,7 @@ export declare function resolveCreateCompletionPublicApiUrl(publicApiUrl: string
|
|
|
79
80
|
* the operator's choice on a state-changing app create.
|
|
80
81
|
*/
|
|
81
82
|
export declare function resolveCreateTenantContext(publicApiUrl: string, options: CreateCommandOptions): ReturnType<typeof resolveActiveTenantContext>;
|
|
83
|
+
/** Builds init arguments from create's validated binding, onboarding answers, and selected AI tool. */
|
|
82
84
|
export declare function buildForwardedInitArgs(nameArg: string | undefined, options: CreateCommandOptions, answers?: CreateOnboardingAnswers, tenantId?: string): string[];
|
|
83
85
|
/**
|
|
84
86
|
* Next-step copy for guided create. A failed or unchecked builder readiness must
|
|
@@ -104,6 +106,8 @@ interface ExistingAppSelection {
|
|
|
104
106
|
}
|
|
105
107
|
/** Select one exact app enrollment and derive its recorded runtime tenant. */
|
|
106
108
|
export declare function selectExistingAppSelection(payload: unknown, requestedAppKey: string): ExistingAppSelection;
|
|
109
|
+
/** Serialize init writers; existing-target rename is atomic, not a filesystem compare-and-swap. */
|
|
110
|
+
export declare function updateInitProjectFile(targetDir: string, name: ".gitignore" | ".env.local", update: (content: Buffer) => Buffer, mode?: number): Promise<void>;
|
|
107
111
|
export declare function isMissingGitIdentity(error: unknown): boolean;
|
|
108
112
|
export declare function describeGitCommitFailure(error: unknown): string;
|
|
109
113
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsCpC,OAAO,EAEL,0BAA0B,EAI3B,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AA2BvB,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAgCxE;AAED,mGAAmG;AACnG,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAO1F;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWhE;AAgBD,KAAK,YAAY,GACb,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,aAAa,CAAC;AAsDlB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAsDD,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,OAAO,GACb,MAAM,CAmCR;AAUD,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAEvE;AAID;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlE;AAED,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,MAAM,EACtB,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,CAqCnB;AA4ED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,oBAAoB,EAAE,OAAO,GAC5B,OAAO,CAET;AAQD,wBAAgB,sBAAsB,IAAI,oBAAoB,GAAG,SAAS,CAIzE;AAED,6GAA6G;AAC7G,eAAO,MAAM,WAAW,SAiiBpB,CAAC;AAEL,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,SAgFtB,CAAC;AA0EL,wHAAwH;AACxH,wBAAgB,mCAAmC,CAAC,YAAY,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGnH;AAaD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,oBAAoB,GAC5B,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAM/C;AAED,uGAAuG;AACvG,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,oBAAoB,EAC7B,OAAO,CAAC,EAAE,uBAAuB,EACjC,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,EAAE,CAkCV;AAqND;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,OAAO,EACrB,MAAM,EAAE,YAAY,GACnB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAoBtC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAwKD,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAuGD,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAgBD,8EAA8E;AAC9E,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,MAAM,GACtB,oBAAoB,CAyCtB;AA+UD,mGAAmG;AACnG,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,GAAG,YAAY,EACjC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EACnC,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAyBf;AAkHD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK5D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAS/D"}
|
package/dist/commands/init.js
CHANGED
|
@@ -6,7 +6,8 @@ import { execFile } from "node:child_process";
|
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
-
import {
|
|
9
|
+
import { constants } from "node:fs";
|
|
10
|
+
import { readFile, writeFile, access, mkdir, rm, cp, mkdtemp, chmod, realpath, lstat, open, link, rename, unlink, } from "node:fs/promises";
|
|
10
11
|
import { basename, join, resolve } from "node:path";
|
|
11
12
|
import { randomBytes } from "node:crypto";
|
|
12
13
|
import ora from "ora";
|
|
@@ -19,7 +20,7 @@ import { browserLogin, isAuthenticated, loadTokens, resolveAuthConfig, storeToke
|
|
|
19
20
|
import { publicApiUrlForHomeRegion, resolveActiveTenantContext, resolveMainCompanyTenantId, resolvePublicApiUrl, } from "../lib/tenant-context.js";
|
|
20
21
|
import { buildTenantHierarchy, promptForTenantFromHierarchy, } from "../lib/tenant-hierarchy.js";
|
|
21
22
|
import { isManagedPublicRequestTimeout, parseApiError, readManagedPublicResponseText, PlatformAPIClient, } from "../lib/api.js";
|
|
22
|
-
import { findProjectRoot,
|
|
23
|
+
import { entraScopeEnvPatch, findProjectRoot, patchEnvContent } from "../lib/config.js";
|
|
23
24
|
import { pullCloudEnvValues } from "../lib/cloud-env.js";
|
|
24
25
|
import { findGuidance } from "../lib/error-guidance/match.js";
|
|
25
26
|
import { formatGuidanceText } from "../lib/error-guidance/render.js";
|
|
@@ -319,6 +320,7 @@ export function consumeLastInitBinding() {
|
|
|
319
320
|
lastInitBinding = undefined;
|
|
320
321
|
return binding;
|
|
321
322
|
}
|
|
323
|
+
/** Scaffolds an app and its managed guidance, with optional AI tool selection for generated instructions. */
|
|
322
324
|
export const initCommand = new Command("init")
|
|
323
325
|
.description("Scaffold a new application")
|
|
324
326
|
.argument("[name]", "Name for the app (kebab-case)")
|
|
@@ -340,6 +342,7 @@ export const initCommand = new Command("init")
|
|
|
340
342
|
.option("--binding-receipt-nonce <uuid>", "Fresh UUID binding this invocation to its private receipt")
|
|
341
343
|
.option("--app-key <key>", "Bind the local project to an existing app instead of creating a new app")
|
|
342
344
|
.option("--template-version <tag>", "Scaffold from a specific published app-template release (for example v1.2.0)")
|
|
345
|
+
.option("--tool <tool>", "AI tool to prepare for: codex, claude, vscode, grok, or antigravity")
|
|
343
346
|
.option("--no-splash", "Skip the interactive EAI wordmark")
|
|
344
347
|
.addHelpText("after", `
|
|
345
348
|
Gofer AI CLI assets are installed by default:
|
|
@@ -356,6 +359,7 @@ repo or local path.
|
|
|
356
359
|
Use --no-gofer only when you need a bare app scaffold.
|
|
357
360
|
`)
|
|
358
361
|
.action(async (nameArg, options) => {
|
|
362
|
+
const aiTool = validateCreateAiTool(options.tool);
|
|
359
363
|
if ((options.bindingReceipt !== undefined || options.bindingReceiptNonce !== undefined)
|
|
360
364
|
&& (!options.skipPrompts || !nameArg))
|
|
361
365
|
throw new Error("Init binding receipts require a named --skip-prompts invocation.");
|
|
@@ -525,13 +529,16 @@ Use --no-gofer only when you need a bare app scaffold.
|
|
|
525
529
|
// Step 3: Generate .env.local with placeholders
|
|
526
530
|
const envSpinner = startEaiStep("Generating .env.local...");
|
|
527
531
|
try {
|
|
532
|
+
await ensureLocalEnvIgnored(targetDir);
|
|
528
533
|
const envContent = generateEnvFile(initOptions);
|
|
529
|
-
await
|
|
534
|
+
await updateInitProjectFile(targetDir, ".env.local", () => Buffer.from(envContent), 0o600);
|
|
530
535
|
await hydrateEnvFromLoginContext(targetDir, initOptions.name, initOptions.parentTenantId, initOptions.tenantId, initOptions.tenantHomeRegion, initOptions.appKey);
|
|
531
536
|
envSpinner.succeed("Generated .env.local");
|
|
532
537
|
}
|
|
533
|
-
catch (
|
|
538
|
+
catch (err) {
|
|
534
539
|
envSpinner.fail("Failed to generate .env.local");
|
|
540
|
+
out.error(errMsg(err));
|
|
541
|
+
process.exit(1);
|
|
535
542
|
}
|
|
536
543
|
// Step 4: Generate Object Types scaffold
|
|
537
544
|
const typesSpinner = startEaiStep("Creating Object Types scaffold...");
|
|
@@ -697,6 +704,9 @@ Use --no-gofer only when you need a bare app scaffold.
|
|
|
697
704
|
out.nestedDim(`Run ${chalk.cyan("eai provision entra")} inside the project to set up Entra authentication.`);
|
|
698
705
|
}
|
|
699
706
|
out.nestedDim(`Template: ${templatePlan.displaySource}`);
|
|
707
|
+
if (aiTool) {
|
|
708
|
+
out.nestedInfo(`AI tool: ${CREATE_AI_TOOL_LABELS[aiTool]}. Run \`eai start\` in the project to open your AI workspace.`);
|
|
709
|
+
}
|
|
700
710
|
if (options.gofer) {
|
|
701
711
|
out.nestedDim("Gofer: run eai start to open this project in a detected AI workspace with the public EAI skill.");
|
|
702
712
|
}
|
|
@@ -820,6 +830,7 @@ export function resolveCreateTenantContext(publicApiUrl, options) {
|
|
|
820
830
|
tenantId: options.companyTenant || options.tenant,
|
|
821
831
|
});
|
|
822
832
|
}
|
|
833
|
+
/** Builds init arguments from create's validated binding, onboarding answers, and selected AI tool. */
|
|
823
834
|
export function buildForwardedInitArgs(nameArg, options, answers, tenantId) {
|
|
824
835
|
const appName = answers?.name || nameArg;
|
|
825
836
|
const args = [];
|
|
@@ -862,6 +873,9 @@ export function buildForwardedInitArgs(nameArg, options, answers, tenantId) {
|
|
|
862
873
|
if (options.packageProfile) {
|
|
863
874
|
args.push("--package-profile", options.packageProfile);
|
|
864
875
|
}
|
|
876
|
+
const aiTool = answers?.aiTool || validateCreateAiTool(options.tool);
|
|
877
|
+
if (aiTool)
|
|
878
|
+
args.push("--tool", aiTool);
|
|
865
879
|
return args;
|
|
866
880
|
}
|
|
867
881
|
async function runCreatePreflight() {
|
|
@@ -1103,24 +1117,27 @@ async function provisionEntraInline(targetDir, appName, tenantId, publicApiUrl)
|
|
|
1103
1117
|
out.warn(`The app registration ${result.clientId} exists, but the workspace allowlist was not updated. Run \`eai provision entra --force --debug\` after platform access is fixed.`);
|
|
1104
1118
|
return false;
|
|
1105
1119
|
}
|
|
1120
|
+
const scopeEnv = entraScopeEnvPatch(result.scopes);
|
|
1106
1121
|
if (result.clientSecret) {
|
|
1107
|
-
await
|
|
1122
|
+
await patchInitEnvFile(targetDir, {
|
|
1108
1123
|
ENTRA_CLIENT_ID: result.clientId,
|
|
1109
1124
|
ENTRA_CLIENT_SECRET: result.clientSecret,
|
|
1110
1125
|
AUTH_URL: authEndpointUrl,
|
|
1111
1126
|
NEXTAUTH_URL: authSiteUrl,
|
|
1112
1127
|
AUTH_TRUST_HOST: "true",
|
|
1128
|
+
...scopeEnv,
|
|
1113
1129
|
});
|
|
1114
1130
|
spinner.succeed(`Entra app registration ${result.existing ? "confirmed" : "created"}: ${chalk.dim(result.clientId)}`);
|
|
1115
1131
|
out.warn("The client secret has been written to .env.local and cannot be retrieved again.");
|
|
1116
1132
|
return true;
|
|
1117
1133
|
}
|
|
1118
1134
|
if (result.existing) {
|
|
1119
|
-
await
|
|
1135
|
+
await patchInitEnvFile(targetDir, {
|
|
1120
1136
|
ENTRA_CLIENT_ID: result.clientId,
|
|
1121
1137
|
AUTH_URL: authEndpointUrl,
|
|
1122
1138
|
NEXTAUTH_URL: authSiteUrl,
|
|
1123
1139
|
AUTH_TRUST_HOST: "true",
|
|
1140
|
+
...scopeEnv,
|
|
1124
1141
|
});
|
|
1125
1142
|
const hydratedSecret = await hydrateCloudSecret(targetDir, appName);
|
|
1126
1143
|
spinner.succeed(`Entra app registration confirmed: ${chalk.dim(result.clientId)}`);
|
|
@@ -1155,7 +1172,7 @@ async function hydrateCloudSecret(targetDir, appName) {
|
|
|
1155
1172
|
if (!secret) {
|
|
1156
1173
|
return false;
|
|
1157
1174
|
}
|
|
1158
|
-
await
|
|
1175
|
+
await patchInitEnvFile(targetDir, { ENTRA_CLIENT_SECRET: secret });
|
|
1159
1176
|
return true;
|
|
1160
1177
|
}
|
|
1161
1178
|
catch {
|
|
@@ -1503,6 +1520,9 @@ async function hydrateEnvFromLoginContext(targetDir, appName, parentTenantId, pl
|
|
|
1503
1520
|
const envKey = appName.replace(/-/g, "_").toUpperCase();
|
|
1504
1521
|
const regionalPublicApiUrl = publicApiUrlForHomeRegion(tenantHomeRegion);
|
|
1505
1522
|
const profileName = getActiveProfile();
|
|
1523
|
+
// Persist only the selector. Private profile auth/API settings and CLI tokens
|
|
1524
|
+
// remain in the user's local CLI configuration, outside the app repository.
|
|
1525
|
+
patches.EAI_PROFILE = profileName;
|
|
1506
1526
|
const profileConfig = await loadProfileConfig(profileName);
|
|
1507
1527
|
if (profileConfig?.publicApiUrl) {
|
|
1508
1528
|
patches.BASE_URL_PUBLIC_API = profileConfig.publicApiUrl;
|
|
@@ -1552,7 +1572,147 @@ async function hydrateEnvFromLoginContext(targetDir, appName, parentTenantId, pl
|
|
|
1552
1572
|
patches.NEXT_PUBLIC_EAI_APP_KEY = appKey;
|
|
1553
1573
|
}
|
|
1554
1574
|
if (Object.keys(patches).length > 0) {
|
|
1555
|
-
await
|
|
1575
|
+
await patchInitEnvFile(targetDir, patches);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/** Apply every init hydration/provisioning patch through the protected credential writer. */
|
|
1579
|
+
async function patchInitEnvFile(targetDir, patches) {
|
|
1580
|
+
await updateInitProjectFile(targetDir, ".env.local", content => Buffer.from(patchEnvContent(content.toString("utf8"), patches)), 0o600);
|
|
1581
|
+
}
|
|
1582
|
+
async function ensureLocalEnvIgnored(targetDir) {
|
|
1583
|
+
await updateInitProjectFile(targetDir, ".gitignore", (content) => {
|
|
1584
|
+
// Last-match precedence also protects custom templates which unignore env files.
|
|
1585
|
+
const separator = content.length && content[content.length - 1] !== 10 ? "\n" : "";
|
|
1586
|
+
return Buffer.concat([content, Buffer.from(`${separator}\n# Local app credentials and EAI CLI profile selector\n/.env.local\n/.env.local.eai-*.tmp\n`)]);
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
/** Serialize init writers; existing-target rename is atomic, not a filesystem compare-and-swap. */
|
|
1590
|
+
export async function updateInitProjectFile(targetDir, name, update, mode) {
|
|
1591
|
+
const root = await realpath(targetDir);
|
|
1592
|
+
const parent = await lstat(root);
|
|
1593
|
+
const lockPath = join(root, `${name}.eai-init.lock`);
|
|
1594
|
+
const lock = await open(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0), 0o600);
|
|
1595
|
+
const ownedLock = await lock.stat();
|
|
1596
|
+
const assertLock = async () => {
|
|
1597
|
+
const current = await lstat(lockPath);
|
|
1598
|
+
if (!current.isFile() || current.nlink !== 1 || current.dev !== ownedLock.dev || current.ino !== ownedLock.ino) {
|
|
1599
|
+
throw new Error(`Project ${name} initialization lock changed; inspect the project files before retrying.`);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
const releaseOwnedLock = async () => {
|
|
1603
|
+
await lock.close();
|
|
1604
|
+
try {
|
|
1605
|
+
const current = await lstat(lockPath);
|
|
1606
|
+
if (current.isFile() && current.nlink === 1 && current.dev === ownedLock.dev && current.ino === ownedLock.ino)
|
|
1607
|
+
await unlink(lockPath);
|
|
1608
|
+
}
|
|
1609
|
+
catch (error) {
|
|
1610
|
+
if (error.code !== "ENOENT")
|
|
1611
|
+
throw error;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
try {
|
|
1615
|
+
await assertLock();
|
|
1616
|
+
await updateLockedInitProjectFile(root, parent, name, update, mode, assertLock);
|
|
1617
|
+
}
|
|
1618
|
+
finally {
|
|
1619
|
+
await releaseOwnedLock();
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async function updateLockedInitProjectFile(root, parent, name, update, mode, assertLock) {
|
|
1623
|
+
const path = join(root, name);
|
|
1624
|
+
const maxBytes = 1024 * 1024;
|
|
1625
|
+
const unsafe = () => new Error(`Project ${name} must be an unlinked regular file within 1 MiB.`);
|
|
1626
|
+
const sameFile = (left, right) => left.dev === right.dev && left.ino === right.ino
|
|
1627
|
+
&& left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs
|
|
1628
|
+
&& left.mode === right.mode && right.isFile() && right.nlink === 1;
|
|
1629
|
+
const statLeaf = async () => {
|
|
1630
|
+
try {
|
|
1631
|
+
return await lstat(path);
|
|
1632
|
+
}
|
|
1633
|
+
catch (error) {
|
|
1634
|
+
if (error.code === "ENOENT")
|
|
1635
|
+
return null;
|
|
1636
|
+
throw error;
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
const original = await statLeaf();
|
|
1640
|
+
if (original && (!original.isFile() || original.isSymbolicLink() || original.nlink !== 1 || original.size > maxBytes))
|
|
1641
|
+
throw unsafe();
|
|
1642
|
+
let content = Buffer.alloc(0);
|
|
1643
|
+
if (original) {
|
|
1644
|
+
const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
|
|
1645
|
+
try {
|
|
1646
|
+
const opened = await file.stat();
|
|
1647
|
+
if (!sameFile(original, opened))
|
|
1648
|
+
throw unsafe();
|
|
1649
|
+
const buffer = Buffer.alloc(maxBytes + 1);
|
|
1650
|
+
let length = 0;
|
|
1651
|
+
while (length < buffer.length) {
|
|
1652
|
+
const { bytesRead } = await file.read(buffer, length, buffer.length - length, null);
|
|
1653
|
+
if (!bytesRead)
|
|
1654
|
+
break;
|
|
1655
|
+
length += bytesRead;
|
|
1656
|
+
}
|
|
1657
|
+
if (length !== original.size || length > maxBytes || !sameFile(original, await file.stat()))
|
|
1658
|
+
throw unsafe();
|
|
1659
|
+
content = buffer.subarray(0, length);
|
|
1660
|
+
}
|
|
1661
|
+
finally {
|
|
1662
|
+
await file.close();
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const assertUnchanged = async () => {
|
|
1666
|
+
const current = await statLeaf();
|
|
1667
|
+
const parentAfter = await lstat(root);
|
|
1668
|
+
if ((original ? !current || !sameFile(original, current) : current !== null)
|
|
1669
|
+
|| parent.dev !== parentAfter.dev || parent.ino !== parentAfter.ino || !parentAfter.isDirectory()) {
|
|
1670
|
+
throw new Error(`Project ${name} changed during initialization; retry after checking the project files.`);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
await assertUnchanged();
|
|
1674
|
+
const updated = update(content);
|
|
1675
|
+
const temporary = join(root, `${name}.eai-${randomBytes(16).toString("hex")}.tmp`);
|
|
1676
|
+
const file = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0), 0o600);
|
|
1677
|
+
const ownedTemporary = await file.stat();
|
|
1678
|
+
const removeOwnedTemporary = async () => {
|
|
1679
|
+
try {
|
|
1680
|
+
const current = await lstat(temporary);
|
|
1681
|
+
if (current.isFile() && current.dev === ownedTemporary.dev && current.ino === ownedTemporary.ino)
|
|
1682
|
+
await unlink(temporary);
|
|
1683
|
+
}
|
|
1684
|
+
catch (error) {
|
|
1685
|
+
if (error.code !== "ENOENT")
|
|
1686
|
+
throw error;
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
try {
|
|
1690
|
+
await file.writeFile(updated);
|
|
1691
|
+
await file.chmod(mode ?? (original ? original.mode & 0o777 : 0o666 & ~process.umask()));
|
|
1692
|
+
await file.sync();
|
|
1693
|
+
const written = await file.stat();
|
|
1694
|
+
if (!sameFile(written, await lstat(temporary)))
|
|
1695
|
+
throw unsafe();
|
|
1696
|
+
await assertLock();
|
|
1697
|
+
await assertUnchanged();
|
|
1698
|
+
// link publishes an absent target without replacing a concurrent occupant.
|
|
1699
|
+
// Existing targets are rechecked under the cooperative lock, then renamed.
|
|
1700
|
+
if (original)
|
|
1701
|
+
await rename(temporary, path);
|
|
1702
|
+
else {
|
|
1703
|
+
await link(temporary, path);
|
|
1704
|
+
await removeOwnedTemporary();
|
|
1705
|
+
}
|
|
1706
|
+
const published = await statLeaf();
|
|
1707
|
+
const parentAfter = await lstat(root);
|
|
1708
|
+
if (!published || !sameFile(await file.stat(), published)
|
|
1709
|
+
|| parent.dev !== parentAfter.dev || parent.ino !== parentAfter.ino || !parentAfter.isDirectory()) {
|
|
1710
|
+
throw new Error(`Project ${name} publication could not be verified; inspect the project files before retrying.`);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
finally {
|
|
1714
|
+
await file.close();
|
|
1715
|
+
await removeOwnedTemporary();
|
|
1556
1716
|
}
|
|
1557
1717
|
}
|
|
1558
1718
|
function toDisplayName(name) {
|
|
@@ -2298,6 +2458,25 @@ await client.resources.create('MyType', { title: 'Hello' });
|
|
|
2298
2458
|
| \`eai deploy trigger\` | Trigger deployment |
|
|
2299
2459
|
| \`eai verify\` | Platform connectivity check |
|
|
2300
2460
|
| \`eai doctor\` | Diagnose issues |
|
|
2461
|
+
| \`eai support\` | Review and approve a support report |
|
|
2462
|
+
|
|
2463
|
+
## Support after an unresolved EAI error
|
|
2464
|
+
|
|
2465
|
+
After an \`eai\` command fails, run \`eai errors explain <code-or-reason> --format json\`.
|
|
2466
|
+
Follow its diagnostics, approval requirements, and fixes once. Stop at its retry limit.
|
|
2467
|
+
If the command still fails, prepare a report with
|
|
2468
|
+
\`eai support --source harness --tool <current-tool> --format json\`.
|
|
2469
|
+
Use \`codex\`, \`claude\`, \`vscode\`, \`grok\`, or \`antigravity\` for \`<current-tool>\`.
|
|
2470
|
+
This command previews the locally redacted bundle in a noninteractive harness.
|
|
2471
|
+
Show the person the bundle summary and ask for explicit consent in the chat.
|
|
2472
|
+
Only after they approve that bundle, repeat the command with \`--yes --no-open\` and give them the returned link.
|
|
2473
|
+
If the bundle changes, show it again and ask for fresh consent. Never assume consent or run \`--yes\` automatically.
|
|
2474
|
+
Keep secrets, tokens, raw logs, and local \`.env\` values out of the chat.
|
|
2475
|
+
The CLI creates the draft; the AI never calls the support API itself.
|
|
2476
|
+
Report content stays in the draft body. The Support link fragment carries only its id and token.
|
|
2477
|
+
If sign-in is unavailable, give the person the plain Support page link returned by the CLI.
|
|
2478
|
+
In the welcome reply, say: If anything fails, say "get help" or type \`eai support\`; I can prepare a report for you to approve.
|
|
2479
|
+
If the person asks for help with a silent failure, start with the same report preview.
|
|
2301
2480
|
|
|
2302
2481
|
## App Delivery Checklist
|
|
2303
2482
|
|