openpalm 0.10.2 → 0.11.0-beta.2

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.
Files changed (47) hide show
  1. package/README.md +11 -19
  2. package/package.json +4 -2
  3. package/src/commands/addon.ts +5 -4
  4. package/src/commands/automations.ts +63 -0
  5. package/src/commands/install.ts +98 -280
  6. package/src/commands/logs.ts +1 -1
  7. package/src/commands/restart.ts +5 -4
  8. package/src/commands/rollback.ts +4 -3
  9. package/src/commands/scan.ts +66 -32
  10. package/src/commands/service.ts +5 -4
  11. package/src/commands/start.ts +5 -4
  12. package/src/commands/status.ts +1 -1
  13. package/src/commands/stop.ts +2 -4
  14. package/src/commands/uninstall.ts +3 -5
  15. package/src/commands/update.ts +19 -2
  16. package/src/commands/validate.ts +16 -34
  17. package/src/install-flow.test.ts +153 -154
  18. package/src/lib/admin-skills/index.test.ts +70 -0
  19. package/src/lib/admin-skills/index.ts +113 -0
  20. package/src/lib/browser.ts +20 -0
  21. package/src/lib/cli-compose.ts +2 -20
  22. package/src/lib/cli-state.ts +1 -1
  23. package/src/lib/docker.ts +8 -214
  24. package/src/lib/env.ts +12 -83
  25. package/src/lib/io.ts +130 -0
  26. package/src/lib/opencode-subprocess.ts +14 -6
  27. package/src/lib/paths.ts +2 -2
  28. package/src/lib/ui-server.ts +150 -0
  29. package/src/main.test.ts +76 -173
  30. package/src/main.ts +131 -7
  31. package/e2e/start-wizard-server.ts +0 -59
  32. package/src/commands/admin.ts +0 -43
  33. package/src/commands/install-services.test.ts +0 -13
  34. package/src/commands/install-services.ts +0 -9
  35. package/src/commands/upgrade.ts +0 -12
  36. package/src/lib/embedded-assets.ts +0 -115
  37. package/src/lib/varlock.ts +0 -126
  38. package/src/setup-wizard/index.html +0 -321
  39. package/src/setup-wizard/server-errors.test.ts +0 -418
  40. package/src/setup-wizard/server-integration.test.ts +0 -511
  41. package/src/setup-wizard/server.test.ts +0 -508
  42. package/src/setup-wizard/server.ts +0 -342
  43. package/src/setup-wizard/wizard-renderers.js +0 -1294
  44. package/src/setup-wizard/wizard-state.js +0 -346
  45. package/src/setup-wizard/wizard-validators.js +0 -81
  46. package/src/setup-wizard/wizard.css +0 -1611
  47. package/src/setup-wizard/wizard.js +0 -613
@@ -1,47 +1,81 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { join } from 'node:path';
3
- import { rm } from 'node:fs/promises';
4
- import { resolveVaultDir } from '@openpalm/lib';
5
- import { ensureVarlock, prepareVarlockDir } from '../lib/varlock.ts';
3
+ import { existsSync } from 'node:fs';
4
+ import { resolveStackDir } from '@openpalm/lib';
5
+ import { parseEnvFile, isSensitiveEnvKey } from '@openpalm/lib';
6
6
 
7
+ /**
8
+ * `openpalm scan` — list sensitive env keys that carry a non-empty value
9
+ * in the live vault env files. Replaces the varlock-based scanner; the
10
+ * canonical inventory now lives in `akm vault` and the operator-managed
11
+ * `.env` files. Exits non-zero only on filesystem errors, never on the
12
+ * mere presence of secrets (that is the expected state).
13
+ *
14
+ * Output formats:
15
+ * --format json (default) machine-readable JSON
16
+ * { "files": [{ "path": "...", "keys": [{ "name": "...", "set": true }] }] }
17
+ * --format human grouped, one line per key:
18
+ * # /path/to/file.env
19
+ * KEY_NAME set
20
+ */
7
21
  export default defineCommand({
8
22
  meta: {
9
23
  name: 'scan',
10
- description: 'Scan codebase for leaked secrets (requires local user.env)',
24
+ description: 'List vault env keys whose name matches the secret pattern (_TOKEN/_SECRET/_KEY/_PASSWORD/_HMAC)',
11
25
  },
12
- async run() {
13
- const vaultDir = resolveVaultDir();
14
-
15
- const schemaPath = join(vaultDir, 'user', 'user.env.schema');
16
- const envPath = join(vaultDir, 'user', 'user.env');
17
-
18
- if (!(await Bun.file(schemaPath).exists())) {
19
- console.error(
20
- `Error: vault/user/user.env.schema not found at ${schemaPath}.\nRun 'openpalm install' first.`,
21
- );
22
- process.exit(1);
26
+ args: {
27
+ format: {
28
+ type: 'string',
29
+ description: 'Output format: json (default) or human',
30
+ default: 'json',
31
+ },
32
+ },
33
+ async run({ args }) {
34
+ const format = String(args.format ?? 'json').toLowerCase();
35
+ if (format !== 'json' && format !== 'human') {
36
+ console.error(`Unknown --format value: ${args.format}. Expected 'json' or 'human'.`);
37
+ process.exit(2);
23
38
  }
24
39
 
25
- if (!(await Bun.file(envPath).exists())) {
26
- console.error(
27
- `Error: user.env not found at ${envPath}.\nRun 'openpalm install' first.`,
28
- );
29
- process.exit(1);
30
- }
40
+ const stackDir = resolveStackDir();
41
+ const targets = [
42
+ join(stackDir, 'stack.env'),
43
+ join(stackDir, 'guardian.env'),
44
+ ];
31
45
 
32
- const varlockBin = await ensureVarlock();
46
+ type FileResult = { path: string; keys: Array<{ name: string; set: boolean }> };
47
+ const results: FileResult[] = [];
33
48
 
34
- const tmpDir = await prepareVarlockDir(schemaPath, envPath);
35
- let exitCode = 1;
36
- try {
37
- const proc = Bun.spawn([varlockBin, 'scan', '--path', `${tmpDir}/`], {
38
- stdout: 'inherit',
39
- stderr: 'inherit',
49
+ for (const path of targets) {
50
+ if (!existsSync(path)) continue;
51
+ const parsed = parseEnvFile(path);
52
+ const sensitive = Object.keys(parsed)
53
+ .filter((k) => isSensitiveEnvKey(k))
54
+ .sort();
55
+ if (sensitive.length === 0) continue;
56
+ results.push({
57
+ path,
58
+ keys: sensitive.map((name) => ({
59
+ name,
60
+ set: typeof parsed[name] === 'string' && parsed[name].length > 0,
61
+ })),
40
62
  });
41
- exitCode = await proc.exited;
42
- } finally {
43
- await rm(tmpDir, { recursive: true, force: true });
44
63
  }
45
- process.exit(exitCode);
64
+
65
+ if (format === 'json') {
66
+ console.log(JSON.stringify({ files: results }));
67
+ } else {
68
+ if (results.length === 0) {
69
+ console.log('No vault env files found. Run `openpalm install` first.');
70
+ } else {
71
+ for (const file of results) {
72
+ console.log(`# ${file.path}`);
73
+ for (const key of file.keys) {
74
+ console.log(` ${key.name}\t${key.set ? 'set' : 'empty'}`);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ process.exit(0);
46
80
  },
47
81
  });
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
+ import { buildManagedServices } from '@openpalm/lib';
2
3
  import { ensureValidState } from '../lib/cli-state.ts';
3
- import { buildManagedServiceNames, runComposeWithPreflight, runComposeReadOnly } from '../lib/cli-compose.ts';
4
+ import { runComposeWithPreflight, runComposeReadOnly } from '../lib/cli-compose.ts';
4
5
  import { runLogsAction } from './logs.ts';
5
6
  import { runStartAction } from './start.ts';
6
7
  import { runStopAction } from './stop.ts';
@@ -41,8 +42,8 @@ const logsCmd = defineCommand({
41
42
  const updateCmd = defineCommand({
42
43
  meta: { name: 'update', description: 'Pull latest images' },
43
44
  async run() {
44
- const state = await ensureValidState();
45
- const managedServices = await buildManagedServiceNames(state);
45
+ const state = ensureValidState();
46
+ const managedServices = await buildManagedServices(state);
46
47
  console.log('Pulling latest images...');
47
48
  await runComposeWithPreflight(state, ['pull', ...managedServices]);
48
49
  console.log('Recreating containers...');
@@ -54,7 +55,7 @@ const updateCmd = defineCommand({
54
55
  const statusCmd = defineCommand({
55
56
  meta: { name: 'status', description: 'Show container status' },
56
57
  async run() {
57
- const state = await ensureValidState();
58
+ const state = ensureValidState();
58
59
  await runComposeReadOnly(state, ['ps', '--format', 'table']);
59
60
  },
60
61
  });
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
+ import { buildManagedServices } from '@openpalm/lib';
2
3
  import { ensureValidState } from '../lib/cli-state.ts';
3
- import { buildManagedServiceNames, runComposeWithPreflight } from '../lib/cli-compose.ts';
4
+ import { runComposeWithPreflight } from '../lib/cli-compose.ts';
4
5
 
5
6
  export default defineCommand({
6
7
  meta: {
@@ -25,14 +26,14 @@ export async function runStartAction(
25
26
  ): Promise<void> {
26
27
  if (services.length === 0) {
27
28
  // Stage artifacts and start all managed services (admin included if enabled)
28
- const state = await ensureValidState();
29
- const managedServices = await buildManagedServiceNames(state);
29
+ const state = ensureValidState();
30
+ const managedServices = await buildManagedServices(state);
30
31
  await runComposeWithPreflight(state, ['up', '-d', ...managedServices]);
31
32
  return;
32
33
  }
33
34
 
34
35
  // Start specific services
35
- const state = await ensureValidState();
36
+ const state = ensureValidState();
36
37
  for (const service of services) {
37
38
  await runComposeWithPreflight(state, ['up', '-d', service]);
38
39
  }
@@ -8,7 +8,7 @@ export default defineCommand({
8
8
  description: 'Show container status',
9
9
  },
10
10
  async run() {
11
- const state = await ensureValidState();
11
+ const state = ensureValidState();
12
12
  await runComposeReadOnly(state, ['ps', '--format', 'table']);
13
13
  },
14
14
  });
@@ -22,14 +22,12 @@ export default defineCommand({
22
22
 
23
23
  export async function runStopAction(services: string[]): Promise<void> {
24
24
  if (services.length === 0) {
25
- // Compose file list includes admin.yml when admin is enabled,
26
- // so `down` tears down all services including admin/socket-proxy.
27
- const state = await ensureValidState();
25
+ const state = ensureValidState();
28
26
  await runComposeWithPreflight(state, ['down']);
29
27
  return;
30
28
  }
31
29
 
32
- const state = await ensureValidState();
30
+ const state = ensureValidState();
33
31
  for (const service of services) {
34
32
  await runComposeWithPreflight(state, ['stop', service]);
35
33
  }
@@ -2,7 +2,7 @@ import { defineCommand } from 'citty';
2
2
  import { rmSync } from 'node:fs';
3
3
  import { ensureValidState } from '../lib/cli-state.ts';
4
4
  import { runComposeWithPreflight } from '../lib/cli-compose.ts';
5
- import { resolveConfigDir, resolveDataDir, resolveLogsDir, resolveVaultDir } from '@openpalm/lib';
5
+ import { resolveConfigDir, resolveStateDir, resolveStashDir, resolveWorkspaceDir } from '@openpalm/lib';
6
6
 
7
7
  export default defineCommand({
8
8
  meta: {
@@ -22,14 +22,12 @@ export default defineCommand({
22
22
  },
23
23
  },
24
24
  async run({ args }) {
25
- // Compose file list includes admin.yml when admin is enabled,
26
- // so `down` tears down all services including admin/socket-proxy.
27
- const state = await ensureValidState();
25
+ const state = ensureValidState();
28
26
  const downArgs = args.volumes || args.purge ? ['down', '-v'] : ['down'];
29
27
  await runComposeWithPreflight(state, downArgs);
30
28
 
31
29
  if (args.purge) {
32
- const dirs = [resolveConfigDir(), resolveDataDir(), resolveLogsDir(), resolveVaultDir()];
30
+ const dirs = [resolveConfigDir(), resolveStateDir(), resolveStashDir(), resolveWorkspaceDir()];
33
31
  for (const dir of dirs) {
34
32
  console.log(`Removing ${dir}`);
35
33
  rmSync(dir, { recursive: true, force: true });
@@ -1,5 +1,5 @@
1
1
  import { defineCommand } from 'citty';
2
- import { performUpgrade } from '@openpalm/lib';
2
+ import { performUpgrade, checkAndUpdateUiBuild } from '@openpalm/lib';
3
3
  import { ensureValidState } from '../lib/cli-state.ts';
4
4
 
5
5
  export default defineCommand({
@@ -13,7 +13,7 @@ export default defineCommand({
13
13
  });
14
14
 
15
15
  export async function runUpgradeAction(): Promise<void> {
16
- const state = await ensureValidState();
16
+ const state = ensureValidState();
17
17
 
18
18
  console.log('Upgrading stack...');
19
19
  const result = await performUpgrade(state);
@@ -21,5 +21,22 @@ export async function runUpgradeAction(): Promise<void> {
21
21
  if (result.assetsUpdated.length > 0) {
22
22
  console.log(`Assets updated: ${result.assetsUpdated.join(', ')}`);
23
23
  }
24
+
25
+ // Check for a newer UI build on GitHub and install it if available.
26
+ // Passes the pre-upgrade image tag as the reference version so any newer
27
+ // release (including the one just upgraded to) triggers a download.
28
+ // Existing state/ui/ is backed up to state/backups/ui-{timestamp}/ before
29
+ // replacement. Non-fatal — existing build remains on any error.
30
+ const currentUiVersion = state.imageTag ?? '0.0.0';
31
+ console.log('Checking for UI build update...');
32
+ const uiResult = await checkAndUpdateUiBuild(currentUiVersion, state.stateDir);
33
+ if (uiResult.updated) {
34
+ console.log(`UI build updated to v${uiResult.latestVersion}.`);
35
+ } else if (uiResult.error) {
36
+ console.warn(`Warning: UI build update skipped — ${uiResult.error}. Existing build still active.`);
37
+ } else {
38
+ console.log(`UI build is current (v${uiResult.latestVersion}).`);
39
+ }
40
+
24
41
  console.log('Update complete.');
25
42
  }
@@ -1,46 +1,28 @@
1
- import { defineCommand } from 'citty';
2
- import { join } from 'node:path';
3
- import { rm } from 'node:fs/promises';
4
- import { resolveVaultDir } from '@openpalm/lib';
5
- import { ensureVarlock, prepareVarlockDir } from '../lib/varlock.ts';
1
+ import { defineCommand } from "citty";
2
+ import { createState, validateProposedState } from "@openpalm/lib";
6
3
 
7
4
  export default defineCommand({
8
5
  meta: {
9
- name: 'validate',
10
- description: 'Validate configuration against schema',
6
+ name: "validate",
7
+ description: "Validate environment configuration (key presence + non-empty required slots)",
11
8
  },
12
9
  async run() {
13
- const vaultDir = resolveVaultDir();
10
+ // Use createState() directly — validateProposedState only needs vaultDir,
11
+ // not the resolved compose artifacts ensureValidState() would pull in.
12
+ const state = createState();
13
+ const result = await validateProposedState(state);
14
14
 
15
- const primarySchema = join(vaultDir, 'user', 'user.env.schema');
16
- const envPath = join(vaultDir, 'user', 'user.env');
17
-
18
- if (!(await Bun.file(primarySchema).exists())) {
19
- console.error(
20
- `Error: vault/user/user.env.schema not found at ${primarySchema}.\nRun 'openpalm install' first.`,
21
- );
22
- process.exit(1);
15
+ for (const warning of result.warnings) {
16
+ console.warn(warning);
23
17
  }
24
-
25
- if (!(await Bun.file(envPath).exists())) {
26
- console.error(
27
- `Error: user.env not found at ${envPath}.\nRun 'openpalm install' first.`,
28
- );
29
- process.exit(1);
18
+ for (const err of result.errors) {
19
+ console.error(err);
30
20
  }
31
21
 
32
- const varlockBin = await ensureVarlock();
33
- const tmpDir = await prepareVarlockDir(primarySchema, envPath);
34
- let exitCode = 1;
35
- try {
36
- const proc = Bun.spawn(
37
- [varlockBin, 'load', '--path', `${tmpDir}/`],
38
- { stdout: 'inherit', stderr: 'inherit' },
39
- );
40
- exitCode = await proc.exited;
41
- } finally {
42
- await rm(tmpDir, { recursive: true, force: true });
22
+ if (result.ok) {
23
+ console.log("Configuration OK.");
24
+ process.exit(0);
43
25
  }
44
- process.exit(exitCode);
26
+ process.exit(1);
45
27
  },
46
28
  });