@crvouga/dev-fleet 0.0.0-stage → 0.1.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/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
- package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
- package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
- package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
- package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
- package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/agents/antigravity-proxy.ts +3 -0
- package/agents/audit.md +89 -0
- package/agents/claude-proxy +5 -0
- package/agents/claude-proxy.ts +3 -0
- package/agents/codex-proxy.ts +3 -0
- package/agents/grok-proxy.ts +3 -0
- package/agents/managed-proxy.ts +55 -0
- package/agents/opencode-proxy.ts +3 -0
- package/agents/registry.json +209 -0
- package/agents/registry.schema.json +149 -0
- package/agents/setup.md +128 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +17 -0
- package/ansible/monitoring/README.md +154 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +134 -0
- package/ansible/monitoring/beszel-agent.service +42 -0
- package/ansible/monitoring/compose.yml +74 -0
- package/ansible/monitoring/hub-darwin.yml +102 -0
- package/ansible/monitoring/lock.json +11 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +43 -0
- package/ansible/ubuntu/recovery.yml +15 -0
- package/ansible/ubuntu/t3/policy.json +7 -0
- package/ansible/ubuntu/t3-desktop.yml +220 -0
- package/ansible/ubuntu/t3-health.sh +33 -0
- package/ansible/ubuntu/t3-policy.yml +76 -0
- package/ansible/ubuntu/t3-route.yml +28 -0
- package/ansible/ubuntu/tailscale.yml +75 -0
- package/ansible/ubuntu/tasks/credentials.yml +47 -0
- package/ansible/ubuntu/tasks/docker.yml +31 -0
- package/ansible/ubuntu/tasks/packages.yml +56 -0
- package/ansible/ubuntu/tasks/power.yml +51 -0
- package/ansible/ubuntu/tasks/recovery.yml +39 -0
- package/ansible/ubuntu/tasks/resources.yml +252 -0
- package/ansible/ubuntu/tasks/services.yml +31 -0
- package/ansible/ubuntu/tasks/ssh.yml +71 -0
- package/ansible/ubuntu/tasks/user.yml +120 -0
- package/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +73 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +189 -0
- package/bootstrap/fleet-manager-run.sh +76 -0
- package/bootstrap/fleet-remote-bootstrap.sh +185 -0
- package/bootstrap/global-config.ts +113 -0
- package/bootstrap/install-launcher.sh +7 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +350 -0
- package/bootstrap/setup-status.ts +51 -0
- package/bootstrap/setup.ts +88 -0
- package/bootstrap/shell-path.ts +105 -0
- package/bootstrap/verify-t3-archive.py +49 -0
- package/bootstrap/worker-auth.ts +65 -0
- package/bootstrap/worker-github.ts +47 -0
- package/bootstrap/worker-tools.ts +24 -0
- package/cliproxy/AGENTS.md +11 -0
- package/cliproxy/README.md +269 -0
- package/cliproxy/clients/bootstrap.py +143 -0
- package/cliproxy/clients/launcher.py +57 -0
- package/cliproxy/compose.yaml +90 -0
- package/cliproxy/config.template.yaml +70 -0
- package/cliproxy/docs/cloud-adoption.md +16 -0
- package/cliproxy/docs/deployment.png +0 -0
- package/cliproxy/docs/deployment.svg +109 -0
- package/cliproxy/docs/health-workflow.example.yml +185 -0
- package/cliproxy/docs/migration.md +131 -0
- package/cliproxy/docs/model-health-report.schema.json +364 -0
- package/cliproxy/docs/model-health.md +393 -0
- package/cliproxy/image.lock.json +5 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
- package/cliproxy/models.json +1618 -0
- package/cliproxy/requirements.lock +78 -0
- package/cliproxy/scripts/accounts.py +38 -0
- package/cliproxy/scripts/audit.py +123 -0
- package/cliproxy/scripts/backup +234 -0
- package/cliproxy/scripts/check.py +34 -0
- package/cliproxy/scripts/checkpoint.py +226 -0
- package/cliproxy/scripts/cli.py +64 -0
- package/cliproxy/scripts/cliproxy_host.py +257 -0
- package/cliproxy/scripts/cloud_state.py +150 -0
- package/cliproxy/scripts/container-health +6 -0
- package/cliproxy/scripts/docker +24 -0
- package/cliproxy/scripts/external-health.py +79 -0
- package/cliproxy/scripts/fleet_config.py +43 -0
- package/cliproxy/scripts/fleet_sync.py +274 -0
- package/cliproxy/scripts/initialize +84 -0
- package/cliproxy/scripts/install-dependencies +48 -0
- package/cliproxy/scripts/install.py +406 -0
- package/cliproxy/scripts/model_health.py +16 -0
- package/cliproxy/scripts/modelhealth/__init__.py +5 -0
- package/cliproxy/scripts/modelhealth/checks.py +429 -0
- package/cliproxy/scripts/modelhealth/cli.py +117 -0
- package/cliproxy/scripts/modelhealth/config.py +186 -0
- package/cliproxy/scripts/modelhealth/emulator.py +536 -0
- package/cliproxy/scripts/modelhealth/expect.py +88 -0
- package/cliproxy/scripts/modelhealth/findings.py +183 -0
- package/cliproxy/scripts/modelhealth/protocol.py +330 -0
- package/cliproxy/scripts/modelhealth/redact.py +28 -0
- package/cliproxy/scripts/modelhealth/report.py +150 -0
- package/cliproxy/scripts/modelhealth/runner.py +390 -0
- package/cliproxy/scripts/modelhealth/scenario.py +131 -0
- package/cliproxy/scripts/modelhealth/schema.py +63 -0
- package/cliproxy/scripts/modelhealth/simulate.py +111 -0
- package/cliproxy/scripts/modelhealth/transport.py +213 -0
- package/cliproxy/scripts/monitor +68 -0
- package/cliproxy/scripts/ops +527 -0
- package/cliproxy/scripts/public-endpoint +127 -0
- package/cliproxy/scripts/recover_node.py +177 -0
- package/cliproxy/scripts/restore-verify +60 -0
- package/cliproxy/scripts/retention.py +144 -0
- package/cliproxy/scripts/service-start +7 -0
- package/cliproxy/scripts/validate-config +56 -0
- package/cliproxy/scripts/vault-access.py +57 -0
- package/cliproxy/scripts/vault-backup-login +55 -0
- package/cliproxy/scripts/vault-recovery +56 -0
- package/cliproxy/scripts/vault-renew +30 -0
- package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
- package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
- package/cliproxy/systemd/cliproxy-backup.service +13 -0
- package/cliproxy/systemd/cliproxy-backup.timer +8 -0
- package/cliproxy/systemd/cliproxy-fleet.service +17 -0
- package/cliproxy/systemd/cliproxy-gc.service +13 -0
- package/cliproxy/systemd/cliproxy-gc.timer +8 -0
- package/cliproxy/systemd/cliproxy-monitor.service +8 -0
- package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
- package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
- package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
- package/cliproxy/vendor/LICENSE +22 -0
- package/cliproxy/vendor/config.upstream.yaml +1349 -0
- package/examples/fleet.json +95 -0
- package/examples/roster.json +13 -0
- package/mise.lock +1492 -0
- package/mise.toml +99 -0
- package/package.json +65 -4
- package/schema/fleet.schema.json +364 -0
- package/schema/roster.schema.json +77 -0
- package/src/agents/account-relay.ts +111 -0
- package/src/agents/accounts.ts +85 -0
- package/src/agents/audit.ts +77 -0
- package/src/agents/cli.ts +13 -0
- package/src/agents/command.ts +204 -0
- package/src/agents/converge.ts +123 -0
- package/src/agents/doctor.ts +107 -0
- package/src/agents/errors.ts +2 -0
- package/src/agents/files.ts +73 -0
- package/src/agents/fleet-verify.ts +156 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +226 -0
- package/src/agents/provider-errors.ts +50 -0
- package/src/agents/registry.ts +55 -0
- package/src/agents/render.ts +110 -0
- package/src/agents/round-trip.ts +192 -0
- package/src/agents/secrets.ts +13 -0
- package/src/agents/toolchain.ts +34 -0
- package/src/agents/verify-cache.ts +103 -0
- package/src/agents/verify.ts +127 -0
- package/src/auth-checks.ts +163 -0
- package/src/cli/agent-guide.ts +95 -0
- package/src/cli/app.ts +115 -0
- package/src/cli/cli.ts +21 -0
- package/src/cli/command-index.ts +50 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-sync-checkout.ts +91 -0
- package/src/cli/fleet-sync-enrollment.ts +144 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +193 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +314 -0
- package/src/cli/fleet-sync-privilege.ts +27 -0
- package/src/cli/fleet-sync-progress.ts +109 -0
- package/src/cli/fleet-sync-remote.ts +236 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +309 -0
- package/src/cli/fleet.ts +163 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +114 -0
- package/src/cli/node-t3.ts +31 -0
- package/src/cli/options.ts +141 -0
- package/src/cli/probe-source.ts +7 -0
- package/src/cli/program.ts +169 -0
- package/src/cli/register-fleet.ts +246 -0
- package/src/cli/registry.ts +158 -0
- package/src/cli/repair-apply.ts +67 -0
- package/src/cli/repair-guard.ts +11 -0
- package/src/cli/repair-inspect.ts +150 -0
- package/src/cli/repair-script.ts +76 -0
- package/src/cli/repair.ts +209 -0
- package/src/cli/roster.ts +89 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +64 -0
- package/src/cli/source-operations.ts +78 -0
- package/src/cli/t3/command.ts +90 -0
- package/src/cli/t3/register.ts +55 -0
- package/src/cli/tailnet.ts +127 -0
- package/src/cli/ui-actions.ts +133 -0
- package/src/cli/ui-controller.ts +113 -0
- package/src/cli/ui-data.ts +106 -0
- package/src/cli/ui-execute.ts +163 -0
- package/src/cli/ui-repairs.ts +34 -0
- package/src/cli/ui-reports.ts +211 -0
- package/src/cli/ui-targets.ts +100 -0
- package/src/cli/ui-terminal.ts +109 -0
- package/src/cli/ui-types.ts +49 -0
- package/src/cli/ui-wizard.ts +101 -0
- package/src/cli/ui.ts +9 -0
- package/src/cli/worker-health-output.ts +37 -0
- package/src/config.ts +186 -0
- package/src/configure.ts +255 -0
- package/src/definition.ts +260 -0
- package/src/diagnostics.ts +92 -0
- package/src/doctor-check.ts +10 -0
- package/src/errors.ts +1 -0
- package/src/fingerprint.ts +120 -0
- package/src/fleet-config.ts +88 -0
- package/src/index.ts +6 -0
- package/src/input-hashes.ts +73 -0
- package/src/machine/local-source.ts +52 -0
- package/src/machine/lock-error.ts +2 -0
- package/src/machine/platform.ts +44 -0
- package/src/machine/source.ts +68 -0
- package/src/machine/state.ts +185 -0
- package/src/machine-health.ts +95 -0
- package/src/managed-source.ts +47 -0
- package/src/manager-failure.ts +48 -0
- package/src/manager-sync.ts +400 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/apply.ts +171 -0
- package/src/monitoring/beszel.ts +191 -0
- package/src/monitoring/enroll.ts +110 -0
- package/src/monitoring/hub-access.ts +113 -0
- package/src/monitoring/hub-viewer.ts +142 -0
- package/src/monitoring/hub.ts +93 -0
- package/src/monitoring/manager-agent.ts +73 -0
- package/src/monitoring/manager-hub.ts +232 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +372 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +62 -0
- package/src/platform/darwin.ts +203 -0
- package/src/platform/detect.ts +11 -0
- package/src/platform/fallback.ts +110 -0
- package/src/platform/index.ts +38 -0
- package/src/platform/launchd.ts +98 -0
- package/src/platform/linux.ts +139 -0
- package/src/platform/ps.ts +45 -0
- package/src/platform/resource-verify.ts +128 -0
- package/src/platform/systemd-user.ts +95 -0
- package/src/platform/t3-policy.ts +137 -0
- package/src/platform/t3-route.ts +68 -0
- package/src/platform/t3-service.ts +75 -0
- package/src/platform/types.ts +123 -0
- package/src/platform/ubuntu-audit.ts +63 -0
- package/src/platform/ubuntu-verify.ts +105 -0
- package/src/platform/windows.ts +148 -0
- package/src/power-recovery.ts +55 -0
- package/src/probe.ts +51 -0
- package/src/protocol.ts +119 -0
- package/src/proxy-converge.ts +85 -0
- package/src/remote-bootstrap.ts +212 -0
- package/src/remote-entry.ts +89 -0
- package/src/remote-lock.ts +12 -0
- package/src/repair-catalog.ts +206 -0
- package/src/repair-plan.ts +78 -0
- package/src/repair-runner.ts +84 -0
- package/src/report.ts +71 -0
- package/src/runner.ts +104 -0
- package/src/secrets.ts +169 -0
- package/src/service-auth.ts +115 -0
- package/src/ssh.ts +135 -0
- package/src/sync.ts +104 -0
- package/src/t3/accounts.ts +81 -0
- package/src/t3/desired.ts +165 -0
- package/src/t3/files.ts +81 -0
- package/src/t3/names-check.ts +53 -0
- package/src/t3/names.ts +155 -0
- package/src/t3/plan.ts +82 -0
- package/src/t3/providers.ts +74 -0
- package/src/t3/reconcile.ts +123 -0
- package/src/t3-endpoint.ts +48 -0
- package/src/t3-linked-check.ts +101 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/test-fixtures.ts +36 -0
- package/src/toolchain-checks.ts +83 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +95 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +115 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +176 -0
- package/src/worker-policy.ts +64 -0
- package/src/worker-readiness.ts +64 -0
- package/src/workstation-checks.ts +29 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { read, object, write } from '../t3/files';
|
|
3
|
+
import { FleetUsageError } from '../errors';
|
|
4
|
+
|
|
5
|
+
export async function deployment(root: string) {
|
|
6
|
+
const raw = await read(join(root, 'node_modules/.personal-node.json'));
|
|
7
|
+
if (raw === null) return null;
|
|
8
|
+
const data = object(raw);
|
|
9
|
+
if (
|
|
10
|
+
data['version'] !== 1 ||
|
|
11
|
+
typeof data['revision'] !== 'string' ||
|
|
12
|
+
!/^[a-f0-9]{40}$/.test(data['revision']) ||
|
|
13
|
+
typeof data['node'] !== 'string' ||
|
|
14
|
+
!/^[a-z0-9][a-z0-9-]{0,62}$/.test(data['node'])
|
|
15
|
+
)
|
|
16
|
+
throw new FleetUsageError(
|
|
17
|
+
'Invalid local node receipt. Restore it before reconciling.'
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
workspace: join(root, 'node_modules/.fleet-revisions', data['revision']),
|
|
21
|
+
node: data['node'],
|
|
22
|
+
revision: data['revision'],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function recordDeployment(
|
|
27
|
+
root: string,
|
|
28
|
+
node: string,
|
|
29
|
+
revision: string
|
|
30
|
+
) {
|
|
31
|
+
const path = join(root, 'node_modules/.personal-node.json');
|
|
32
|
+
const content = JSON.stringify({ version: 1, node, revision }) + '\n';
|
|
33
|
+
if ((await read(path)) !== content) await write(path, content);
|
|
34
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { configureWorkspace } from '../configure';
|
|
3
|
+
import { runCommand } from '../runner';
|
|
4
|
+
import { FleetUsageError } from '../errors';
|
|
5
|
+
import type { Runner } from '../types';
|
|
6
|
+
import type { Settings } from './settings';
|
|
7
|
+
import type { CommandOutcome } from './contracts';
|
|
8
|
+
import { syncT3 } from './node-t3';
|
|
9
|
+
import { nodeId, type T3Options } from '../t3/desired';
|
|
10
|
+
import { recordDeployment } from './node-deployment';
|
|
11
|
+
|
|
12
|
+
export interface NodeSyncOptions extends T3Options {
|
|
13
|
+
revision?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One local convergence: locked tools, public launcher, profiles, then T3. */
|
|
17
|
+
export async function syncNode(
|
|
18
|
+
options: NodeSyncOptions,
|
|
19
|
+
settings: Settings,
|
|
20
|
+
run: Runner = runCommand
|
|
21
|
+
): Promise<CommandOutcome> {
|
|
22
|
+
if (process.platform === 'win32' && !options.dryRun)
|
|
23
|
+
throw new FleetUsageError(
|
|
24
|
+
'Codex proxy node sync requires macOS/Linux/WSL. Use dev-fleet commands for native Windows configuration.'
|
|
25
|
+
);
|
|
26
|
+
const node = nodeId(options.id);
|
|
27
|
+
const head = await run(['git', 'rev-parse', '--verify', 'HEAD'], {
|
|
28
|
+
cwd: settings.workspace,
|
|
29
|
+
});
|
|
30
|
+
const revision = options.revision ?? head.stdout.trim();
|
|
31
|
+
if (!head.ok || !/^[a-f0-9]{40}$/.test(revision))
|
|
32
|
+
throw new FleetUsageError(
|
|
33
|
+
'An existing personal checkout is required; set --workspace PATH.'
|
|
34
|
+
);
|
|
35
|
+
const request = { node, revision, tool: 'codex', profile: 'proxy' };
|
|
36
|
+
if (options.dryRun)
|
|
37
|
+
return {
|
|
38
|
+
exitCode: 0,
|
|
39
|
+
data: {
|
|
40
|
+
version: 1,
|
|
41
|
+
command: 'fleet-node-sync',
|
|
42
|
+
ok: true,
|
|
43
|
+
applied: false,
|
|
44
|
+
...request,
|
|
45
|
+
steps: [
|
|
46
|
+
'Reconcile locked tools',
|
|
47
|
+
'Install personal and retire owned ws launcher',
|
|
48
|
+
'Upsert Codex proxy profile',
|
|
49
|
+
'Reconcile declared T3 instances and remove stale owned entries',
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const checks = await configureWorkspace(settings.workspace, request, run);
|
|
54
|
+
if (checks.some((check) => check.status !== 'pass'))
|
|
55
|
+
return {
|
|
56
|
+
exitCode: 1,
|
|
57
|
+
data: { version: 1, command: 'fleet-node-sync', ok: false, checks },
|
|
58
|
+
};
|
|
59
|
+
return finishSync(options, settings, { request, checks }, run);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function finishSync(
|
|
63
|
+
options: NodeSyncOptions,
|
|
64
|
+
settings: Settings,
|
|
65
|
+
input: {
|
|
66
|
+
request: { node: string; revision: string; tool: string; profile: string };
|
|
67
|
+
checks: Awaited<ReturnType<typeof configureWorkspace>>;
|
|
68
|
+
},
|
|
69
|
+
run: Runner
|
|
70
|
+
): Promise<CommandOutcome> {
|
|
71
|
+
const { request, checks } = input;
|
|
72
|
+
const { node, revision } = request;
|
|
73
|
+
const workspace = join(
|
|
74
|
+
settings.workspace,
|
|
75
|
+
'node_modules/.fleet-revisions',
|
|
76
|
+
revision
|
|
77
|
+
);
|
|
78
|
+
const installed = await run(['sh', 'bootstrap/install-launcher.sh'], {
|
|
79
|
+
cwd: workspace,
|
|
80
|
+
timeoutMs: 30_000,
|
|
81
|
+
});
|
|
82
|
+
if (!installed.ok)
|
|
83
|
+
return {
|
|
84
|
+
exitCode: 1,
|
|
85
|
+
data: {
|
|
86
|
+
version: 1,
|
|
87
|
+
ok: false,
|
|
88
|
+
checks,
|
|
89
|
+
error:
|
|
90
|
+
'Launcher installation failed; profile changes may already have applied.',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const t3 = await syncT3({ ...options, id: node }, { ...settings, workspace });
|
|
94
|
+
if (t3.exitCode !== 0) return t3;
|
|
95
|
+
await recordDeployment(settings.workspace, node, revision);
|
|
96
|
+
return {
|
|
97
|
+
exitCode: 0,
|
|
98
|
+
data: {
|
|
99
|
+
version: 1,
|
|
100
|
+
command: 'fleet-node-sync',
|
|
101
|
+
ok: true,
|
|
102
|
+
applied: true,
|
|
103
|
+
...request,
|
|
104
|
+
checks,
|
|
105
|
+
t3: t3.data,
|
|
106
|
+
readiness: {
|
|
107
|
+
authentication: 'unverified',
|
|
108
|
+
inference: 'unverified',
|
|
109
|
+
pairing: 'unverified',
|
|
110
|
+
},
|
|
111
|
+
next: 'Use devfleet t3-status for configuration drift and devfleet fleet-status for machine health.',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FleetUsageError } from '../errors';
|
|
2
|
+
import type { Settings } from './settings';
|
|
3
|
+
import type { CommandOutcome } from './contracts';
|
|
4
|
+
import { t3Command } from './t3/command';
|
|
5
|
+
import type { T3Options } from '../t3/desired';
|
|
6
|
+
|
|
7
|
+
export async function syncT3(
|
|
8
|
+
options: T3Options,
|
|
9
|
+
settings: Settings
|
|
10
|
+
): Promise<CommandOutcome> {
|
|
11
|
+
try {
|
|
12
|
+
return await t3Command('t3-apply', options, settings);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
exitCode: 1,
|
|
16
|
+
data: {
|
|
17
|
+
version: 1,
|
|
18
|
+
command: 'fleet-node-sync',
|
|
19
|
+
ok: false,
|
|
20
|
+
applied: true,
|
|
21
|
+
stage: 't3',
|
|
22
|
+
error:
|
|
23
|
+
error instanceof FleetUsageError
|
|
24
|
+
? error.message
|
|
25
|
+
: 'T3 reconciliation failed; inspect local configuration and ownership state.',
|
|
26
|
+
progress:
|
|
27
|
+
'Tools, launcher and profile steps completed. Node receipt was not advanced. Fix the T3 conflict and repeat fleet-node-sync.',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { CAPABILITIES } from '../config';
|
|
2
|
+
import { FleetUsageError } from '../errors';
|
|
3
|
+
import type { Capability } from '../types';
|
|
4
|
+
import type { Settings } from './settings';
|
|
5
|
+
|
|
6
|
+
export interface CliOptions extends Partial<Settings> {
|
|
7
|
+
managed?: boolean;
|
|
8
|
+
workers?: boolean;
|
|
9
|
+
tailnet?: boolean;
|
|
10
|
+
list?: boolean;
|
|
11
|
+
onboard?: boolean;
|
|
12
|
+
schema?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
nonInteractive?: boolean;
|
|
15
|
+
interactive?: boolean;
|
|
16
|
+
local?: boolean;
|
|
17
|
+
all?: boolean;
|
|
18
|
+
node?: string[];
|
|
19
|
+
require?: string;
|
|
20
|
+
timeout?: string;
|
|
21
|
+
strict?: boolean;
|
|
22
|
+
repair?: string;
|
|
23
|
+
script?: boolean;
|
|
24
|
+
planHash?: string;
|
|
25
|
+
apply?: boolean;
|
|
26
|
+
yes?: boolean;
|
|
27
|
+
id?: string;
|
|
28
|
+
ssh?: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
runtime?: string;
|
|
31
|
+
search?: string;
|
|
32
|
+
group?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function capabilities(value?: string): Capability[] {
|
|
36
|
+
const values =
|
|
37
|
+
value === 'none'
|
|
38
|
+
? []
|
|
39
|
+
: (value?.split(',') ??
|
|
40
|
+
CAPABILITIES.filter((entry) => entry !== 'worker'));
|
|
41
|
+
if (values.some((entry) => !CAPABILITIES.includes(entry as Capability)))
|
|
42
|
+
throw new FleetUsageError(
|
|
43
|
+
`--require expects ${CAPABILITIES.join(',')} or none.`
|
|
44
|
+
);
|
|
45
|
+
return [...new Set(values)] as Capability[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function timeout(options: CliOptions): number {
|
|
49
|
+
const value = Number(
|
|
50
|
+
options.timeout ?? (options.apply ? 1_500_000 : 120_000)
|
|
51
|
+
);
|
|
52
|
+
if (!Number.isInteger(value) || value < 1000 || value > 1_800_000)
|
|
53
|
+
throw new FleetUsageError('--timeout must be 1000–1800000 milliseconds.');
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Validation runs on the internal action selected by fleet-* flags. */
|
|
58
|
+
export function validateOptions(command: string, options: CliOptions): void {
|
|
59
|
+
if (options.tailnet && options.workers)
|
|
60
|
+
throw new FleetUsageError('Choose --tailnet or --workers, not both.');
|
|
61
|
+
if (options.onboard && options.schema)
|
|
62
|
+
throw new FleetUsageError('Choose --onboard or --schema, not both.');
|
|
63
|
+
validateMutation(command, options);
|
|
64
|
+
validateRepair(command, options);
|
|
65
|
+
validateScope(command, options);
|
|
66
|
+
if (
|
|
67
|
+
(['discover', 'health'].includes(command) ||
|
|
68
|
+
(command === 'repair' && !options.local)) &&
|
|
69
|
+
options.require !== undefined
|
|
70
|
+
)
|
|
71
|
+
throw new FleetUsageError(
|
|
72
|
+
'--require is supported for roster status and local repair only; tailnet checks use declared roster requirements.'
|
|
73
|
+
);
|
|
74
|
+
if (options.timeout !== undefined) timeout(options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateMutation(command: string, options: CliOptions): void {
|
|
78
|
+
if (options.apply && !['add', 'repair'].includes(command))
|
|
79
|
+
throw new FleetUsageError(
|
|
80
|
+
'--apply is only valid for fleet-node-add and fleet-repair.'
|
|
81
|
+
);
|
|
82
|
+
if (options.apply && !options.yes)
|
|
83
|
+
throw new FleetUsageError('Mutation requires both --apply and --yes.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateScope(command: string, options: CliOptions): void {
|
|
87
|
+
const selected = [options.local, options.all, options.node?.length].filter(
|
|
88
|
+
Boolean
|
|
89
|
+
);
|
|
90
|
+
if (['discover', 'health'].includes(command) && selected.length)
|
|
91
|
+
throw new FleetUsageError(
|
|
92
|
+
'Tailscale discovery, --tailnet and --workers always cover all visible computers; --local, --all and --node are not supported.'
|
|
93
|
+
);
|
|
94
|
+
if (selected.length > 1)
|
|
95
|
+
throw new FleetUsageError(
|
|
96
|
+
'Choose --local, --all, or --node, not a combination.'
|
|
97
|
+
);
|
|
98
|
+
validateRosterScope(command, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateRosterScope(command: string, options: CliOptions): void {
|
|
102
|
+
if ((options.local || options.all) && !['doctor', 'repair'].includes(command))
|
|
103
|
+
throw new FleetUsageError(
|
|
104
|
+
'--local and --all are only valid for fleet-status and fleet-repair.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateRepair(command: string, options: CliOptions): void {
|
|
109
|
+
if (
|
|
110
|
+
options.planHash !== undefined &&
|
|
111
|
+
!/^[a-f0-9]{64}$/.test(options.planHash)
|
|
112
|
+
)
|
|
113
|
+
throw new FleetUsageError(
|
|
114
|
+
'--plan-hash expects the SHA-256 value from a repair preview.'
|
|
115
|
+
);
|
|
116
|
+
const repairFlags =
|
|
117
|
+
options.repair !== undefined ||
|
|
118
|
+
options.script === true ||
|
|
119
|
+
options.planHash !== undefined;
|
|
120
|
+
if (repairFlags && command !== 'repair')
|
|
121
|
+
throw new FleetUsageError(
|
|
122
|
+
'--repair, --script and --plan-hash are only valid for fleet-repair without --list.'
|
|
123
|
+
);
|
|
124
|
+
if (options.script && options.apply)
|
|
125
|
+
throw new FleetUsageError(
|
|
126
|
+
'--script only generates a preview; run a reviewed repair separately with --apply --yes.'
|
|
127
|
+
);
|
|
128
|
+
validateCatalog(command, options);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function validateCatalog(command: string, options: CliOptions): void {
|
|
132
|
+
const filtered =
|
|
133
|
+
options.local ||
|
|
134
|
+
options.all ||
|
|
135
|
+
Boolean(options.node?.length) ||
|
|
136
|
+
options.require !== undefined;
|
|
137
|
+
if (command === 'repairs' && filtered)
|
|
138
|
+
throw new FleetUsageError(
|
|
139
|
+
'fleet-repair --list is a static catalog and does not accept target or capability filters.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { bundleProbe } from '../transport';
|
|
3
|
+
|
|
4
|
+
export async function probeSource(): Promise<string> {
|
|
5
|
+
if (typeof Bun !== 'undefined') return bundleProbe();
|
|
6
|
+
return readFile(new URL('./probe.mjs', import.meta.url), 'utf8');
|
|
7
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
import { FleetUsageError } from '../errors';
|
|
3
|
+
import { indexedCommands } from './command-index';
|
|
4
|
+
import { registerT3 } from './t3/register';
|
|
5
|
+
import { registerSourceOperations } from './source-operations';
|
|
6
|
+
import { registerFleet } from './register-fleet';
|
|
7
|
+
import { agentGuide } from './agent-guide';
|
|
8
|
+
import type { Settings } from './settings';
|
|
9
|
+
import type { CliOptions } from './options';
|
|
10
|
+
import type { CommandOutcome } from './contracts';
|
|
11
|
+
|
|
12
|
+
export interface Release {
|
|
13
|
+
version: string;
|
|
14
|
+
/** ISO timestamp recorded when this distribution was built; null for source runs. */
|
|
15
|
+
releasedAt: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const GLOBAL_OPTIONS = [
|
|
19
|
+
{
|
|
20
|
+
flag: '--json',
|
|
21
|
+
description: 'One versioned JSON document on stdout; never prompt',
|
|
22
|
+
},
|
|
23
|
+
{ flag: '--non-interactive', description: 'Never open interactive prompts' },
|
|
24
|
+
{
|
|
25
|
+
flag: '--workspace <path>',
|
|
26
|
+
description: 'Existing manager checkout; does not clone or change it',
|
|
27
|
+
},
|
|
28
|
+
{ flag: '--config <path>', description: 'Local, secret-free fleet roster' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** The version a checkout carries; a published package has a real one. */
|
|
32
|
+
export const SOURCE_VERSION = '0.0.0-development';
|
|
33
|
+
|
|
34
|
+
export function releaseLabel(release: Release): string {
|
|
35
|
+
if (release.releasedAt)
|
|
36
|
+
return `${release.version} (released ${release.releasedAt})`;
|
|
37
|
+
return release.version === SOURCE_VERSION
|
|
38
|
+
? `${release.version} (unreleased source build)`
|
|
39
|
+
: release.version;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createProgram(
|
|
43
|
+
release: Release,
|
|
44
|
+
output: (text: string) => void
|
|
45
|
+
): Command {
|
|
46
|
+
const program = new Command()
|
|
47
|
+
.name('devfleet')
|
|
48
|
+
.description('Developer fleet, T3 and platform operations')
|
|
49
|
+
.version(releaseLabel(release))
|
|
50
|
+
.exitOverride()
|
|
51
|
+
.configureOutput({ writeOut: output, writeErr: () => undefined })
|
|
52
|
+
.showSuggestionAfterError(false)
|
|
53
|
+
.addHelpCommand('help [command]', 'Show help for a command')
|
|
54
|
+
.option(
|
|
55
|
+
'--interactive',
|
|
56
|
+
'Open the interactive terminal application (TTY required)'
|
|
57
|
+
);
|
|
58
|
+
for (const option of GLOBAL_OPTIONS)
|
|
59
|
+
program.option(option.flag, option.description);
|
|
60
|
+
program.addHelpText(
|
|
61
|
+
'after',
|
|
62
|
+
[
|
|
63
|
+
'',
|
|
64
|
+
`Release: ${releaseLabel(release)}`,
|
|
65
|
+
'Groups: fleet-* developer computers · t3-* T3 provider instances · cliproxy the CLI proxy service.',
|
|
66
|
+
'Apply on invocation (--dry-run previews): fleet-node-sync, t3-apply, t3-cleanup. fleet-sync confirms interactively or with --apply --yes.',
|
|
67
|
+
'Preview until --apply --yes: fleet-repair, fleet-node-add. --json does not make a command read-only.',
|
|
68
|
+
'Agents: devfleet commands --json. Menu: devfleet --interactive.',
|
|
69
|
+
].join('\n')
|
|
70
|
+
);
|
|
71
|
+
return program;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function catalog(options: CliOptions, release: Release): CommandOutcome {
|
|
75
|
+
const commands = indexedCommands(options.search, options.group);
|
|
76
|
+
return {
|
|
77
|
+
exitCode: 0,
|
|
78
|
+
data: {
|
|
79
|
+
version: 1,
|
|
80
|
+
cliVersion: release.version,
|
|
81
|
+
releasedAt: release.releasedAt,
|
|
82
|
+
command: 'commands',
|
|
83
|
+
ok: true,
|
|
84
|
+
commands,
|
|
85
|
+
globalOptions: GLOBAL_OPTIONS,
|
|
86
|
+
defaultCommand: 'help',
|
|
87
|
+
schema: 'devfleet fleet-nodes --schema --json',
|
|
88
|
+
agent: agentGuide(release),
|
|
89
|
+
},
|
|
90
|
+
text: [
|
|
91
|
+
`Personal command catalog · ${releaseLabel(release)}`,
|
|
92
|
+
...commands.map((entry) => `\n ${entry.id}\n ${entry.description}`),
|
|
93
|
+
'\nUse personal <command> --help for options; personal --interactive opens the menu.',
|
|
94
|
+
].join('\n'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function execute(
|
|
99
|
+
args: string[],
|
|
100
|
+
defaults: Settings,
|
|
101
|
+
version: string | Release
|
|
102
|
+
): Promise<CommandOutcome> {
|
|
103
|
+
const release: Release =
|
|
104
|
+
typeof version === 'string' ? { version, releasedAt: null } : version;
|
|
105
|
+
let outcome: CommandOutcome | undefined;
|
|
106
|
+
let output = '';
|
|
107
|
+
const program = createProgram(release, (text) => {
|
|
108
|
+
output += text;
|
|
109
|
+
});
|
|
110
|
+
const complete = (result: CommandOutcome) => {
|
|
111
|
+
outcome = result;
|
|
112
|
+
};
|
|
113
|
+
registerFleet(program, defaults, complete);
|
|
114
|
+
registerT3(program, defaults, complete);
|
|
115
|
+
registerSourceOperations(program, defaults, complete);
|
|
116
|
+
program
|
|
117
|
+
.command('commands')
|
|
118
|
+
.description('Search the command catalog and agent contract')
|
|
119
|
+
.option('--search <text>', 'Filter names, descriptions and keywords')
|
|
120
|
+
.option(
|
|
121
|
+
'--group <name>',
|
|
122
|
+
'Filter group: Fleet, T3, Infra, Workstation, Reference'
|
|
123
|
+
)
|
|
124
|
+
.action((_options, command: Command) => {
|
|
125
|
+
outcome = catalog(command.optsWithGlobals<CliOptions>(), release);
|
|
126
|
+
});
|
|
127
|
+
program.action(() => {
|
|
128
|
+
if (program.opts<CliOptions>().json)
|
|
129
|
+
outcome = catalog(program.opts<CliOptions>(), release);
|
|
130
|
+
else program.help();
|
|
131
|
+
});
|
|
132
|
+
try {
|
|
133
|
+
await program.parseAsync(args, { from: 'user' });
|
|
134
|
+
return (
|
|
135
|
+
outcome ?? {
|
|
136
|
+
exitCode: 0,
|
|
137
|
+
data: { version: 1, help: output.trim() },
|
|
138
|
+
text: output.trim(),
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return commandError(error, output);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function commandError(error: unknown, output: string): CommandOutcome {
|
|
147
|
+
if (error instanceof CommanderError && error.exitCode === 0)
|
|
148
|
+
return {
|
|
149
|
+
exitCode: 0,
|
|
150
|
+
data: { version: 1, help: output.trim() },
|
|
151
|
+
text: output.trim(),
|
|
152
|
+
};
|
|
153
|
+
const usage =
|
|
154
|
+
error instanceof CommanderError || error instanceof FleetUsageError;
|
|
155
|
+
return {
|
|
156
|
+
exitCode: usage ? 2 : 1,
|
|
157
|
+
data: {
|
|
158
|
+
version: 1,
|
|
159
|
+
ok: false,
|
|
160
|
+
code: usage ? 'invalid_usage' : 'operation_failed',
|
|
161
|
+
error:
|
|
162
|
+
error instanceof FleetUsageError
|
|
163
|
+
? error.message
|
|
164
|
+
: usage
|
|
165
|
+
? 'Invalid command or option. Run personal --help or personal commands --json.'
|
|
166
|
+
: 'Operation failed. Inspect configuration and permissions locally; raw errors are withheld to protect credentials.',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|