@action-llama/action-llama 0.10.1 → 0.11.0
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/AGENTS.md +17 -6
- package/README.md +8 -4
- package/dist/agents/container-entry.d.ts.map +1 -1
- package/dist/agents/container-entry.js +37 -0
- package/dist/agents/container-entry.js.map +1 -1
- package/dist/agents/container-runner.d.ts +4 -0
- package/dist/agents/container-runner.d.ts.map +1 -1
- package/dist/agents/container-runner.js +82 -5
- package/dist/agents/container-runner.js.map +1 -1
- package/dist/agents/execution-engine.d.ts +2 -0
- package/dist/agents/execution-engine.d.ts.map +1 -1
- package/dist/agents/execution-engine.js +7 -16
- package/dist/agents/execution-engine.js.map +1 -1
- package/dist/agents/runner.d.ts +3 -0
- package/dist/agents/runner.d.ts.map +1 -1
- package/dist/agents/runner.js +47 -17
- package/dist/agents/runner.js.map +1 -1
- package/dist/cli/commands/chat.d.ts +5 -2
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +240 -23
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/cloud-deploy.d.ts.map +1 -1
- package/dist/cli/commands/cloud-deploy.js +5 -47
- package/dist/cli/commands/cloud-deploy.js.map +1 -1
- package/dist/cli/commands/cloud-setup.d.ts.map +1 -1
- package/dist/cli/commands/cloud-setup.js +20 -858
- package/dist/cli/commands/cloud-setup.js.map +1 -1
- package/dist/cli/commands/cloud-teardown.d.ts.map +1 -1
- package/dist/cli/commands/cloud-teardown.js +6 -126
- package/dist/cli/commands/cloud-teardown.js.map +1 -1
- package/dist/cli/commands/creds.d.ts.map +1 -1
- package/dist/cli/commands/creds.js +2 -4
- package/dist/cli/commands/creds.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +0 -3
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +76 -646
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/kill.d.ts +1 -1
- package/dist/cli/commands/kill.d.ts.map +1 -1
- package/dist/cli/commands/kill.js +26 -18
- package/dist/cli/commands/kill.js.map +1 -1
- package/dist/cli/commands/logs.d.ts.map +1 -1
- package/dist/cli/commands/logs.js +104 -58
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/pause.d.ts +1 -1
- package/dist/cli/commands/pause.d.ts.map +1 -1
- package/dist/cli/commands/pause.js +19 -19
- package/dist/cli/commands/pause.js.map +1 -1
- package/dist/cli/commands/resume.d.ts +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +19 -19
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +12 -54
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +22 -39
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/gateway-client.d.ts +12 -0
- package/dist/cli/gateway-client.d.ts.map +1 -0
- package/dist/cli/gateway-client.js +27 -0
- package/dist/cli/gateway-client.js.map +1 -0
- package/dist/cli/main.js +79 -42
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/with-command.d.ts +13 -0
- package/dist/cli/with-command.d.ts.map +1 -0
- package/dist/cli/with-command.js +45 -0
- package/dist/cli/with-command.js.map +1 -0
- package/dist/{shared/aws-constants.d.ts → cloud/aws/constants.d.ts} +3 -34
- package/dist/cloud/aws/constants.d.ts.map +1 -0
- package/dist/{shared/aws-constants.js → cloud/aws/constants.js} +3 -34
- package/dist/cloud/aws/constants.js.map +1 -0
- package/dist/cloud/{deploy-apprunner.d.ts → aws/deploy.d.ts} +6 -6
- package/dist/cloud/aws/deploy.d.ts.map +1 -0
- package/dist/cloud/{deploy-apprunner.js → aws/deploy.js} +2 -2
- package/dist/cloud/aws/deploy.js.map +1 -0
- package/dist/cloud/aws/iam.d.ts +37 -0
- package/dist/cloud/aws/iam.d.ts.map +1 -0
- package/dist/cloud/aws/iam.js +442 -0
- package/dist/cloud/aws/iam.js.map +1 -0
- package/dist/cloud/aws/provider.d.ts +69 -0
- package/dist/cloud/aws/provider.d.ts.map +1 -0
- package/dist/cloud/aws/provider.js +173 -0
- package/dist/cloud/aws/provider.js.map +1 -0
- package/dist/cloud/aws/provision.d.ts +9 -0
- package/dist/cloud/aws/provision.d.ts.map +1 -0
- package/dist/cloud/aws/provision.js +698 -0
- package/dist/cloud/aws/provision.js.map +1 -0
- package/dist/cloud/aws/teardown.d.ts +15 -0
- package/dist/cloud/aws/teardown.d.ts.map +1 -0
- package/dist/cloud/aws/teardown.js +74 -0
- package/dist/cloud/aws/teardown.js.map +1 -0
- package/dist/cloud/gcp/constants.d.ts +16 -0
- package/dist/cloud/gcp/constants.d.ts.map +1 -0
- package/dist/cloud/gcp/constants.js +16 -0
- package/dist/cloud/gcp/constants.js.map +1 -0
- package/dist/cloud/{deploy-cloudrun.d.ts → gcp/deploy.d.ts} +6 -6
- package/dist/cloud/gcp/deploy.d.ts.map +1 -0
- package/dist/cloud/{deploy-cloudrun.js → gcp/deploy.js} +7 -7
- package/dist/cloud/gcp/deploy.js.map +1 -0
- package/dist/cloud/gcp/iam.d.ts +20 -0
- package/dist/cloud/gcp/iam.d.ts.map +1 -0
- package/dist/cloud/gcp/iam.js +179 -0
- package/dist/cloud/gcp/iam.js.map +1 -0
- package/dist/cloud/gcp/provider.d.ts +28 -0
- package/dist/cloud/gcp/provider.d.ts.map +1 -0
- package/dist/cloud/gcp/provider.js +84 -0
- package/dist/cloud/gcp/provider.js.map +1 -0
- package/dist/cloud/gcp/provision.d.ts +14 -0
- package/dist/cloud/gcp/provision.d.ts.map +1 -0
- package/dist/cloud/gcp/provision.js +37 -0
- package/dist/cloud/gcp/provision.js.map +1 -0
- package/dist/cloud/gcp/teardown.d.ts +12 -0
- package/dist/cloud/gcp/teardown.d.ts.map +1 -0
- package/dist/cloud/gcp/teardown.js +67 -0
- package/dist/cloud/gcp/teardown.js.map +1 -0
- package/dist/cloud/image-builder.d.ts.map +1 -1
- package/dist/cloud/image-builder.js +115 -18
- package/dist/cloud/image-builder.js.map +1 -1
- package/dist/cloud/provider.d.ts +57 -0
- package/dist/cloud/provider.d.ts.map +1 -0
- package/dist/cloud/provider.js +21 -0
- package/dist/cloud/provider.js.map +1 -0
- package/dist/cloud/scheduler-image.d.ts.map +1 -1
- package/dist/cloud/scheduler-image.js +16 -3
- package/dist/cloud/scheduler-image.js.map +1 -1
- package/dist/cloud/state.d.ts +17 -0
- package/dist/cloud/state.d.ts.map +1 -0
- package/dist/cloud/state.js +48 -0
- package/dist/cloud/state.js.map +1 -0
- package/dist/docker/aws-shared.d.ts +6 -4
- package/dist/docker/aws-shared.d.ts.map +1 -1
- package/dist/docker/aws-shared.js +405 -23
- package/dist/docker/aws-shared.js.map +1 -1
- package/dist/docker/cloud-run-runtime.d.ts.map +1 -1
- package/dist/docker/cloud-run-runtime.js +10 -9
- package/dist/docker/cloud-run-runtime.js.map +1 -1
- package/dist/docker/ecs-runtime.d.ts +3 -1
- package/dist/docker/ecs-runtime.d.ts.map +1 -1
- package/dist/docker/ecs-runtime.js +78 -29
- package/dist/docker/ecs-runtime.js.map +1 -1
- package/dist/docker/image.d.ts +7 -0
- package/dist/docker/image.d.ts.map +1 -1
- package/dist/docker/image.js +31 -4
- package/dist/docker/image.js.map +1 -1
- package/dist/docker/lambda-runtime.js +1 -1
- package/dist/docker/lambda-runtime.js.map +1 -1
- package/dist/docker/local-runtime.js +4 -4
- package/dist/docker/local-runtime.js.map +1 -1
- package/dist/docker/network.d.ts.map +1 -1
- package/dist/docker/network.js +2 -2
- package/dist/docker/network.js.map +1 -1
- package/dist/docker/runtime.d.ts +23 -0
- package/dist/docker/runtime.d.ts.map +1 -1
- package/dist/gateway/api-key.d.ts +10 -0
- package/dist/gateway/api-key.d.ts.map +1 -0
- package/dist/gateway/api-key.js +19 -0
- package/dist/gateway/api-key.js.map +1 -0
- package/dist/gateway/auth.d.ts +16 -0
- package/dist/gateway/auth.d.ts.map +1 -0
- package/dist/gateway/auth.js +60 -0
- package/dist/gateway/auth.js.map +1 -0
- package/dist/gateway/index.d.ts +2 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +38 -4
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/rate-limiter.d.ts +16 -0
- package/dist/gateway/rate-limiter.d.ts.map +1 -0
- package/dist/gateway/rate-limiter.js +38 -0
- package/dist/gateway/rate-limiter.js.map +1 -0
- package/dist/gateway/routes/control.d.ts +6 -0
- package/dist/gateway/routes/control.d.ts.map +1 -1
- package/dist/gateway/routes/control.js +117 -0
- package/dist/gateway/routes/control.js.map +1 -1
- package/dist/gateway/routes/dashboard.d.ts +1 -1
- package/dist/gateway/routes/dashboard.d.ts.map +1 -1
- package/dist/gateway/routes/dashboard.js +148 -49
- package/dist/gateway/routes/dashboard.js.map +1 -1
- package/dist/gateway/routes/locks.d.ts +3 -1
- package/dist/gateway/routes/locks.d.ts.map +1 -1
- package/dist/gateway/routes/locks.js +18 -13
- package/dist/gateway/routes/locks.js.map +1 -1
- package/dist/gateway/routes/webhooks.d.ts.map +1 -1
- package/dist/gateway/routes/webhooks.js +14 -0
- package/dist/gateway/routes/webhooks.js.map +1 -1
- package/dist/gateway/views/dashboard-page.d.ts.map +1 -1
- package/dist/gateway/views/dashboard-page.js +93 -20
- package/dist/gateway/views/dashboard-page.js.map +1 -1
- package/dist/gateway/views/login-page.d.ts +2 -0
- package/dist/gateway/views/login-page.d.ts.map +1 -0
- package/dist/gateway/views/login-page.js +54 -0
- package/dist/gateway/views/login-page.js.map +1 -0
- package/dist/scheduler/index.d.ts +1 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +176 -216
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/runner-pool.d.ts +5 -0
- package/dist/scheduler/runner-pool.d.ts.map +1 -1
- package/dist/scheduler/runner-pool.js +14 -0
- package/dist/scheduler/runner-pool.js.map +1 -1
- package/dist/scheduler/runtime-factory.d.ts +26 -4
- package/dist/scheduler/runtime-factory.d.ts.map +1 -1
- package/dist/scheduler/runtime-factory.js +43 -77
- package/dist/scheduler/runtime-factory.js.map +1 -1
- package/dist/scheduler/webhook-setup.d.ts +11 -20
- package/dist/scheduler/webhook-setup.d.ts.map +1 -1
- package/dist/scheduler/webhook-setup.js +22 -68
- package/dist/scheduler/webhook-setup.js.map +1 -1
- package/dist/setup/scaffold.d.ts.map +1 -1
- package/dist/setup/scaffold.js +25 -0
- package/dist/setup/scaffold.js.map +1 -1
- package/dist/shared/asm-backend.d.ts.map +1 -1
- package/dist/shared/asm-backend.js +2 -2
- package/dist/shared/asm-backend.js.map +1 -1
- package/dist/shared/config.d.ts +29 -13
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +53 -6
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/constants.d.ts +34 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +34 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/credential-backend.d.ts +1 -1
- package/dist/shared/credentials.d.ts.map +1 -1
- package/dist/shared/credentials.js +2 -1
- package/dist/shared/credentials.js.map +1 -1
- package/dist/shared/errors.d.ts +34 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +47 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/filesystem-backend.d.ts +1 -1
- package/dist/shared/filesystem-backend.js +1 -1
- package/dist/shared/git.d.ts.map +1 -1
- package/dist/shared/git.js +3 -2
- package/dist/shared/git.js.map +1 -1
- package/dist/shared/gsm-backend.d.ts.map +1 -1
- package/dist/shared/gsm-backend.js +2 -2
- package/dist/shared/gsm-backend.js.map +1 -1
- package/dist/shared/paths.d.ts +2 -0
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +3 -1
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/remote.d.ts +1 -0
- package/dist/shared/remote.d.ts.map +1 -1
- package/dist/shared/remote.js +4 -16
- package/dist/shared/remote.js.map +1 -1
- package/dist/shared/usage.d.ts +22 -0
- package/dist/shared/usage.d.ts.map +1 -0
- package/dist/shared/usage.js +43 -0
- package/dist/shared/usage.js.map +1 -0
- package/dist/telemetry/index.d.ts +57 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +189 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/providers/otel.d.ts +15 -0
- package/dist/telemetry/providers/otel.d.ts.map +1 -0
- package/dist/telemetry/providers/otel.js +77 -0
- package/dist/telemetry/providers/otel.js.map +1 -0
- package/dist/telemetry/providers/xray.d.ts +13 -0
- package/dist/telemetry/providers/xray.d.ts.map +1 -0
- package/dist/telemetry/providers/xray.js +33 -0
- package/dist/telemetry/providers/xray.js.map +1 -0
- package/dist/telemetry/types.d.ts +59 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +2 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +2 -0
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/plain-logger.d.ts.map +1 -1
- package/dist/tui/plain-logger.js +8 -2
- package/dist/tui/plain-logger.js.map +1 -1
- package/dist/tui/status-tracker.d.ts +4 -1
- package/dist/tui/status-tracker.d.ts.map +1 -1
- package/dist/tui/status-tracker.js +11 -1
- package/dist/tui/status-tracker.js.map +1 -1
- package/dist/webhooks/providers/github.d.ts.map +1 -1
- package/dist/webhooks/providers/github.js +2 -21
- package/dist/webhooks/providers/github.js.map +1 -1
- package/dist/webhooks/providers/linear.d.ts.map +1 -1
- package/dist/webhooks/providers/linear.js +2 -21
- package/dist/webhooks/providers/linear.js.map +1 -1
- package/dist/webhooks/providers/sentry.d.ts.map +1 -1
- package/dist/webhooks/providers/sentry.js +2 -20
- package/dist/webhooks/providers/sentry.js.map +1 -1
- package/dist/webhooks/validation.d.ts +17 -0
- package/dist/webhooks/validation.d.ts.map +1 -0
- package/dist/webhooks/validation.js +37 -0
- package/dist/webhooks/validation.js.map +1 -0
- package/docker/adot-collector-config.yaml +57 -0
- package/package.json +9 -1
- package/dist/cloud/deploy-apprunner.d.ts.map +0 -1
- package/dist/cloud/deploy-apprunner.js.map +0 -1
- package/dist/cloud/deploy-cloudrun.d.ts.map +0 -1
- package/dist/cloud/deploy-cloudrun.js.map +0 -1
- package/dist/scheduler/config-validator.d.ts +0 -18
- package/dist/scheduler/config-validator.d.ts.map +0 -1
- package/dist/scheduler/config-validator.js +0 -53
- package/dist/scheduler/config-validator.js.map +0 -1
- package/dist/scheduler/cron-manager.d.ts +0 -14
- package/dist/scheduler/cron-manager.d.ts.map +0 -1
- package/dist/scheduler/cron-manager.js +0 -75
- package/dist/scheduler/cron-manager.js.map +0 -1
- package/dist/scheduler/shutdown-handler.d.ts +0 -16
- package/dist/scheduler/shutdown-handler.d.ts.map +0 -1
- package/dist/scheduler/shutdown-handler.js +0 -44
- package/dist/scheduler/shutdown-handler.js.map +0 -1
- package/dist/scheduler/trigger-dispatcher.d.ts +0 -12
- package/dist/scheduler/trigger-dispatcher.d.ts.map +0 -1
- package/dist/scheduler/trigger-dispatcher.js +0 -46
- package/dist/scheduler/trigger-dispatcher.js.map +0 -1
- package/dist/shared/aws-constants.d.ts.map +0 -1
- package/dist/shared/aws-constants.js.map +0 -1
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { resolve } from "path";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
|
-
import { confirm } from "@inquirer/prompts";
|
|
4
|
-
import { execFileSync } from "child_process";
|
|
5
|
-
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
6
|
-
import { IAMClient, CreateRoleCommand, PutRolePolicyCommand, PutUserPolicyCommand, GetRoleCommand } from "@aws-sdk/client-iam";
|
|
7
|
-
import { ECRClient, SetRepositoryPolicyCommand } from "@aws-sdk/client-ecr";
|
|
8
3
|
import { discoverAgents, loadAgentConfig, loadGlobalConfig } from "../../shared/config.js";
|
|
9
4
|
import { resolveCredential } from "../../credentials/registry.js";
|
|
10
5
|
import { promptCredential } from "../../credentials/prompter.js";
|
|
11
6
|
import { parseCredentialRef, credentialExists, writeCredentialFields } from "../../shared/credentials.js";
|
|
12
7
|
import { createLocalBackend, createBackendFromCloudConfig } from "../../shared/remote.js";
|
|
13
|
-
import {
|
|
8
|
+
import { ConfigError, CredentialError } from "../../shared/errors.js";
|
|
9
|
+
import { createCloudProvider } from "../../cloud/provider.js";
|
|
10
|
+
import { ensureGatewayApiKey } from "../../gateway/api-key.js";
|
|
14
11
|
// Webhook secret credential types — these support multiple named instances
|
|
15
12
|
const WEBHOOK_SECRET_TYPES = {
|
|
16
13
|
github: "github_webhook_secret",
|
|
@@ -20,7 +17,7 @@ export async function execute(opts) {
|
|
|
20
17
|
const projectPath = resolve(opts.project);
|
|
21
18
|
// Guard: refuse to run if the project path looks like an agent directory
|
|
22
19
|
if (existsSync(resolve(projectPath, "agent-config.toml")) || existsSync(resolve(projectPath, "ACTIONS.md"))) {
|
|
23
|
-
throw new
|
|
20
|
+
throw new ConfigError(`"${projectPath}" looks like an agent directory, not a project directory. ` +
|
|
24
21
|
`Run 'al doctor' from the project root (the parent directory).`);
|
|
25
22
|
}
|
|
26
23
|
const agents = discoverAgents(projectPath);
|
|
@@ -52,92 +49,33 @@ export async function execute(opts) {
|
|
|
52
49
|
console.log("No credentials required by any agent.");
|
|
53
50
|
}
|
|
54
51
|
else if (opts.checkOnly) {
|
|
55
|
-
|
|
56
|
-
let okCount = 0;
|
|
57
|
-
const missing = [];
|
|
58
|
-
if (opts.cloud) {
|
|
59
|
-
// Check cloud backend
|
|
60
|
-
const globalConfig = loadGlobalConfig(projectPath);
|
|
61
|
-
const cloudConfig = globalConfig.cloud;
|
|
62
|
-
if (!cloudConfig) {
|
|
63
|
-
throw new Error("No [cloud] section found in config.toml. " +
|
|
64
|
-
"Run 'al cloud setup' to configure a cloud provider first.");
|
|
65
|
-
}
|
|
66
|
-
const remote = await createBackendFromCloudConfig(cloudConfig);
|
|
67
|
-
console.log(`Checking ${credentialRefs.size} credential(s) in ${cloudConfig.provider}...`);
|
|
68
|
-
for (const ref of credentialRefs) {
|
|
69
|
-
const { type, instance } = parseCredentialRef(ref);
|
|
70
|
-
const def = resolveCredential(type);
|
|
71
|
-
if (await remote.exists(type, instance)) {
|
|
72
|
-
console.log(` [ok] ${def.label} (${ref})`);
|
|
73
|
-
okCount++;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
console.log(` [MISSING] ${def.label} (${ref})`);
|
|
77
|
-
missing.push(ref);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (missing.length > 0) {
|
|
81
|
-
throw new Error(`${missing.length} credential(s) missing from ${cloudConfig.provider}: ${missing.join(", ")}.\n` +
|
|
82
|
-
`Push them with 'al doctor -c' first.`);
|
|
83
|
-
}
|
|
84
|
-
console.log(`${okCount} credential(s) verified in ${cloudConfig.provider}.`);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
// Check local filesystem (no prompts)
|
|
88
|
-
console.log(`Checking ${credentialRefs.size} credential(s)...`);
|
|
89
|
-
for (const ref of credentialRefs) {
|
|
90
|
-
const { type, instance } = parseCredentialRef(ref);
|
|
91
|
-
const def = resolveCredential(type);
|
|
92
|
-
if (await credentialExists(type, instance)) {
|
|
93
|
-
console.log(` [ok] ${def.label} (${ref})`);
|
|
94
|
-
okCount++;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
console.log(` [MISSING] ${def.label} (${ref})`);
|
|
98
|
-
missing.push(ref);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (missing.length > 0) {
|
|
102
|
-
throw new Error(`${missing.length} credential(s) missing: ${missing.join(", ")}.\n` +
|
|
103
|
-
`Run 'al doctor' interactively to configure them.`);
|
|
104
|
-
}
|
|
105
|
-
console.log(`${okCount} credential(s) verified.`);
|
|
106
|
-
}
|
|
52
|
+
await checkCredentials(credentialRefs, projectPath, opts.cloud);
|
|
107
53
|
}
|
|
108
54
|
else {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
const result = await promptCredential(def, instance);
|
|
122
|
-
if (result && Object.keys(result.values).length > 0) {
|
|
123
|
-
await writeCredentialFields(type, instance, result.values);
|
|
124
|
-
promptedCount++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
console.log(`\nDone. ${okCount} already present, ${promptedCount} configured.`);
|
|
55
|
+
await promptCredentials(credentialRefs);
|
|
56
|
+
}
|
|
57
|
+
// --- Gateway API key ---
|
|
58
|
+
console.log("\nGateway API key:");
|
|
59
|
+
const { key, generated } = await ensureGatewayApiKey();
|
|
60
|
+
if (generated) {
|
|
61
|
+
console.log(` [new] Generated gateway API key: ${key}`);
|
|
62
|
+
console.log(" Save this key — you'll need it to log into the dashboard.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(" [ok] Gateway API key already configured.");
|
|
128
66
|
}
|
|
129
67
|
// --- Cloud mode: push creds + reconcile IAM (interactive only) ---
|
|
130
68
|
if (opts.cloud && !opts.checkOnly) {
|
|
131
|
-
const globalConfig = loadGlobalConfig(projectPath);
|
|
132
69
|
const cloudConfig = globalConfig.cloud;
|
|
133
70
|
if (!cloudConfig) {
|
|
134
|
-
throw new
|
|
71
|
+
throw new ConfigError("No [cloud] section found in config.toml. " +
|
|
135
72
|
"Run 'al cloud setup' to configure a cloud provider first.");
|
|
136
73
|
}
|
|
74
|
+
const provider = await createCloudProvider(cloudConfig);
|
|
137
75
|
// Push local creds to cloud
|
|
138
76
|
console.log(`\nPushing credentials to cloud (${cloudConfig.provider})...`);
|
|
139
77
|
const local = createLocalBackend();
|
|
140
|
-
const remote = await
|
|
78
|
+
const remote = await provider.createCredentialBackend();
|
|
141
79
|
const localEntries = await local.list();
|
|
142
80
|
if (localEntries.length === 0) {
|
|
143
81
|
console.log("No local credentials found. Run 'al doctor' first to configure them.");
|
|
@@ -153,593 +91,85 @@ export async function execute(opts) {
|
|
|
153
91
|
}
|
|
154
92
|
console.log(`Pushed ${pushed} credential field(s) to ${cloudConfig.provider}.`);
|
|
155
93
|
}
|
|
156
|
-
// Reconcile IAM
|
|
94
|
+
// Reconcile IAM (handles task roles, service accounts, Lambda roles)
|
|
157
95
|
console.log(`\nReconciling cloud IAM...`);
|
|
158
|
-
await
|
|
159
|
-
// Validate IAM roles
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await validateEcsRoles(projectPath, cloudConfig);
|
|
163
|
-
// Create Lambda execution roles for short-timeout agents
|
|
164
|
-
console.log(`\nReconciling Lambda roles for short-timeout agents...`);
|
|
165
|
-
await reconcileLambdaRoles(projectPath, cloudConfig);
|
|
166
|
-
}
|
|
96
|
+
await provider.reconcileAgents(projectPath);
|
|
97
|
+
// Validate IAM roles
|
|
98
|
+
console.log(`\nValidating IAM roles...`);
|
|
99
|
+
await provider.validateRoles(projectPath);
|
|
167
100
|
}
|
|
168
101
|
}
|
|
169
|
-
// ---
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async function reconcileGcp(projectPath, cloud) {
|
|
182
|
-
const { gcpProject, secretPrefix: configPrefix } = cloud;
|
|
183
|
-
if (!gcpProject) {
|
|
184
|
-
throw new Error("cloud.gcpProject is required in config.toml");
|
|
185
|
-
}
|
|
186
|
-
const secretPrefix = configPrefix || AWS_CONSTANTS.DEFAULT_SECRET_PREFIX;
|
|
187
|
-
// Verify gcloud is available and authenticated
|
|
188
|
-
try {
|
|
189
|
-
gcloud(["auth", "print-access-token"], gcpProject);
|
|
190
|
-
}
|
|
191
|
-
catch (err) {
|
|
192
|
-
throw new Error("gcloud CLI is not authenticated. Run 'gcloud auth login' first.\n" +
|
|
193
|
-
`Original error: ${err.message}`);
|
|
194
|
-
}
|
|
195
|
-
const agents = discoverAgents(projectPath);
|
|
196
|
-
if (agents.length === 0) {
|
|
197
|
-
console.log("No agents found. Create agents first.");
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
// Pre-flight: check if any secrets exist in GSM with this prefix
|
|
201
|
-
const preflight = listGsmSecretCount(gcpProject, secretPrefix);
|
|
202
|
-
if (preflight === 0) {
|
|
203
|
-
console.log(`\nWarning: No secrets found in GSM with prefix "${secretPrefix}".\n` +
|
|
204
|
-
`IAM bindings are created against existing secrets, so you should push credentials first.\n`);
|
|
205
|
-
const proceed = await confirm({
|
|
206
|
-
message: "Continue anyway? (Service accounts will be created but no secrets will be bound)",
|
|
207
|
-
default: false,
|
|
208
|
-
});
|
|
209
|
-
if (!proceed) {
|
|
210
|
-
console.log("Aborted. Push credentials first, then re-run.");
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
console.log(`\nSetting up Cloud Run service accounts for ${agents.length} agent(s)...\n`);
|
|
215
|
-
for (const name of agents) {
|
|
216
|
-
const config = loadAgentConfig(projectPath, name);
|
|
217
|
-
const saName = AWS_CONSTANTS.serviceAccountName(name);
|
|
218
|
-
const saEmail = AWS_CONSTANTS.serviceAccountEmail(name, gcpProject);
|
|
219
|
-
console.log(` Agent: ${name}`);
|
|
220
|
-
console.log(` SA: ${saEmail}`);
|
|
221
|
-
// 1. Create service account (idempotent)
|
|
222
|
-
try {
|
|
223
|
-
gcloud([
|
|
224
|
-
"iam", "service-accounts", "create", saName,
|
|
225
|
-
"--display-name", `Action Llama agent: ${name}`,
|
|
226
|
-
"--project", gcpProject,
|
|
227
|
-
], gcpProject);
|
|
228
|
-
console.log(` Created service account`);
|
|
229
|
-
}
|
|
230
|
-
catch (err) {
|
|
231
|
-
if (err.message?.includes("already exists")) {
|
|
232
|
-
console.log(` Service account already exists`);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
throw err;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// 2. Collect all secret names this agent needs
|
|
239
|
-
const credRefs = [...new Set(config.credentials)];
|
|
240
|
-
if (config.model.authType !== "pi_auth" && !credRefs.includes("anthropic_key:default")) {
|
|
241
|
-
credRefs.push("anthropic_key:default");
|
|
102
|
+
// --- Credential check (headless / non-interactive) ---
|
|
103
|
+
async function checkCredentials(credentialRefs, projectPath, cloud) {
|
|
104
|
+
let okCount = 0;
|
|
105
|
+
const missing = [];
|
|
106
|
+
if (cloud) {
|
|
107
|
+
const globalConfig = loadGlobalConfig(projectPath);
|
|
108
|
+
const cloudConfig = globalConfig.cloud;
|
|
109
|
+
if (!cloudConfig) {
|
|
110
|
+
throw new ConfigError("No [cloud] section found in config.toml. " +
|
|
111
|
+
"Run 'al cloud setup' to configure a cloud provider first.");
|
|
242
112
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
113
|
+
const remote = await createBackendFromCloudConfig(cloudConfig);
|
|
114
|
+
console.log(`Checking ${credentialRefs.size} credential(s) in ${cloudConfig.provider}...`);
|
|
115
|
+
for (const ref of credentialRefs) {
|
|
245
116
|
const { type, instance } = parseCredentialRef(ref);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
// 3. Grant secretmanager.secretAccessor on each secret
|
|
252
|
-
let boundCount = 0;
|
|
253
|
-
for (const secretName of secretNames) {
|
|
254
|
-
try {
|
|
255
|
-
gcloud([
|
|
256
|
-
"secrets", "add-iam-policy-binding", secretName,
|
|
257
|
-
"--member", `serviceAccount:${saEmail}`,
|
|
258
|
-
"--role", "roles/secretmanager.secretAccessor",
|
|
259
|
-
"--project", gcpProject,
|
|
260
|
-
], gcpProject);
|
|
261
|
-
boundCount++;
|
|
262
|
-
}
|
|
263
|
-
catch (err) {
|
|
264
|
-
if (err.message?.includes("already exists") || err.message?.includes("already has")) {
|
|
265
|
-
boundCount++;
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
console.log(` Warning: failed to bind ${secretName}: ${err.message}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
console.log(` Bound ${boundCount} secret(s)`);
|
|
273
|
-
// 4. Grant the SA permission to act as itself (for Cloud Run job execution)
|
|
274
|
-
try {
|
|
275
|
-
gcloud([
|
|
276
|
-
"iam", "service-accounts", "add-iam-policy-binding", saEmail,
|
|
277
|
-
"--member", `serviceAccount:${saEmail}`,
|
|
278
|
-
"--role", "roles/iam.serviceAccountUser",
|
|
279
|
-
"--project", gcpProject,
|
|
280
|
-
], gcpProject);
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
// May already be bound
|
|
284
|
-
}
|
|
285
|
-
console.log("");
|
|
286
|
-
}
|
|
287
|
-
console.log("Done. Each agent now has an isolated service account with access to only its declared secrets.");
|
|
288
|
-
}
|
|
289
|
-
async function reconcileAws(projectPath, cloud) {
|
|
290
|
-
const { awsRegion, ecrRepository, awsSecretPrefix } = cloud;
|
|
291
|
-
if (!awsRegion) {
|
|
292
|
-
throw new Error("cloud.awsRegion is required in config.toml");
|
|
293
|
-
}
|
|
294
|
-
if (!ecrRepository) {
|
|
295
|
-
throw new Error("cloud.ecrRepository is required in config.toml");
|
|
296
|
-
}
|
|
297
|
-
const secretPrefix = awsSecretPrefix || AWS_CONSTANTS.DEFAULT_SECRET_PREFIX;
|
|
298
|
-
// Extract account ID from ECR repo URI
|
|
299
|
-
const accountMatch = ecrRepository.match(/^(\d+)\.dkr\.ecr\./);
|
|
300
|
-
if (!accountMatch) {
|
|
301
|
-
throw new Error(`Cannot extract AWS account ID from cloud.ecrRepository: "${ecrRepository}". ` +
|
|
302
|
-
`Expected format: 123456789012.dkr.ecr.<region>.amazonaws.com/<repo>`);
|
|
303
|
-
}
|
|
304
|
-
const accountId = accountMatch[1];
|
|
305
|
-
// Verify AWS credentials are valid
|
|
306
|
-
const stsClient = new STSClient({ region: awsRegion });
|
|
307
|
-
try {
|
|
308
|
-
await stsClient.send(new GetCallerIdentityCommand({}));
|
|
309
|
-
}
|
|
310
|
-
catch (err) {
|
|
311
|
-
throw new Error("AWS CLI is not authenticated. Run 'aws configure' or set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY.\n" +
|
|
312
|
-
`Original error: ${err.message}`);
|
|
313
|
-
}
|
|
314
|
-
const agents = discoverAgents(projectPath);
|
|
315
|
-
if (agents.length === 0) {
|
|
316
|
-
console.log("No agents found. Create agents first.");
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// Trust policy for ECS tasks
|
|
320
|
-
const trustPolicy = JSON.stringify({
|
|
321
|
-
Version: "2012-10-17",
|
|
322
|
-
Statement: [{
|
|
323
|
-
Effect: "Allow",
|
|
324
|
-
Principal: { Service: "ecs-tasks.amazonaws.com" },
|
|
325
|
-
Action: "sts:AssumeRole",
|
|
326
|
-
}],
|
|
327
|
-
});
|
|
328
|
-
const iamClient = new IAMClient({ region: awsRegion });
|
|
329
|
-
// Ensure execution role has Secrets Manager access (ECS uses this role to inject secrets)
|
|
330
|
-
if (cloud.executionRoleArn) {
|
|
331
|
-
const executionRoleName = cloud.executionRoleArn.split("/").pop();
|
|
332
|
-
try {
|
|
333
|
-
await iamClient.send(new PutRolePolicyCommand({
|
|
334
|
-
RoleName: executionRoleName,
|
|
335
|
-
PolicyName: "ActionLlamaExecution",
|
|
336
|
-
PolicyDocument: JSON.stringify({
|
|
337
|
-
Version: "2012-10-17",
|
|
338
|
-
Statement: [
|
|
339
|
-
{
|
|
340
|
-
Effect: "Allow",
|
|
341
|
-
Action: "secretsmanager:GetSecretValue",
|
|
342
|
-
Resource: `arn:aws:secretsmanager:${awsRegion}:${accountId}:secret:${secretPrefix}/*`,
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
Effect: "Allow",
|
|
346
|
-
Action: "logs:CreateLogGroup",
|
|
347
|
-
Resource: `arn:aws:logs:${awsRegion}:${accountId}:log-group:${AWS_CONSTANTS.LOG_GROUP}*`,
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
}),
|
|
351
|
-
}));
|
|
352
|
-
console.log(`Execution role (${executionRoleName}): Secrets Manager + CloudWatch policy applied`);
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
throw new Error(`Failed to attach ActionLlamaExecution policy to ${executionRoleName}: ${err.message}\n` +
|
|
356
|
-
`The execution role needs secretsmanager:GetSecretValue and logs:CreateLogGroup permissions.\n` +
|
|
357
|
-
`Either grant your IAM user iam:PutRolePolicy on this role, or attach the policy manually in the AWS Console.`);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// Ensure ECR repository policy grants Lambda pull access
|
|
361
|
-
await ensureLambdaEcrPolicy(awsRegion, ecrRepository);
|
|
362
|
-
console.log(`\nSetting up ECS task roles for ${agents.length} agent(s)...\n`);
|
|
363
|
-
for (const name of agents) {
|
|
364
|
-
const config = loadAgentConfig(projectPath, name);
|
|
365
|
-
const roleName = AWS_CONSTANTS.taskRoleName(name);
|
|
366
|
-
console.log(` Agent: ${name}`);
|
|
367
|
-
console.log(` Role: ${roleName}`);
|
|
368
|
-
// 1. Create IAM role (idempotent)
|
|
369
|
-
try {
|
|
370
|
-
await iamClient.send(new CreateRoleCommand({
|
|
371
|
-
RoleName: roleName,
|
|
372
|
-
AssumeRolePolicyDocument: trustPolicy,
|
|
373
|
-
}));
|
|
374
|
-
console.log(` Created IAM role`);
|
|
375
|
-
}
|
|
376
|
-
catch (err) {
|
|
377
|
-
if (err.name === "EntityAlreadyExistsException") {
|
|
378
|
-
console.log(` IAM role already exists`);
|
|
117
|
+
const def = resolveCredential(type);
|
|
118
|
+
if (await remote.exists(type, instance)) {
|
|
119
|
+
console.log(` [ok] ${def.label} (${ref})`);
|
|
120
|
+
okCount++;
|
|
379
121
|
}
|
|
380
122
|
else {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
// 2. Collect secret ARNs this agent needs
|
|
385
|
-
const credRefs = [...new Set(config.credentials)];
|
|
386
|
-
if (config.model.authType !== "pi_auth" && !credRefs.includes("anthropic_key:default")) {
|
|
387
|
-
credRefs.push("anthropic_key:default");
|
|
388
|
-
}
|
|
389
|
-
const secretArns = [];
|
|
390
|
-
for (const ref of credRefs) {
|
|
391
|
-
const { type, instance } = parseCredentialRef(ref);
|
|
392
|
-
secretArns.push(`arn:aws:secretsmanager:${awsRegion}:${accountId}:secret:${secretPrefix}/${type}/${instance}/*`);
|
|
393
|
-
}
|
|
394
|
-
// 3. Put inline policy for Secrets Manager access
|
|
395
|
-
if (secretArns.length > 0) {
|
|
396
|
-
const policy = JSON.stringify({
|
|
397
|
-
Version: "2012-10-17",
|
|
398
|
-
Statement: [{
|
|
399
|
-
Effect: "Allow",
|
|
400
|
-
Action: "secretsmanager:GetSecretValue",
|
|
401
|
-
Resource: secretArns,
|
|
402
|
-
}],
|
|
403
|
-
});
|
|
404
|
-
try {
|
|
405
|
-
await iamClient.send(new PutRolePolicyCommand({
|
|
406
|
-
RoleName: roleName,
|
|
407
|
-
PolicyName: "SecretsAccess",
|
|
408
|
-
PolicyDocument: policy,
|
|
409
|
-
}));
|
|
410
|
-
console.log(` Bound ${secretArns.length} secret path(s)`);
|
|
123
|
+
console.log(` [MISSING] ${def.label} (${ref})`);
|
|
124
|
+
missing.push(ref);
|
|
411
125
|
}
|
|
412
|
-
catch (err) {
|
|
413
|
-
console.log(` Warning: failed to put policy: ${err.message}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
console.log(` No secrets to bind`);
|
|
418
126
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const taskRoleArns = agents.map((name) => `arn:aws:iam::${accountId}:role/${AWS_CONSTANTS.taskRoleName(name)}`);
|
|
423
|
-
if (cloud.executionRoleArn) {
|
|
424
|
-
taskRoleArns.push(cloud.executionRoleArn);
|
|
425
|
-
}
|
|
426
|
-
if (taskRoleArns.length > 0) {
|
|
427
|
-
await grantPassRole(awsRegion, iamClient, taskRoleArns, "ActionLlamaEcsPassRole");
|
|
428
|
-
}
|
|
429
|
-
console.log("Done. Each agent now has an isolated IAM task role with access to only its declared secrets.");
|
|
430
|
-
console.log(`\nTask roles follow the convention: al-{agentName}-task-role`);
|
|
431
|
-
console.log("The ECS runtime will use them automatically at launch time.");
|
|
432
|
-
}
|
|
433
|
-
// --- Helpers ---
|
|
434
|
-
/**
|
|
435
|
-
* Grant iam:PassRole on the given role ARNs to the calling IAM user.
|
|
436
|
-
* Required so the CLI can assign roles to ECS tasks and Lambda functions.
|
|
437
|
-
*/
|
|
438
|
-
async function grantPassRole(awsRegion, iamClient, roleArns, policyName) {
|
|
439
|
-
const stsClient = new STSClient({ region: awsRegion });
|
|
440
|
-
const identity = await stsClient.send(new GetCallerIdentityCommand({}));
|
|
441
|
-
const callerArn = identity.Arn;
|
|
442
|
-
// Extract user name from ARN (arn:aws:iam::ACCOUNT:user/USERNAME)
|
|
443
|
-
const userMatch = callerArn.match(/:user\/(.+)$/);
|
|
444
|
-
if (!userMatch) {
|
|
445
|
-
console.log(`\n Note: Caller ${callerArn} is not an IAM user — skipping iam:PassRole auto-grant.`);
|
|
446
|
-
console.log(` If you get PassRole errors, add this policy to your IAM identity:`);
|
|
447
|
-
console.log(JSON.stringify({
|
|
448
|
-
Version: "2012-10-17",
|
|
449
|
-
Statement: [{
|
|
450
|
-
Effect: "Allow",
|
|
451
|
-
Action: "iam:PassRole",
|
|
452
|
-
Resource: roleArns,
|
|
453
|
-
}],
|
|
454
|
-
}, null, 2));
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const userName = userMatch[1];
|
|
458
|
-
const policy = JSON.stringify({
|
|
459
|
-
Version: "2012-10-17",
|
|
460
|
-
Statement: [{
|
|
461
|
-
Effect: "Allow",
|
|
462
|
-
Action: "iam:PassRole",
|
|
463
|
-
Resource: roleArns,
|
|
464
|
-
}],
|
|
465
|
-
});
|
|
466
|
-
try {
|
|
467
|
-
await iamClient.send(new PutUserPolicyCommand({
|
|
468
|
-
UserName: userName,
|
|
469
|
-
PolicyName: policyName,
|
|
470
|
-
PolicyDocument: policy,
|
|
471
|
-
}));
|
|
472
|
-
console.log(` Granted iam:PassRole on ${roleArns.length} role(s) to user ${userName}`);
|
|
473
|
-
}
|
|
474
|
-
catch (err) {
|
|
475
|
-
console.log(` Warning: could not grant iam:PassRole to user ${userName}: ${err.message}`);
|
|
476
|
-
console.log(` You may need to manually add iam:PassRole permission for these roles:`);
|
|
477
|
-
for (const arn of roleArns) {
|
|
478
|
-
console.log(` - ${arn}`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
function gcloud(args, _project) {
|
|
483
|
-
return execFileSync("gcloud", args, {
|
|
484
|
-
encoding: "utf-8",
|
|
485
|
-
timeout: 30_000,
|
|
486
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
487
|
-
}).trim();
|
|
488
|
-
}
|
|
489
|
-
function listGsmSecretCount(gcpProject, prefix) {
|
|
490
|
-
try {
|
|
491
|
-
const output = gcloud([
|
|
492
|
-
"secrets", "list",
|
|
493
|
-
"--filter", `name:${prefix}--`,
|
|
494
|
-
"--format", "value(name)",
|
|
495
|
-
"--project", gcpProject,
|
|
496
|
-
], gcpProject);
|
|
497
|
-
if (!output.trim())
|
|
498
|
-
return 0;
|
|
499
|
-
return output.trim().split("\n").length;
|
|
500
|
-
}
|
|
501
|
-
catch {
|
|
502
|
-
return 0;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
function listGsmFields(gcpProject, prefix, type, instance) {
|
|
506
|
-
const filter = `name:${prefix}--${type}--${instance}--`;
|
|
507
|
-
try {
|
|
508
|
-
const output = gcloud([
|
|
509
|
-
"secrets", "list",
|
|
510
|
-
"--filter", filter,
|
|
511
|
-
"--format", "value(name)",
|
|
512
|
-
"--project", gcpProject,
|
|
513
|
-
], gcpProject);
|
|
514
|
-
if (!output.trim())
|
|
515
|
-
return [];
|
|
516
|
-
const fields = [];
|
|
517
|
-
for (const line of output.split("\n")) {
|
|
518
|
-
const secretId = line.trim().split("/").pop();
|
|
519
|
-
const parts = secretId.split("--");
|
|
520
|
-
if (parts.length === 4 && parts[0] === prefix && parts[1] === type && parts[2] === instance) {
|
|
521
|
-
fields.push(parts[3]);
|
|
522
|
-
}
|
|
127
|
+
if (missing.length > 0) {
|
|
128
|
+
throw new CredentialError(`${missing.length} credential(s) missing from ${cloudConfig.provider}: ${missing.join(", ")}.\n` +
|
|
129
|
+
`Push them with 'al doctor -c' first.`);
|
|
523
130
|
}
|
|
524
|
-
|
|
525
|
-
}
|
|
526
|
-
catch {
|
|
527
|
-
return [];
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
export async function validateEcsRoles(projectPath, cloud) {
|
|
531
|
-
const { awsRegion } = cloud;
|
|
532
|
-
if (!awsRegion) {
|
|
533
|
-
throw new Error("cloud.awsRegion is required for ECS validation");
|
|
131
|
+
console.log(`${okCount} credential(s) verified in ${cloudConfig.provider}.`);
|
|
534
132
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
try {
|
|
544
|
-
const role = await iamClient.send(new GetRoleCommand({ RoleName: roleName }));
|
|
545
|
-
// Check if the role has the correct trust policy for ECS tasks
|
|
546
|
-
const trustPolicy = JSON.parse(decodeURIComponent(role.Role.AssumeRolePolicyDocument));
|
|
547
|
-
const hasEcsTrust = trustPolicy.Statement?.some((stmt) => stmt.Effect === "Allow" &&
|
|
548
|
-
stmt.Principal?.Service === "ecs-tasks.amazonaws.com" &&
|
|
549
|
-
(stmt.Action === "sts:AssumeRole" || stmt.Action?.includes("sts:AssumeRole")));
|
|
550
|
-
if (!hasEcsTrust) {
|
|
551
|
-
hasIncorrectTrust.push(roleName);
|
|
552
|
-
console.log(` [TRUST ISSUE] ${roleName} - missing ECS task trust policy`);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
console.log(` [ok] ${roleName}`);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
catch (err) {
|
|
559
|
-
if (err.name === "NoSuchEntityException") {
|
|
560
|
-
missing.push(roleName);
|
|
561
|
-
console.log(` [MISSING] ${roleName}`);
|
|
133
|
+
else {
|
|
134
|
+
console.log(`Checking ${credentialRefs.size} credential(s)...`);
|
|
135
|
+
for (const ref of credentialRefs) {
|
|
136
|
+
const { type, instance } = parseCredentialRef(ref);
|
|
137
|
+
const def = resolveCredential(type);
|
|
138
|
+
if (await credentialExists(type, instance)) {
|
|
139
|
+
console.log(` [ok] ${def.label} (${ref})`);
|
|
140
|
+
okCount++;
|
|
562
141
|
}
|
|
563
142
|
else {
|
|
564
|
-
console.log(` [
|
|
143
|
+
console.log(` [MISSING] ${def.label} (${ref})`);
|
|
144
|
+
missing.push(ref);
|
|
565
145
|
}
|
|
566
146
|
}
|
|
567
|
-
}
|
|
568
|
-
if (missing.length > 0 || hasIncorrectTrust.length > 0) {
|
|
569
|
-
console.log(`\n⚠️ Found IAM role issues that will cause ECS task failures:`);
|
|
570
147
|
if (missing.length > 0) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
console.log(`\n🔧 Fix: Run 'al doctor -c' to create missing roles automatically.`);
|
|
574
|
-
}
|
|
575
|
-
if (hasIncorrectTrust.length > 0) {
|
|
576
|
-
console.log(`\n${hasIncorrectTrust.length} IAM role(s) have incorrect trust policies:`);
|
|
577
|
-
hasIncorrectTrust.forEach(role => console.log(` - ${role}`));
|
|
578
|
-
console.log(`\n🔧 Fix: Update trust policy to allow ECS tasks to assume the role:`);
|
|
579
|
-
console.log(`For each role above, run:`);
|
|
580
|
-
console.log(` aws iam update-assume-role-policy --role-name ROLE_NAME --policy-document file://ecs-trust.json`);
|
|
148
|
+
throw new CredentialError(`${missing.length} credential(s) missing: ${missing.join(", ")}.\n` +
|
|
149
|
+
`Run 'al doctor' interactively to configure them.`);
|
|
581
150
|
}
|
|
582
|
-
console.log(
|
|
583
|
-
console.log(JSON.stringify({
|
|
584
|
-
Version: "2012-10-17",
|
|
585
|
-
Statement: [{
|
|
586
|
-
Effect: "Allow",
|
|
587
|
-
Principal: { Service: "ecs-tasks.amazonaws.com" },
|
|
588
|
-
Action: "sts:AssumeRole",
|
|
589
|
-
}],
|
|
590
|
-
}, null, 2));
|
|
591
|
-
console.log(`\n💡 Alternatively, re-run the cloud setup to fix all issues:`);
|
|
592
|
-
console.log(` al cloud setup`);
|
|
593
|
-
// Throw error to prevent proceeding with invalid configuration
|
|
594
|
-
throw new Error(`${missing.length + hasIncorrectTrust.length} IAM task role(s) have issues that will prevent ECS tasks from starting. Fix the roles above before proceeding.`);
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
console.log(`All ${agents.length} IAM task role(s) exist and have correct trust policies.`);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
async function ensureLambdaEcrPolicy(awsRegion, ecrRepoUri) {
|
|
601
|
-
const repoName = ecrRepoUri.split("/").pop();
|
|
602
|
-
if (!repoName)
|
|
603
|
-
return;
|
|
604
|
-
const ecrClient = new ECRClient({ region: awsRegion });
|
|
605
|
-
const policy = JSON.stringify({
|
|
606
|
-
Version: "2012-10-17",
|
|
607
|
-
Statement: [{
|
|
608
|
-
Sid: "LambdaECRImageRetrievalPolicy",
|
|
609
|
-
Effect: "Allow",
|
|
610
|
-
Principal: { Service: "lambda.amazonaws.com" },
|
|
611
|
-
Action: [
|
|
612
|
-
"ecr:BatchGetImage",
|
|
613
|
-
"ecr:GetDownloadUrlForLayer",
|
|
614
|
-
],
|
|
615
|
-
}],
|
|
616
|
-
});
|
|
617
|
-
try {
|
|
618
|
-
await ecrClient.send(new SetRepositoryPolicyCommand({
|
|
619
|
-
repositoryName: repoName,
|
|
620
|
-
policyText: policy,
|
|
621
|
-
}));
|
|
622
|
-
console.log(`ECR repository policy: granted Lambda pull access`);
|
|
623
|
-
}
|
|
624
|
-
catch (err) {
|
|
625
|
-
console.log(`Warning: could not set ECR repository policy for Lambda: ${err.message}`);
|
|
151
|
+
console.log(`${okCount} credential(s) verified.`);
|
|
626
152
|
}
|
|
627
153
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const iamClient = new IAMClient({ region: awsRegion });
|
|
640
|
-
// Trust policy for Lambda
|
|
641
|
-
const trustPolicy = JSON.stringify({
|
|
642
|
-
Version: "2012-10-17",
|
|
643
|
-
Statement: [{
|
|
644
|
-
Effect: "Allow",
|
|
645
|
-
Principal: { Service: "lambda.amazonaws.com" },
|
|
646
|
-
Action: "sts:AssumeRole",
|
|
647
|
-
}],
|
|
648
|
-
});
|
|
649
|
-
let created = 0;
|
|
650
|
-
for (const name of agents) {
|
|
651
|
-
const config = loadAgentConfig(projectPath, name);
|
|
652
|
-
const effectiveTimeout = config.timeout ?? globalConfig.local?.timeout ?? 900;
|
|
653
|
-
// Only create Lambda roles for agents that will route to Lambda
|
|
654
|
-
if (effectiveTimeout > AWS_CONSTANTS.LAMBDA_MAX_TIMEOUT)
|
|
154
|
+
// --- Interactive credential prompting ---
|
|
155
|
+
async function promptCredentials(credentialRefs) {
|
|
156
|
+
console.log(`\nChecking ${credentialRefs.size} credential(s)...\n`);
|
|
157
|
+
let okCount = 0;
|
|
158
|
+
let promptedCount = 0;
|
|
159
|
+
for (const ref of credentialRefs) {
|
|
160
|
+
const { type, instance } = parseCredentialRef(ref);
|
|
161
|
+
const def = resolveCredential(type);
|
|
162
|
+
if (await credentialExists(type, instance)) {
|
|
163
|
+
console.log(` [ok] ${def.label} (${ref})`);
|
|
164
|
+
okCount++;
|
|
655
165
|
continue;
|
|
656
|
-
const roleName = AWS_CONSTANTS.lambdaRoleName(name);
|
|
657
|
-
// Create role
|
|
658
|
-
try {
|
|
659
|
-
await iamClient.send(new CreateRoleCommand({
|
|
660
|
-
RoleName: roleName,
|
|
661
|
-
AssumeRolePolicyDocument: trustPolicy,
|
|
662
|
-
}));
|
|
663
|
-
console.log(` Created Lambda role: ${roleName}`);
|
|
664
|
-
created++;
|
|
665
166
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
else {
|
|
671
|
-
console.log(` [ERROR] ${roleName}: ${err.message}`);
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
167
|
+
const result = await promptCredential(def, instance);
|
|
168
|
+
if (result && Object.keys(result.values).length > 0) {
|
|
169
|
+
await writeCredentialFields(type, instance, result.values);
|
|
170
|
+
promptedCount++;
|
|
674
171
|
}
|
|
675
|
-
// Add secrets + ECR + logs policy
|
|
676
|
-
const credRefs = [...new Set(config.credentials)];
|
|
677
|
-
if (config.model.authType !== "pi_auth" && !credRefs.includes("anthropic_key:default")) {
|
|
678
|
-
credRefs.push("anthropic_key:default");
|
|
679
|
-
}
|
|
680
|
-
const secretArns = credRefs.map((ref) => {
|
|
681
|
-
const { type, instance } = parseCredentialRef(ref);
|
|
682
|
-
return `arn:aws:secretsmanager:${awsRegion}:${accountId}:secret:${secretPrefix}/${type}/${instance}/*`;
|
|
683
|
-
});
|
|
684
|
-
const policy = JSON.stringify({
|
|
685
|
-
Version: "2012-10-17",
|
|
686
|
-
Statement: [
|
|
687
|
-
{
|
|
688
|
-
Effect: "Allow",
|
|
689
|
-
Action: "secretsmanager:GetSecretValue",
|
|
690
|
-
Resource: secretArns,
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
Effect: "Allow",
|
|
694
|
-
Action: [
|
|
695
|
-
"logs:CreateLogGroup",
|
|
696
|
-
"logs:CreateLogStream",
|
|
697
|
-
"logs:PutLogEvents",
|
|
698
|
-
],
|
|
699
|
-
Resource: `arn:aws:logs:${awsRegion}:${accountId}:*`,
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
Effect: "Allow",
|
|
703
|
-
Action: "ecr:GetAuthorizationToken",
|
|
704
|
-
Resource: "*",
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
Effect: "Allow",
|
|
708
|
-
Action: [
|
|
709
|
-
"ecr:BatchGetImage",
|
|
710
|
-
"ecr:GetDownloadUrlForLayer",
|
|
711
|
-
],
|
|
712
|
-
Resource: `arn:aws:ecr:${awsRegion}:${accountId}:repository/*`,
|
|
713
|
-
},
|
|
714
|
-
],
|
|
715
|
-
});
|
|
716
|
-
try {
|
|
717
|
-
await iamClient.send(new PutRolePolicyCommand({
|
|
718
|
-
RoleName: roleName,
|
|
719
|
-
PolicyName: "LambdaExecution",
|
|
720
|
-
PolicyDocument: policy,
|
|
721
|
-
}));
|
|
722
|
-
}
|
|
723
|
-
catch (err) {
|
|
724
|
-
console.log(` Warning: failed to put policy on ${roleName}: ${err.message}`);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (created > 0) {
|
|
728
|
-
console.log(`Created ${created} Lambda execution role(s).`);
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
console.log(`All Lambda roles up to date.`);
|
|
732
|
-
}
|
|
733
|
-
// Grant iam:PassRole on Lambda roles to the calling identity
|
|
734
|
-
const lambdaRoleArns = agents
|
|
735
|
-
.filter((name) => {
|
|
736
|
-
const config = loadAgentConfig(projectPath, name);
|
|
737
|
-
const effectiveTimeout = config.timeout ?? globalConfig.local?.timeout ?? 900;
|
|
738
|
-
return effectiveTimeout <= AWS_CONSTANTS.LAMBDA_MAX_TIMEOUT;
|
|
739
|
-
})
|
|
740
|
-
.map((name) => `arn:aws:iam::${accountId}:role/${AWS_CONSTANTS.lambdaRoleName(name)}`);
|
|
741
|
-
if (lambdaRoleArns.length > 0) {
|
|
742
|
-
await grantPassRole(awsRegion, iamClient, lambdaRoleArns, "ActionLlamaLambdaPassRole");
|
|
743
172
|
}
|
|
173
|
+
console.log(`\nDone. ${okCount} already present, ${promptedCount} configured.`);
|
|
744
174
|
}
|
|
745
175
|
//# sourceMappingURL=doctor.js.map
|