feat(vault): scaffold vault package and command registration
This commit is contained in:
parent
07e8c98c7c
commit
1281bd4092
22 changed files with 766 additions and 3 deletions
169
docs/features/vault.md
Normal file
169
docs/features/vault.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# Dew Vault Secret Manager
|
||||
|
||||
Dew Vault is a secret manager for your projects. It helps you keep secrets out of
|
||||
version control by storing each secret as an encrypted file under `.project/vault`.
|
||||
By default the vault password is stored in `.project/secrets/dew.vault.password`.
|
||||
|
||||
## Config
|
||||
|
||||
Vault settings live in `.project/dew.yaml` under `dew.vault`.
|
||||
|
||||
```yaml
|
||||
dew:
|
||||
vault:
|
||||
password_file: .project/secrets/dew.vault.password
|
||||
storage_dir: .project/vault
|
||||
generators:
|
||||
postgres_password:
|
||||
type: random_password
|
||||
description: Generate random PostgreSQL passwords.
|
||||
config:
|
||||
length: 64
|
||||
include_symbols: true
|
||||
jwt_secret:
|
||||
type: random_token
|
||||
description: Generate JWT signing secrets.
|
||||
config:
|
||||
encoding: base64
|
||||
bytes: 48
|
||||
service_uuid:
|
||||
type: uuid_v4
|
||||
description: Generate stable-looking unique IDs.
|
||||
```
|
||||
|
||||
`generators` maps a generator name (for example `postgres_password`) to a built-in
|
||||
generator definition. Values under `config` are defaults and can be overridden per
|
||||
run or stored in secret rotation metadata.
|
||||
|
||||
Built-in generator types are resolved inside Dew, so secrets can be generated
|
||||
without depending on host binaries.
|
||||
|
||||
## Commands
|
||||
|
||||
Most commands support `--format [default|json]` (default is `default`) for
|
||||
machine-friendly automation.
|
||||
|
||||
### Initialize Vault
|
||||
|
||||
Initialize the vault storage and metadata.
|
||||
|
||||
```bash
|
||||
dew vault init
|
||||
|
||||
dew vault init --password-file .project/secrets/dew.vault.password
|
||||
```
|
||||
|
||||
### List all secrets
|
||||
|
||||
List stored secrets.
|
||||
|
||||
```bash
|
||||
dew vault list
|
||||
dew vault list --format json
|
||||
```
|
||||
|
||||
### Set a secret
|
||||
|
||||
`set` stores or replaces a secret and optional metadata.
|
||||
|
||||
```bash
|
||||
dew vault set <secret-name> # Prompts for secret value
|
||||
dew vault set <secret-name> --env ENV_VAR_NAME # Uses value from environment variable
|
||||
dew vault set <secret-name> --file /path/to/secret.txt # Uses value from file
|
||||
echo "secret value" | dew vault set <secret-name> # Uses piped stdin
|
||||
|
||||
# Include metadata for automated rotation requirements
|
||||
dew vault set DB_PASSWORD --metadata '{"rotation":{"enabled":true,"generator":"postgres_password","length":64}}'
|
||||
dew vault set DB_PASSWORD --metadata-file .project/vault/db_password.meta.json
|
||||
```
|
||||
|
||||
### Get a secret
|
||||
|
||||
`get` retrieves a secret by name.
|
||||
|
||||
```bash
|
||||
dew vault get <secret-name>
|
||||
dew vault get <secret-name> --format json
|
||||
```
|
||||
|
||||
### Update a secret
|
||||
|
||||
`update` patches secret metadata and/or value. Omit value source flags to edit
|
||||
metadata only.
|
||||
|
||||
```bash
|
||||
dew vault update <secret-name> --env ROLLED_PASSWORD
|
||||
dew vault update <secret-name> --metadata '{"rotation":{"enabled":false}}'
|
||||
dew vault update <secret-name> --metadata-file .project/vault/db_password.meta.json
|
||||
```
|
||||
|
||||
### Rename a secret
|
||||
|
||||
`rename` changes a secret identifier while preserving value and metadata.
|
||||
|
||||
```bash
|
||||
dew vault rename OLD_NAME NEW_NAME
|
||||
dew vault rename OLD_NAME NEW_NAME --format json
|
||||
```
|
||||
|
||||
### Generate a secret value
|
||||
|
||||
`generate` runs a built-in generator without writing to the vault by default.
|
||||
|
||||
```bash
|
||||
dew vault generate postgres_password --length 64 --include_symbols
|
||||
dew vault generate jwt_secret --bytes 64 --encoding base64
|
||||
dew vault generate postgres_password --service payments --username app_user --format json
|
||||
```
|
||||
|
||||
Pipe generated output directly into `set` when needed:
|
||||
|
||||
```bash
|
||||
dew vault generate postgres_password --service payments | dew vault set DB_PASSWORD
|
||||
```
|
||||
|
||||
### Rotate secrets
|
||||
|
||||
`rotate` rewraps secrets with a new vault password when run without a name.
|
||||
When run with a secret name, it rotates only that secret. For a secret with rotation
|
||||
metadata (`rotation.enabled: true` and `rotation.generator`), Dew invokes that
|
||||
configured built-in generator using the provided rotation values.
|
||||
|
||||
```bash
|
||||
dew vault rotate
|
||||
dew vault rotate <secret-name>
|
||||
dew vault rotate <secret-name> --format json
|
||||
```
|
||||
|
||||
### Delete a secret
|
||||
|
||||
`delete` removes a secret and metadata from the vault.
|
||||
|
||||
```bash
|
||||
dew vault delete <secret-name>
|
||||
dew vault delete <secret-name> --format json
|
||||
```
|
||||
|
||||
### Metadata format for rotation-aware secrets
|
||||
|
||||
Attach arbitrary metadata and include rotation policy details. Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"rotation": {
|
||||
"enabled": true,
|
||||
"generator": "postgres_password",
|
||||
"service": "payments",
|
||||
"username": "app_user",
|
||||
"length": 64
|
||||
},
|
||||
"notes": "Rotate monthly and update app config via sidecar"
|
||||
}
|
||||
```
|
||||
|
||||
Rotation flow:
|
||||
|
||||
1. Define a built-in generator in `dew.yaml` under `dew.vault.generators`.
|
||||
2. Attach `rotation.generator` and generator args to the secret metadata.
|
||||
3. Run `dew vault rotate <secret-name>` to rotate one secret, or `dew vault rotate`
|
||||
to rotate all configured secrets.
|
||||
|
|
@ -2,11 +2,13 @@ import 'package:args/command_runner.dart';
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
import 'package:dew_kanban/dew_kanban.dart' as kanban;
|
||||
import 'package:dew_mcp/dew_mcp.dart' as mcp;
|
||||
import 'package:dew_vault/dew_vault.dart' as vault;
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
final commandRegistry = CommandRegistry();
|
||||
|
||||
kanban.registerCommands(commandRegistry);
|
||||
vault.registerCommands(commandRegistry);
|
||||
mcp.registerCommands(commandRegistry);
|
||||
|
||||
final runner = CommandRunner<void>('dew', 'A project management tool.');
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ dependencies:
|
|||
args: ^2.7.0
|
||||
dew_core: ^0.1.0
|
||||
dew_kanban: ^0.1.0
|
||||
dew_vault: ^0.1.0
|
||||
dew_mcp: ^0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import 'package:args/command_runner.dart';
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
import 'package:dew_kanban/dew_kanban.dart' as kanban;
|
||||
import 'package:dew_mcp/dew_mcp.dart' as mcp;
|
||||
import 'package:dew_vault/dew_vault.dart' as vault;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Builds the same CommandRunner as bin/dew.dart without actually running it.
|
||||
CommandRunner<void> buildRunner() {
|
||||
final commandRegistry = CommandRegistry();
|
||||
kanban.registerCommands(commandRegistry);
|
||||
vault.registerCommands(commandRegistry);
|
||||
mcp.registerCommands(commandRegistry);
|
||||
|
||||
final runner = CommandRunner<void>('dew', 'A project management tool.');
|
||||
|
|
@ -24,9 +26,9 @@ void main() {
|
|||
expect(buildRunner, returnsNormally);
|
||||
});
|
||||
|
||||
test('has kanban, init, and mcp commands registered', () {
|
||||
test('has kanban, vault, init, and mcp commands registered', () {
|
||||
final runner = buildRunner();
|
||||
expect(runner.commands.keys, containsAll(['kanban', 'init', 'mcp']));
|
||||
expect(runner.commands.keys, containsAll(['kanban', 'vault', 'init', 'mcp']));
|
||||
});
|
||||
|
||||
test('--help flag does not throw', () async {
|
||||
|
|
|
|||
9
packages/vault/CHANGELOG.md
Normal file
9
packages/vault/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.2.0 — 2026-05-03
|
||||
|
||||
Initial feature stub package for vault support.
|
||||
|
||||
- Added `dew vault` command registration and MCP tool wiring.
|
||||
- Added vault subcommands for init/list/get/set/update/rename/rotate/generate/delete.
|
||||
- Added `VaultInitHook` scaffold for `.project/vault` and `.project/secrets`.
|
||||
21
packages/vault/LICENSE
Normal file
21
packages/vault/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 artificery-dev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
packages/vault/README.md
Normal file
16
packages/vault/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# dew_vault
|
||||
|
||||
Vault feature package for the [Dew](https://github.com/artificerchris/dew) project
|
||||
management tool.
|
||||
|
||||
This package provides the `dew vault` command surface and registers Vault commands
|
||||
as MCP tools through `DewToolCommand`.
|
||||
|
||||
## Status
|
||||
|
||||
This package is currently a scaffold/stub to establish the command and MCP
|
||||
wiring. It does not yet implement full encrypted secret storage or rotation logic.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
19
packages/vault/lib/dew_vault.dart
Normal file
19
packages/vault/lib/dew_vault.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
library;
|
||||
|
||||
export 'src/dew_vault_base.dart';
|
||||
|
||||
import 'package:dew_core/dew_core.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
|
||||
import 'package:dew_vault/src/dew_vault_base.dart';
|
||||
import 'package:dew_vault/src/vault_init_hook.dart';
|
||||
|
||||
/// Registers all Vault commands and init hooks into [registry].
|
||||
void registerCommands(
|
||||
CommandRegistry registry, {
|
||||
FileSystem fs = const LocalFileSystem(),
|
||||
}) {
|
||||
registry.register(VaultCommand());
|
||||
registry.registerInitHook(VaultInitHook(fs: fs));
|
||||
}
|
||||
30
packages/vault/lib/src/command_output.dart
Normal file
30
packages/vault/lib/src/command_output.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import 'dart:convert';
|
||||
|
||||
String renderVaultOutput({
|
||||
String format = 'default',
|
||||
required String message,
|
||||
Map<String, dynamic>? json,
|
||||
}) {
|
||||
if (format == 'json') {
|
||||
final payload = <String, dynamic>{
|
||||
'message': message,
|
||||
if (json != null) ...json,
|
||||
};
|
||||
return const JsonEncoder.withIndent(' ').convert(payload);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
String requireStringArg(Map<String, dynamic> args, String key) {
|
||||
final value = args[key];
|
||||
if (value == null || value.toString().trim().isEmpty) {
|
||||
throw ArgumentError('Missing required argument: --$key');
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
String formatFromArgs(Map<String, dynamic> args) {
|
||||
final value = args['format'];
|
||||
if (value == null || value.toString().trim().isEmpty) return 'default';
|
||||
return value.toString();
|
||||
}
|
||||
42
packages/vault/lib/src/commands/delete_command.dart
Normal file
42
packages/vault/lib/src/commands/delete_command.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class DeleteCommand extends DewCommand with DewToolCommand {
|
||||
DeleteCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'name',
|
||||
abbr: 'n',
|
||||
mandatory: true,
|
||||
help: 'Secret name.',
|
||||
)
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'delete';
|
||||
|
||||
@override
|
||||
final String description = 'Delete a secret.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_delete_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final secretName = requireStringArg(args, 'name');
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Delete stub executed.',
|
||||
json: {'secret': secretName},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
51
packages/vault/lib/src/commands/generate_command.dart
Normal file
51
packages/vault/lib/src/commands/generate_command.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class GenerateCommand extends DewCommand with DewToolCommand {
|
||||
GenerateCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'generator',
|
||||
abbr: 'g',
|
||||
mandatory: true,
|
||||
help: 'Generator ID.',
|
||||
)
|
||||
..addMultiOption(
|
||||
'arg',
|
||||
help: 'Generator option as key=value. Repeat as needed.',
|
||||
)
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'generate';
|
||||
|
||||
@override
|
||||
final String description = 'Generate a new secret value using a built-in generator.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_generate_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final generator = requireStringArg(args, 'generator');
|
||||
final overrides = args['arg'] as List<dynamic>?;
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Generate stub output.',
|
||||
json: {
|
||||
'generator': generator,
|
||||
'options': overrides == null ? const <String>[] : overrides,
|
||||
'value': '<generated>',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
37
packages/vault/lib/src/commands/get_command.dart
Normal file
37
packages/vault/lib/src/commands/get_command.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class GetCommand extends DewCommand with DewToolCommand {
|
||||
GetCommand() {
|
||||
argParser
|
||||
..addOption('name', abbr: 'n', mandatory: true, help: 'Secret name.')
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'get';
|
||||
|
||||
@override
|
||||
final String description = 'Get a secret value.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_get_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final secretName = requireStringArg(args, 'name');
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Get stub value: [redacted].',
|
||||
json: {'secret': secretName},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
51
packages/vault/lib/src/commands/init_command.dart
Normal file
51
packages/vault/lib/src/commands/init_command.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class VaultInitCommand extends DewCommand with DewToolCommand {
|
||||
VaultInitCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'password-file',
|
||||
abbr: 'p',
|
||||
defaultsTo: '.project/secrets/dew.vault.password',
|
||||
help: 'Path to the vault password file to record in config.',
|
||||
)
|
||||
..addOption(
|
||||
'storage-dir',
|
||||
defaultsTo: '.project/vault',
|
||||
help: 'Directory where encrypted secret files are stored.',
|
||||
)
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'init';
|
||||
|
||||
@override
|
||||
final String description = 'Initialise vault directories and defaults.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_init';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final passwordFile = requireStringArg(args, 'password-file');
|
||||
final storageDir = requireStringArg(args, 'storage-dir');
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Vault init stub completed.',
|
||||
json: {
|
||||
'password_file': passwordFile,
|
||||
'storage_dir': storageDir,
|
||||
'initialized': true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
34
packages/vault/lib/src/commands/list_command.dart
Normal file
34
packages/vault/lib/src/commands/list_command.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class ListCommand extends DewCommand with DewToolCommand {
|
||||
ListCommand() {
|
||||
argParser.addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'list';
|
||||
|
||||
@override
|
||||
final String description = 'List vault secrets.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_list_secrets';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'No secrets found (stubbed vault).',
|
||||
json: {'secrets': <String>[], 'count': 0},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
47
packages/vault/lib/src/commands/rename_command.dart
Normal file
47
packages/vault/lib/src/commands/rename_command.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class RenameCommand extends DewCommand with DewToolCommand {
|
||||
RenameCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'from',
|
||||
mandatory: true,
|
||||
help: 'Current secret name.',
|
||||
)
|
||||
..addOption(
|
||||
'to',
|
||||
mandatory: true,
|
||||
help: 'New secret name.',
|
||||
)
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'rename';
|
||||
|
||||
@override
|
||||
final String description = 'Rename a secret while preserving value and metadata.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_rename_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final from = requireStringArg(args, 'from');
|
||||
final to = requireStringArg(args, 'to');
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Rename stub executed.',
|
||||
json: {'from': from, 'to': to},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
38
packages/vault/lib/src/commands/rotate_command.dart
Normal file
38
packages/vault/lib/src/commands/rotate_command.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class RotateCommand extends DewCommand with DewToolCommand {
|
||||
RotateCommand() {
|
||||
argParser
|
||||
..addOption('name', help: 'Secret name to rotate; omit to rotate vault password.')
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'rotate';
|
||||
|
||||
@override
|
||||
final String description =
|
||||
'Rotate vault password or a single secret value (stub).';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_rotate_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final target = args['name']?.toString();
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Rotate stub executed.',
|
||||
json: {'target': target ?? '<all>', 'scope': target == null ? 'vault' : 'secret'},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
52
packages/vault/lib/src/commands/set_command.dart
Normal file
52
packages/vault/lib/src/commands/set_command.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class SetCommand extends DewCommand with DewToolCommand {
|
||||
SetCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'name',
|
||||
abbr: 'n',
|
||||
mandatory: true,
|
||||
help: 'Secret name.',
|
||||
)
|
||||
..addOption('env', help: 'Use value from an environment variable.')
|
||||
..addOption('file', help: 'Use value from file path.')
|
||||
..addOption('metadata', help: 'JSON object to save as metadata.')
|
||||
..addOption('metadata-file', help: 'Path to JSON metadata file.')
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'set';
|
||||
|
||||
@override
|
||||
final String description = 'Set a secret value and optional metadata.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_set_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final secretName = requireStringArg(args, 'name');
|
||||
final source =
|
||||
args['env'] ?? args['file'] ?? args['metadata'] ?? args['metadata-file'];
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Set stub executed.',
|
||||
json: {
|
||||
'secret': secretName,
|
||||
'source': source == null ? 'interactive' : source.toString(),
|
||||
'status': 'set',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
50
packages/vault/lib/src/commands/update_command.dart
Normal file
50
packages/vault/lib/src/commands/update_command.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import '../command_output.dart';
|
||||
|
||||
class UpdateCommand extends DewCommand with DewToolCommand {
|
||||
UpdateCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'name',
|
||||
abbr: 'n',
|
||||
mandatory: true,
|
||||
help: 'Secret name.',
|
||||
)
|
||||
..addOption('env', help: 'Use value from an environment variable.')
|
||||
..addOption('file', help: 'Use value from file path.')
|
||||
..addOption('metadata', help: 'JSON object to save as metadata.')
|
||||
..addOption('metadata-file', help: 'Path to JSON metadata file.')
|
||||
..addOption(
|
||||
'format',
|
||||
defaultsTo: 'default',
|
||||
allowed: ['default', 'json'],
|
||||
help: 'Output format for this command.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'update';
|
||||
|
||||
@override
|
||||
final String description = 'Update a secret value and/or metadata.';
|
||||
|
||||
@override
|
||||
final String toolName = 'vault_update_secret';
|
||||
|
||||
@override
|
||||
Future<String> callAsTool(Map<String, dynamic> args) async {
|
||||
final format = formatFromArgs(args);
|
||||
final secretName = requireStringArg(args, 'name');
|
||||
final mode = args['env'] != null || args['file'] != null ? 'value-update' : 'metadata-only';
|
||||
return renderVaultOutput(
|
||||
format: format,
|
||||
message: 'Update stub executed.',
|
||||
json: {
|
||||
'secret': secretName,
|
||||
'mode': mode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
35
packages/vault/lib/src/dew_vault_base.dart
Normal file
35
packages/vault/lib/src/dew_vault_base.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
|
||||
import 'commands/delete_command.dart';
|
||||
import 'commands/generate_command.dart';
|
||||
import 'commands/get_command.dart';
|
||||
import 'commands/init_command.dart';
|
||||
import 'commands/list_command.dart';
|
||||
import 'commands/rename_command.dart';
|
||||
import 'commands/rotate_command.dart';
|
||||
import 'commands/set_command.dart';
|
||||
import 'commands/update_command.dart';
|
||||
|
||||
/// Top-level CLI command for all Vault operations.
|
||||
class VaultCommand extends DewCommand {
|
||||
VaultCommand() {
|
||||
addSubcommand(VaultInitCommand());
|
||||
addSubcommand(ListCommand());
|
||||
addSubcommand(SetCommand());
|
||||
addSubcommand(GetCommand());
|
||||
addSubcommand(UpdateCommand());
|
||||
addSubcommand(RenameCommand());
|
||||
addSubcommand(RotateCommand());
|
||||
addSubcommand(GenerateCommand());
|
||||
addSubcommand(DeleteCommand());
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'vault';
|
||||
|
||||
@override
|
||||
final String description = 'Manage project secrets with a local vault.';
|
||||
|
||||
@override
|
||||
Future<void> run() async => printUsage();
|
||||
}
|
||||
39
packages/vault/lib/src/vault_init_hook.dart
Normal file
39
packages/vault/lib/src/vault_init_hook.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:dew_core/dew_core.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class VaultInitHook implements DewInitHook {
|
||||
final FileSystem _fs;
|
||||
|
||||
VaultInitHook({FileSystem fs = const LocalFileSystem()}) : _fs = fs;
|
||||
|
||||
@override
|
||||
Future<void> onInit(
|
||||
String projectRoot,
|
||||
DewConfig config,
|
||||
DewInitOptions options,
|
||||
) async {
|
||||
final vaultDir = p.join(projectRoot, '.project', 'vault');
|
||||
final secretDir = p.join(projectRoot, '.project', 'secrets');
|
||||
|
||||
await _createDir(vaultDir, options.gitkeep);
|
||||
await _createDir(secretDir, options.gitkeep);
|
||||
}
|
||||
|
||||
Future<void> _createDir(String path, bool withGitkeep) async {
|
||||
final dir = _fs.directory(path);
|
||||
final existed = await dir.exists();
|
||||
await dir.create(recursive: true);
|
||||
final relPath = p.join('.project', p.basename(path));
|
||||
if (existed) {
|
||||
print(' found $relPath/');
|
||||
} else {
|
||||
print(' created $relPath/');
|
||||
if (withGitkeep) {
|
||||
await _fs.file(p.join(path, '.gitkeep')).writeAsString('');
|
||||
print(' created $relPath/.gitkeep');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
packages/vault/pubspec.yaml
Normal file
18
packages/vault/pubspec.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
name: dew_vault
|
||||
description: Vault feature package for the Dew project management tool.
|
||||
version: 0.2.0
|
||||
repository: https://github.com/artificerchris/dew
|
||||
issue_tracker: https://github.com/artificerchris/dew/issues
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.4
|
||||
|
||||
dependencies:
|
||||
dew_core: ^0.2.0
|
||||
file: ^7.0.1
|
||||
path: ^1.9.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^6.0.0
|
||||
test: ^1.25.6
|
||||
|
|
@ -6,12 +6,12 @@ publish_to: none
|
|||
|
||||
environment:
|
||||
sdk: ^3.11.4
|
||||
|
||||
workspace:
|
||||
- packages/cli
|
||||
- packages/core
|
||||
- packages/kanban
|
||||
- packages/mcp
|
||||
- packages/vault
|
||||
|
||||
dev_dependencies:
|
||||
file: ^7.0.1
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue