@celilo/cli 0.11.0 → 0.12.1
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 +7 -4
- package/CELILO_CORE_MODULES.md +7 -6
- package/CELILO_SUBSYSTEMS.md +52 -6
- package/MODULE_PRIMITIVES.md +164 -0
- package/README.md +5 -5
- package/drizzle/0015_port_forwards.sql +12 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +13 -5
- package/schemas/system_config.json +1 -1
- package/src/ansible/inventory.test.ts +1 -1
- package/src/ansible/inventory.ts +3 -3
- package/src/api/protocol.test.ts +1 -1
- package/src/api/remote-client.test.ts +1 -1
- package/src/api/serve.ts +5 -5
- package/src/capabilities/public-web-publish.test.ts +209 -0
- package/src/capabilities/validation.test.ts +1 -1
- package/src/capabilities/well-known.test.ts +1 -1
- package/src/capabilities/well-known.ts +2 -2
- package/src/cli/command-tree-parser.ts +3 -1
- package/src/cli/commands/api.ts +1 -1
- package/src/cli/commands/apt-upgrade.test.ts +33 -0
- package/src/cli/commands/apt-upgrade.ts +63 -0
- package/src/cli/commands/backup-pull.ts +1 -1
- package/src/cli/commands/commands-json.ts +29 -0
- package/src/cli/commands/completion.ts +1 -1
- package/src/cli/commands/events.ts +1 -1
- package/src/cli/commands/module-changeset.ts +1 -1
- package/src/cli/commands/module-import.ts +1 -1
- package/src/cli/commands/module-list.ts +16 -2
- package/src/cli/commands/module-publish.ts +1 -1
- package/src/cli/commands/module-remove.ts +26 -1
- package/src/cli/commands/module-types.ts +1 -1
- package/src/cli/commands/module-upgrade.ts +1 -1
- package/src/cli/commands/module-version.ts +1 -1
- package/src/cli/commands/module-where.test.ts +26 -0
- package/src/cli/commands/module-where.ts +130 -0
- package/src/cli/commands/proxmox-node-list.ts +1 -1
- package/src/cli/commands/publish/alpha.test.ts +1 -1
- package/src/cli/commands/publish/alpha.ts +3 -3
- package/src/cli/commands/publish/changesets.ts +1 -1
- package/src/cli/commands/publish/global-install.ts +6 -2
- package/src/cli/commands/publish/helpers.ts +21 -14
- package/src/cli/commands/publish/index.ts +4 -4
- package/src/cli/commands/publish/plan.ts +3 -3
- package/src/cli/commands/publish/preflight.ts +2 -2
- package/src/cli/commands/publish/types.ts +2 -2
- package/src/cli/commands/publish/workspace.test.ts +1 -1
- package/src/cli/commands/publish/workspace.ts +2 -2
- package/src/cli/commands/registry-owner.test.ts +166 -0
- package/src/cli/commands/registry-owner.ts +124 -0
- package/src/cli/commands/registry-token.test.ts +109 -0
- package/src/cli/commands/registry-token.ts +194 -0
- package/src/cli/commands/restore.ts +1 -1
- package/src/cli/commands/service-add-proxmox.ts +1 -1
- package/src/cli/commands/service-list.ts +15 -2
- package/src/cli/commands/subscribers-list.ts +1 -1
- package/src/cli/commands/system-apply-config-equivalence.test.ts +1 -1
- package/src/cli/commands/system-apply-config.ts +1 -1
- package/src/cli/commands/system-init-deprecation.test.ts +1 -1
- package/src/cli/commands/system-init.ts +2 -2
- package/src/cli/commands/token.test.ts +26 -0
- package/src/cli/commands/token.ts +160 -0
- package/src/cli/completion.ts +21 -0
- package/src/cli/generate-zsh-completion.test.ts +22 -4
- package/src/cli/generate-zsh-completion.ts +7 -3
- package/src/cli/index.ts +170 -7
- package/src/cli/parser.ts +1 -1
- package/src/cli/restore-command.test.ts +1 -1
- package/src/db/client.ts +1 -1
- package/src/db/schema.ts +50 -7
- package/src/hooks/capability-loader-firewall.test.ts +4 -3
- package/src/hooks/capability-loader.ts +100 -10
- package/src/hooks/define-hook.test.ts +24 -0
- package/src/hooks/executor.test.ts +1 -1
- package/src/hooks/executor.ts +2 -2
- package/src/hooks/types.ts +1 -1
- package/src/manifest/contracts/v1.ts +2 -2
- package/src/manifest/schema.ts +13 -13
- package/src/manifest/template-validator.ts +1 -1
- package/src/module/packaging/build.ts +19 -0
- package/src/module/packaging/workspace-deps.test.ts +94 -0
- package/src/module/packaging/workspace-deps.ts +185 -0
- package/src/module/versioning/changeset-version.ts +1 -1
- package/src/policy/no-hand-built-ssh.test.ts +90 -0
- package/src/registry/client.test.ts +86 -0
- package/src/registry/client.ts +67 -1
- package/src/services/api-access.ts +1 -1
- package/src/services/aspect-approvals.ts +1 -1
- package/src/services/aspect-runner.ts +3 -3
- package/src/services/aspect-template-resolver.test.ts +1 -1
- package/src/services/aspect-template-resolver.ts +1 -1
- package/src/services/build-bus/delivery-events.ts +1 -1
- package/src/services/build-bus/fan-out.ts +1 -1
- package/src/services/build-bus/hook-dispatch.ts +1 -1
- package/src/services/build-bus/receiver-server.ts +1 -1
- package/src/services/build-bus/status.test.ts +1 -1
- package/src/services/build-bus/status.ts +1 -1
- package/src/services/build-bus/subscriber-store.ts +1 -1
- package/src/services/bus-interview.ts +1 -1
- package/src/services/celilo-events.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +1 -1
- package/src/services/cross-module-read.ts +1 -1
- package/src/services/deploy-posture.ts +1 -1
- package/src/services/deployed-systems.test.ts +1 -1
- package/src/services/deployed-systems.ts +4 -4
- package/src/services/dns-provider-backfill.ts +2 -2
- package/src/services/events-daemon.ts +1 -1
- package/src/services/machine-pool.ts +3 -3
- package/src/services/module-deploy.ts +93 -8
- package/src/services/module-subscriptions.ts +1 -1
- package/src/services/module-types-generator.ts +1 -1
- package/src/services/module-validator/git-hygiene.ts +1 -1
- package/src/services/port-forwards.test.ts +92 -0
- package/src/services/port-forwards.ts +86 -0
- package/src/services/proxmox-reconcile.ts +2 -2
- package/src/services/public-web-republish.test.ts +189 -0
- package/src/services/public-web-republish.ts +84 -0
- package/src/services/restore-from-file.ts +1 -1
- package/src/services/restore-preflight.ts +1 -1
- package/src/services/system-identity.ts +1 -1
- package/src/services/system-init.test.ts +2 -2
- package/src/services/system-init.ts +2 -2
- package/src/services/web-route-cleanup.test.ts +250 -0
- package/src/services/web-route-cleanup.ts +144 -0
- package/src/templates/generator.ts +2 -2
- package/src/types/infrastructure.ts +1 -1
- package/src/variables/computed/computed-integration.test.ts +1 -1
- package/src/variables/computed/evaluate.ts +1 -1
- package/src/variables/computed/parse.ts +1 -1
- package/src/variables/context.test.ts +1 -1
- package/src/variables/context.ts +5 -5
- package/src/variables/lxc-nameserver.test.ts +1 -1
- package/src/variables/resolver.ts +2 -2
- package/src/variables/types.ts +1 -1
- package/src/api/protocol.ts +0 -159
- package/src/api/remote-client.ts +0 -218
- package/src/cli/command-registry.ts +0 -1488
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contributor identity tokens (idp-issued, per-user)
|
|
3
|
+
*
|
|
4
|
+
* `celilo token obtain|list|revoke` mints, lists, and revokes per-user API
|
|
5
|
+
* tokens from the idp (authentik) so a module contributor authenticates
|
|
6
|
+
* publishes AS THEMSELVES — no admin/shared token ever reaches their machine
|
|
7
|
+
* (SECURE_MODULE_PUBLISH.md). The token consumed by `celilo author init`.
|
|
8
|
+
*
|
|
9
|
+
* Distinct from `celilo registry token add/rm`, which manages the raw
|
|
10
|
+
* bootstrap `publish_tokens` list — a different trust/storage model. Keep the
|
|
11
|
+
* two families separate.
|
|
12
|
+
*
|
|
13
|
+
* Runs on-mgr where the idp provider + bootstrap token live (same model as
|
|
14
|
+
* registry-token.ts). The token string is shown once at mint; the idp stores
|
|
15
|
+
* it hashed and celilo persists nothing.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { IdpCapability } from '@celilo/capabilities';
|
|
19
|
+
import { getDb } from '../../db/client';
|
|
20
|
+
import { loadCapabilityFunctions } from '../../hooks/capability-loader';
|
|
21
|
+
import { createCapturingLogger } from '../../hooks/logger';
|
|
22
|
+
import { getArg, getFlag } from '../parser';
|
|
23
|
+
import type { CommandError, CommandResult } from '../types';
|
|
24
|
+
|
|
25
|
+
/** Default identifier for a contributor's publish token — stable per user so obtain is idempotent. */
|
|
26
|
+
function defaultIdentifier(username: string): string {
|
|
27
|
+
return `celilo-publish-${username}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isError(v: unknown): v is CommandError {
|
|
31
|
+
return typeof v === 'object' && v !== null && 'success' in v && v.success === false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load the idp capability. Runs where the idp provider (authentik) is
|
|
36
|
+
* deployed; errors clearly when it isn't. The capturing logger discards the
|
|
37
|
+
* capability's own progress markers so command output stays clean.
|
|
38
|
+
*/
|
|
39
|
+
async function loadIdp(): Promise<IdpCapability | CommandError> {
|
|
40
|
+
const db = getDb();
|
|
41
|
+
const { logger } = createCapturingLogger();
|
|
42
|
+
const caps = await loadCapabilityFunctions('celilo-token', db, logger);
|
|
43
|
+
const idp = caps.idp as IdpCapability | undefined;
|
|
44
|
+
if (!idp) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error:
|
|
48
|
+
'No idp provider found. Run this on the host where the idp (authentik) is deployed (celilo-mgr).',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return idp;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve the target username from `--user` (or first positional arg). */
|
|
55
|
+
function resolveUser(args: string[], flags: Record<string, string | boolean>): string | undefined {
|
|
56
|
+
const flag = getFlag(flags, 'user').trim();
|
|
57
|
+
if (flag) return flag;
|
|
58
|
+
return getArg(args, 0)?.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `celilo token obtain --user <username> [--identifier <id>] [--description <text>]`
|
|
63
|
+
* Mints (or returns the existing) per-user token. Idempotent on identifier.
|
|
64
|
+
*/
|
|
65
|
+
export async function handleTokenObtain(
|
|
66
|
+
args: string[],
|
|
67
|
+
flags: Record<string, string | boolean>,
|
|
68
|
+
): Promise<CommandResult> {
|
|
69
|
+
const username = resolveUser(args, flags);
|
|
70
|
+
if (!username) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error:
|
|
74
|
+
'Username required\n\nUsage: celilo token obtain --user <username> [--identifier <id>]',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const identifier = getFlag(flags, 'identifier').trim() || defaultIdentifier(username);
|
|
78
|
+
const description = getFlag(flags, 'description').trim() || undefined;
|
|
79
|
+
|
|
80
|
+
const idp = await loadIdp();
|
|
81
|
+
if (isError(idp)) return idp;
|
|
82
|
+
|
|
83
|
+
const result = await idp.create_token({ username, identifier, description });
|
|
84
|
+
const verb = result.created ? 'Minted new' : 'Returned existing';
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
message: `${verb} per-user token for '${username}' (identifier: ${identifier})
|
|
89
|
+
|
|
90
|
+
${result.token}
|
|
91
|
+
|
|
92
|
+
Treat as a secret — store it now (CELILO_PUBLISH_TOKEN, or \`celilo author init --token …\`).
|
|
93
|
+
celilo persists nothing; the idp stores it hashed.
|
|
94
|
+
Revoke with: celilo token revoke ${identifier}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* `celilo token list --user <username>`
|
|
100
|
+
* Lists the user's API tokens (metadata only — never the bearer string).
|
|
101
|
+
*/
|
|
102
|
+
export async function handleTokenList(
|
|
103
|
+
args: string[],
|
|
104
|
+
flags: Record<string, string | boolean>,
|
|
105
|
+
): Promise<CommandResult> {
|
|
106
|
+
const username = resolveUser(args, flags);
|
|
107
|
+
if (!username) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: 'Username required\n\nUsage: celilo token list --user <username>',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const idp = await loadIdp();
|
|
115
|
+
if (isError(idp)) return idp;
|
|
116
|
+
|
|
117
|
+
const tokens = await idp.list_tokens({ username });
|
|
118
|
+
if (tokens.length === 0) {
|
|
119
|
+
return { success: true, message: `No API tokens for '${username}'.` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const lines = tokens.map((t) => {
|
|
123
|
+
const desc = t.description ? ` — ${t.description}` : '';
|
|
124
|
+
const exp = t.expiring && t.expires ? ` (expires ${t.expires})` : '';
|
|
125
|
+
return ` ${t.identifier}${desc}${exp}`;
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
message: `API tokens for '${username}':\n${lines.join('\n')}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `celilo token revoke <identifier>` (or `--identifier <id>`)
|
|
135
|
+
* Deletes a token at the idp; the next publish with it is denied.
|
|
136
|
+
*/
|
|
137
|
+
export async function handleTokenRevoke(
|
|
138
|
+
args: string[],
|
|
139
|
+
flags: Record<string, string | boolean>,
|
|
140
|
+
): Promise<CommandResult> {
|
|
141
|
+
const identifier = getArg(args, 0)?.trim() || getFlag(flags, 'identifier').trim();
|
|
142
|
+
if (!identifier) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: 'Identifier required\n\nUsage: celilo token revoke <identifier>',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const idp = await loadIdp();
|
|
150
|
+
if (isError(idp)) return idp;
|
|
151
|
+
|
|
152
|
+
const { revoked } = await idp.revoke_token({ identifier });
|
|
153
|
+
if (!revoked) {
|
|
154
|
+
return { success: false, error: `No token with identifier '${identifier}' found.` };
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
message: `Revoked token '${identifier}'. The next publish using it will be denied.`,
|
|
159
|
+
};
|
|
160
|
+
}
|
package/src/cli/completion.ts
CHANGED
|
@@ -30,9 +30,11 @@ export async function getCompletions(words: string[], current: number): Promise<
|
|
|
30
30
|
if (currentIndex === 0) {
|
|
31
31
|
const commands = [
|
|
32
32
|
'api',
|
|
33
|
+
'apt-upgrade',
|
|
33
34
|
'audit',
|
|
34
35
|
'backup',
|
|
35
36
|
'capability',
|
|
37
|
+
'commands',
|
|
36
38
|
'dns',
|
|
37
39
|
'completion',
|
|
38
40
|
'events',
|
|
@@ -44,12 +46,14 @@ export async function getCompletions(words: string[], current: number): Promise<
|
|
|
44
46
|
'package',
|
|
45
47
|
'proxmox',
|
|
46
48
|
'publish',
|
|
49
|
+
'registry',
|
|
47
50
|
'restore',
|
|
48
51
|
'service',
|
|
49
52
|
'status',
|
|
50
53
|
'storage',
|
|
51
54
|
'subscribers',
|
|
52
55
|
'system',
|
|
56
|
+
'token',
|
|
53
57
|
'version',
|
|
54
58
|
];
|
|
55
59
|
return filterSuggestions(commands, args[0] || '');
|
|
@@ -68,6 +72,22 @@ export async function getCompletions(words: string[], current: number): Promise<
|
|
|
68
72
|
return filterSuggestions(subcommands, args[1] || '');
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
// Registry subcommands
|
|
76
|
+
if (command === 'registry' && currentIndex === 1) {
|
|
77
|
+
return filterSuggestions(['token', 'owner'], args[1] || '');
|
|
78
|
+
}
|
|
79
|
+
if (command === 'registry' && args[1] === 'token' && currentIndex === 2) {
|
|
80
|
+
return filterSuggestions(['add', 'rm'], args[2] || '');
|
|
81
|
+
}
|
|
82
|
+
if (command === 'registry' && args[1] === 'owner' && currentIndex === 2) {
|
|
83
|
+
return filterSuggestions(['list', 'show', 'set'], args[2] || '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Token subcommands (contributor identity tokens)
|
|
87
|
+
if (command === 'token' && currentIndex === 1) {
|
|
88
|
+
return filterSuggestions(['obtain', 'list', 'revoke'], args[1] || '');
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
// Capability info - complete with capability names
|
|
72
92
|
if (command === 'capability' && args[1] === 'info' && currentIndex === 2) {
|
|
73
93
|
const db = getDb();
|
|
@@ -159,6 +179,7 @@ export async function getCompletions(words: string[], current: number): Promise<
|
|
|
159
179
|
'run-hook',
|
|
160
180
|
'secret',
|
|
161
181
|
'status',
|
|
182
|
+
'where',
|
|
162
183
|
'terraform-unlock',
|
|
163
184
|
'types',
|
|
164
185
|
'validate',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { COMMANDS } from '
|
|
3
|
-
import type { CommandDef } from '
|
|
2
|
+
import { COMMANDS } from '@celilo/core';
|
|
3
|
+
import type { CommandDef } from '@celilo/core';
|
|
4
4
|
import { generateRichZshCompletion } from './generate-zsh-completion';
|
|
5
5
|
|
|
6
6
|
describe('Zsh Completion Generator', () => {
|
|
@@ -79,12 +79,15 @@ describe('Zsh Completion Generator', () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
test('all subcommand descriptions are present', () => {
|
|
82
|
+
function escapeDesc(text: string): string {
|
|
83
|
+
return text.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "'\\''");
|
|
84
|
+
}
|
|
82
85
|
function checkDescriptions(commands: CommandDef[]): void {
|
|
83
86
|
for (const cmd of commands) {
|
|
84
87
|
if (cmd.subcommands) {
|
|
85
88
|
for (const sub of cmd.subcommands) {
|
|
86
|
-
// Description should appear in the _commands function
|
|
87
|
-
expect(output).toContain(sub.description
|
|
89
|
+
// Description should appear (escaped) in the _commands function
|
|
90
|
+
expect(output).toContain(escapeDesc(sub.description));
|
|
88
91
|
}
|
|
89
92
|
checkDescriptions(cmd.subcommands);
|
|
90
93
|
}
|
|
@@ -92,4 +95,19 @@ describe('Zsh Completion Generator', () => {
|
|
|
92
95
|
}
|
|
93
96
|
checkDescriptions(COMMANDS);
|
|
94
97
|
});
|
|
98
|
+
|
|
99
|
+
test('generated completion is valid zsh syntax', () => {
|
|
100
|
+
// Recurrence gate: any unescaped quote/redirect in a description breaks the
|
|
101
|
+
// whole file (an apostrophe in "account's" once did). zsh -n parses without
|
|
102
|
+
// executing. Skip cleanly where zsh is unavailable (minimal CI images).
|
|
103
|
+
let zsh: ReturnType<typeof Bun.spawnSync>;
|
|
104
|
+
try {
|
|
105
|
+
zsh = Bun.spawnSync(['zsh', '-nc', output]);
|
|
106
|
+
} catch {
|
|
107
|
+
return; // ponytail: no zsh here — nothing to check
|
|
108
|
+
}
|
|
109
|
+
if (zsh.exitCode === null) return;
|
|
110
|
+
expect(zsh.stderr?.toString() ?? '').toBe('');
|
|
111
|
+
expect(zsh.exitCode).toBe(0);
|
|
112
|
+
});
|
|
95
113
|
});
|
|
@@ -9,14 +9,18 @@
|
|
|
9
9
|
* should never be hand-edited.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { ArgDef, CommandDef, FlagDef } from '
|
|
12
|
+
import type { ArgDef, CommandDef, FlagDef } from '@celilo/core';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Escape a string for use in zsh completion descriptions.
|
|
16
|
-
*
|
|
16
|
+
* Descriptions are emitted inside single-quoted zsh strings, so:
|
|
17
|
+
* - backslash and colon need escaping in _describe / _arguments specs
|
|
18
|
+
* - a literal apostrophe must close-quote, emit an escaped quote, and reopen
|
|
19
|
+
* ('...'\''...') — otherwise it terminates the string early and the rest of
|
|
20
|
+
* the file mis-parses (e.g. "account's" broke completion at the next `->`).
|
|
17
21
|
*/
|
|
18
22
|
function escapeZshDescription(text: string): string {
|
|
19
|
-
return text.replace(/\\/g, '\\\\').replace(/:/g, '\\:');
|
|
23
|
+
return text.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "'\\''");
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
package/src/cli/index.ts
CHANGED
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* Orchestration function (Rule 10.1) - routes commands to handlers
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { COMMANDS, type CommandDef, resolveRemote, runRemoteClient } from '@celilo/core';
|
|
7
8
|
import * as p from '@clack/prompts';
|
|
8
|
-
import { resolveRemote, runRemoteClient } from '../api/remote-client';
|
|
9
9
|
import { CLIServerRequestSchema, parseJsonWithValidation } from '../validation/schemas';
|
|
10
|
-
import { COMMANDS, type CommandDef } from './command-registry';
|
|
11
10
|
import {
|
|
12
11
|
handleApiAuthorizedKeys,
|
|
13
12
|
handleApiGrant,
|
|
@@ -15,8 +14,10 @@ import {
|
|
|
15
14
|
handleApiList,
|
|
16
15
|
handleApiRevoke,
|
|
17
16
|
} from './commands/api';
|
|
17
|
+
import { handleAptUpgrade } from './commands/apt-upgrade';
|
|
18
18
|
import { handleCapabilityInfo } from './commands/capability-info';
|
|
19
19
|
import { handleCapabilityList } from './commands/capability-list';
|
|
20
|
+
import { handleCommands } from './commands/commands-json';
|
|
20
21
|
import { handleCompletion } from './commands/completion';
|
|
21
22
|
import { handleDnsRegistrations } from './commands/dns';
|
|
22
23
|
import {
|
|
@@ -76,11 +77,11 @@ import { handleModuleUpdate } from './commands/module-update';
|
|
|
76
77
|
import { handleModuleUpgrade } from './commands/module-upgrade';
|
|
77
78
|
import { moduleVerify } from './commands/module-verify';
|
|
78
79
|
import { handleModuleVersion } from './commands/module-version';
|
|
80
|
+
import { handleModuleWhere } from './commands/module-where';
|
|
79
81
|
import { handlePackage } from './commands/package';
|
|
80
82
|
import { handleProxmoxInstanceList } from './commands/proxmox-instance-list';
|
|
81
83
|
import { handleProxmoxInstanceResize } from './commands/proxmox-instance-resize';
|
|
82
84
|
import { handleProxmoxNodeList } from './commands/proxmox-node-list';
|
|
83
|
-
import { main as runPublish } from './commands/publish';
|
|
84
85
|
import { handleSecretList } from './commands/secret-list';
|
|
85
86
|
import { handleSecretSet } from './commands/secret-set';
|
|
86
87
|
import { handleServiceAddDigitalOcean } from './commands/service-add-digitalocean';
|
|
@@ -188,12 +189,16 @@ Commands:
|
|
|
188
189
|
restore Restore a celilo-mgmt backup from a local file (fresh-bootstrap path)
|
|
189
190
|
machine Manage machine pool (bring-your-own-hardware)
|
|
190
191
|
system Manage system configuration
|
|
192
|
+
apt-upgrade Upgrade the deb-installed celilo packages + apply migrations
|
|
191
193
|
ipam Manage IP address and VMID allocations and reservations
|
|
192
194
|
proxmox Proxmox cluster introspection (proxmox node list)
|
|
193
195
|
publish Publish workspace packages to npm and modules to celilo.computer
|
|
196
|
+
registry Administer the module registry (append-safe publish-token management)
|
|
197
|
+
token Manage contributor identity tokens (idp-issued per-user publish tokens)
|
|
194
198
|
subscribers Manage build-bus subscribers (cross-machine publish-event delivery)
|
|
195
199
|
api Manage remote-API access (principals, grants, authorized_keys)
|
|
196
200
|
completion Generate shell completion scripts (bash/zsh)
|
|
201
|
+
commands Print the CLI command registry as JSON (drives @celilo/mcp)
|
|
197
202
|
|
|
198
203
|
help, --help, -h Show this help message
|
|
199
204
|
|
|
@@ -298,7 +303,7 @@ Description:
|
|
|
298
303
|
Modules declare \`subscriptions:\` in their manifests; \`celilo module deploy\`
|
|
299
304
|
emits lifecycle events that subscribers react to.
|
|
300
305
|
|
|
301
|
-
See infra/
|
|
306
|
+
See infra/openspec/specs/event-bus/spec.md for the full design.
|
|
302
307
|
|
|
303
308
|
Examples:
|
|
304
309
|
celilo events run # foreground dispatcher
|
|
@@ -337,7 +342,7 @@ Description:
|
|
|
337
342
|
package name glob). The publisher signs every event with the
|
|
338
343
|
subscriber's secret; the receiver verifies before reacting.
|
|
339
344
|
|
|
340
|
-
This is the static-config first cut of
|
|
345
|
+
This is the static-config first cut of openspec/changes/build-bus-poll-cd/proposal.md — operators
|
|
341
346
|
manage subscribers explicitly. Auto-discovery via the celilo
|
|
342
347
|
registry switchboard is a follow-on.
|
|
343
348
|
|
|
@@ -430,6 +435,79 @@ Examples:
|
|
|
430
435
|
return { success: true, message: helpText };
|
|
431
436
|
}
|
|
432
437
|
|
|
438
|
+
function displayRegistryHelp(): CommandResult {
|
|
439
|
+
const helpText = `
|
|
440
|
+
Celilo - Module Registry Administration
|
|
441
|
+
|
|
442
|
+
Usage:
|
|
443
|
+
celilo registry <subcommand> [args...]
|
|
444
|
+
|
|
445
|
+
Subcommands:
|
|
446
|
+
token add <token> Append a publish token (append-safe; never clobbers others)
|
|
447
|
+
token rm <token> Remove one publish token, leaving the rest intact
|
|
448
|
+
owner list List the module-owner table (who may publish what)
|
|
449
|
+
owner show <module> Show the owner of one module name
|
|
450
|
+
owner set <module> <sub> Reassign a module name to a new owner (admin)
|
|
451
|
+
|
|
452
|
+
Description:
|
|
453
|
+
The celilo-registry module's publish_tokens secret is a newline-separated list
|
|
454
|
+
of bootstrap/admin publish tokens. Editing it with 'module secret set' OVERWRITES
|
|
455
|
+
the whole list, clobbering every other holder. These commands read-modify-write
|
|
456
|
+
the list instead.
|
|
457
|
+
|
|
458
|
+
The module-owner table (owner *) gates which module NAMES a verified publisher
|
|
459
|
+
may publish: the first verified publisher of an unclaimed name owns it
|
|
460
|
+
(first-publish-claims); thereafter only that owner or an admin may publish it.
|
|
461
|
+
'owner set' is the admin reassignment. These read the admin token from the local
|
|
462
|
+
publish_tokens list; pass --registry to target a non-default registry.
|
|
463
|
+
|
|
464
|
+
Run on the host where celilo-registry is deployed (where the master key lives).
|
|
465
|
+
|
|
466
|
+
Examples:
|
|
467
|
+
celilo registry token add ci-token-abc123
|
|
468
|
+
celilo registry owner list
|
|
469
|
+
celilo registry owner show homebridge
|
|
470
|
+
celilo registry owner set homebridge alice
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
return { success: true, message: helpText.trim() };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function displayTokenHelp(): CommandResult {
|
|
477
|
+
const helpText = `
|
|
478
|
+
Celilo - Contributor Identity Tokens (idp-issued, per-user)
|
|
479
|
+
|
|
480
|
+
Usage:
|
|
481
|
+
celilo token <subcommand> [args...]
|
|
482
|
+
|
|
483
|
+
Subcommands:
|
|
484
|
+
obtain --user <username> Mint (or return) a per-user API token for publishing
|
|
485
|
+
list --user <username> List a user's API tokens (metadata only, no secret)
|
|
486
|
+
revoke <identifier> Revoke a token by identifier; next publish denied
|
|
487
|
+
|
|
488
|
+
Options:
|
|
489
|
+
--user <username> The idp user the token is bound to
|
|
490
|
+
--identifier <id> Token identifier (default: celilo-publish-<user>)
|
|
491
|
+
--description <text> Human-readable description (obtain only)
|
|
492
|
+
|
|
493
|
+
Description:
|
|
494
|
+
Issues per-user API tokens from the idp (authentik) so a module contributor
|
|
495
|
+
authenticates publishes AS THEMSELVES — no admin/shared token on their machine.
|
|
496
|
+
The token is consumed by 'celilo author init'. Shown once at mint; the idp
|
|
497
|
+
stores it hashed and celilo persists nothing.
|
|
498
|
+
|
|
499
|
+
Distinct from 'celilo registry token add/rm', which manages the raw bootstrap
|
|
500
|
+
publish_tokens list. Run on the host where the idp is deployed (celilo-mgr).
|
|
501
|
+
|
|
502
|
+
Examples:
|
|
503
|
+
celilo token obtain --user alice
|
|
504
|
+
celilo token list --user alice
|
|
505
|
+
celilo token revoke celilo-publish-alice
|
|
506
|
+
`;
|
|
507
|
+
|
|
508
|
+
return { success: true, message: helpText.trim() };
|
|
509
|
+
}
|
|
510
|
+
|
|
433
511
|
function displayCapabilityHelp(): CommandResult {
|
|
434
512
|
const helpText = `
|
|
435
513
|
Celilo - Capability Management
|
|
@@ -1096,11 +1174,20 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1096
1174
|
return handleStatus();
|
|
1097
1175
|
}
|
|
1098
1176
|
|
|
1177
|
+
// Handle commands command (serialize the registry; drives @celilo/mcp)
|
|
1178
|
+
if (parsed.command === 'commands') {
|
|
1179
|
+
return handleCommands(parsed.args, parsed.flags);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1099
1182
|
// Top-level alias: `celilo audit` → `celilo system audit`
|
|
1100
1183
|
if (parsed.command === 'audit') {
|
|
1101
1184
|
return handleSystemAudit(parsed.args, parsed.flags);
|
|
1102
1185
|
}
|
|
1103
1186
|
|
|
1187
|
+
if (parsed.command === 'apt-upgrade') {
|
|
1188
|
+
return handleAptUpgrade(parsed.args, parsed.flags);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1104
1191
|
// Route commands
|
|
1105
1192
|
if (parsed.command === 'package') {
|
|
1106
1193
|
// Handle package --help
|
|
@@ -1194,6 +1281,71 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1194
1281
|
};
|
|
1195
1282
|
}
|
|
1196
1283
|
|
|
1284
|
+
if (parsed.command === 'registry') {
|
|
1285
|
+
if (parsed.flags.help || parsed.flags.h || !parsed.subcommand) {
|
|
1286
|
+
return displayRegistryHelp();
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const registryFlagError = checkFlags('registry', parsed.subcommand, parsed.flags, parsed.args);
|
|
1290
|
+
if (registryFlagError) return registryFlagError;
|
|
1291
|
+
|
|
1292
|
+
if (parsed.subcommand === 'token') {
|
|
1293
|
+
const action = parsed.args[0];
|
|
1294
|
+
const tokenArgs = parsed.args.slice(1);
|
|
1295
|
+
const { handleRegistryTokenAdd, handleRegistryTokenRm } = await import(
|
|
1296
|
+
'./commands/registry-token'
|
|
1297
|
+
);
|
|
1298
|
+
if (action === 'add') return handleRegistryTokenAdd(tokenArgs);
|
|
1299
|
+
if (action === 'rm') return handleRegistryTokenRm(tokenArgs);
|
|
1300
|
+
return {
|
|
1301
|
+
success: false,
|
|
1302
|
+
error:
|
|
1303
|
+
'Token action required (add or rm)\n\nUsage:\n celilo registry token add <token>\n celilo registry token rm <token>',
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (parsed.subcommand === 'owner') {
|
|
1308
|
+
const action = parsed.args[0];
|
|
1309
|
+
const ownerArgs = parsed.args.slice(1);
|
|
1310
|
+
const { handleRegistryOwnerList, handleRegistryOwnerShow, handleRegistryOwnerSet } =
|
|
1311
|
+
await import('./commands/registry-owner');
|
|
1312
|
+
if (action === 'list') return handleRegistryOwnerList(parsed.flags);
|
|
1313
|
+
if (action === 'show') return handleRegistryOwnerShow(ownerArgs, parsed.flags);
|
|
1314
|
+
if (action === 'set') return handleRegistryOwnerSet(ownerArgs, parsed.flags);
|
|
1315
|
+
return {
|
|
1316
|
+
success: false,
|
|
1317
|
+
error:
|
|
1318
|
+
'Owner action required (list, show, or set)\n\nUsage:\n celilo registry owner list\n celilo registry owner show <module>\n celilo registry owner set <module> <owner-sub>',
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return {
|
|
1323
|
+
success: false,
|
|
1324
|
+
error: `Unknown registry subcommand: ${parsed.subcommand}\n\nRun "celilo registry --help" for usage`,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (parsed.command === 'token') {
|
|
1329
|
+
if (parsed.flags.help || parsed.flags.h || !parsed.subcommand) {
|
|
1330
|
+
return displayTokenHelp();
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const tokenFlagError = checkFlags('token', parsed.subcommand, parsed.flags, parsed.args);
|
|
1334
|
+
if (tokenFlagError) return tokenFlagError;
|
|
1335
|
+
|
|
1336
|
+
const { handleTokenObtain, handleTokenList, handleTokenRevoke } = await import(
|
|
1337
|
+
'./commands/token'
|
|
1338
|
+
);
|
|
1339
|
+
if (parsed.subcommand === 'obtain') return handleTokenObtain(parsed.args, parsed.flags);
|
|
1340
|
+
if (parsed.subcommand === 'list') return handleTokenList(parsed.args, parsed.flags);
|
|
1341
|
+
if (parsed.subcommand === 'revoke') return handleTokenRevoke(parsed.args, parsed.flags);
|
|
1342
|
+
|
|
1343
|
+
return {
|
|
1344
|
+
success: false,
|
|
1345
|
+
error: `Unknown token subcommand: ${parsed.subcommand}\n\nRun "celilo token --help" for usage`,
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1197
1349
|
if (parsed.command === 'publish') {
|
|
1198
1350
|
if (parsed.flags.help || parsed.flags.h) {
|
|
1199
1351
|
return displayPublishHelp();
|
|
@@ -1206,6 +1358,15 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1206
1358
|
// flag identity since the parser canonicalises those.
|
|
1207
1359
|
const publishIdx = process.argv.indexOf('publish');
|
|
1208
1360
|
const publishArgv = publishIdx >= 0 ? process.argv.slice(publishIdx + 1) : [...parsed.args];
|
|
1361
|
+
// Lazy-load the publish command: it's monorepo-only release tooling whose
|
|
1362
|
+
// transitive import chain reaches scripts/workspace-graph.ts, which does NOT
|
|
1363
|
+
// ship in @celilo/cli's files[]. A static top-level import pulled that into
|
|
1364
|
+
// EVERY command's load graph, so `celilo --version` in a consumer install
|
|
1365
|
+
// crashed on the unresolvable module (WORKSPACE_PACKAGE_GRAPH.md — the D3
|
|
1366
|
+
// failure, deeper than the module-load throw). Loading it only when the
|
|
1367
|
+
// operator actually runs `celilo publish` (which only happens in the
|
|
1368
|
+
// monorepo) keeps every other command free of the dependency.
|
|
1369
|
+
const { main: runPublish } = await import('./commands/publish');
|
|
1209
1370
|
await runPublish(publishArgv);
|
|
1210
1371
|
// runPublish handles its own console output (multi-phase, multi-line);
|
|
1211
1372
|
// returning an empty success message tells the outer CLI loop to skip
|
|
@@ -1291,9 +1452,11 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1291
1452
|
case 'import':
|
|
1292
1453
|
return handleModuleImport(parsed.args, parsed.flags);
|
|
1293
1454
|
case 'list':
|
|
1294
|
-
return handleModuleList();
|
|
1455
|
+
return handleModuleList(parsed.flags);
|
|
1295
1456
|
case 'status':
|
|
1296
1457
|
return handleModuleStatus(parsed.args);
|
|
1458
|
+
case 'where':
|
|
1459
|
+
return handleModuleWhere(parsed.args, parsed.flags);
|
|
1297
1460
|
case 'logs':
|
|
1298
1461
|
return handleModuleLogs(parsed.args, parsed.flags);
|
|
1299
1462
|
case 'health':
|
|
@@ -1685,7 +1848,7 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1685
1848
|
}
|
|
1686
1849
|
|
|
1687
1850
|
if (parsed.command === 'restore') {
|
|
1688
|
-
// Phase 4 of
|
|
1851
|
+
// Phase 4 of openspec/specs/management-server-backup/spec.md — top-level
|
|
1689
1852
|
// fresh-bootstrap restore. Distinct from `celilo backup restore
|
|
1690
1853
|
// <id>` (which restores from a configured storage destination
|
|
1691
1854
|
// using an existing backup record); this one takes a local file
|
package/src/cli/parser.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the `celilo restore` top-level command (Phase 4 of
|
|
3
|
-
*
|
|
3
|
+
* openspec/specs/management-server-backup/spec.md).
|
|
4
4
|
*
|
|
5
5
|
* Covers the CLI surface: routing, argument parsing, pre-flight
|
|
6
6
|
* gating, error propagation. The actual file-direct restore +
|
package/src/db/client.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function createDbClient(config?: Partial<DatabaseConfig>) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// One-time upgrade backfill for the target_ip → module_systems refactor
|
|
95
|
-
// (
|
|
95
|
+
// (openspec/specs/module-systems-addressing/spec.md). migrate() above has ensured the
|
|
96
96
|
// module_systems table exists (migration 0007). A deployment created before
|
|
97
97
|
// the refactor has its host data only in module_configs /
|
|
98
98
|
// ip_allocations / module_infrastructure and an EMPTY module_systems, so its
|