@namera-ai/cli 0.1.3 → 0.1.4
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/dist/commands/index.mjs +21 -0
- package/dist/commands/index.mjs.map +1 -0
- package/dist/commands/keystore/create.mjs +74 -0
- package/dist/commands/keystore/create.mjs.map +1 -0
- package/dist/commands/keystore/decrypt.mjs +71 -0
- package/dist/commands/keystore/decrypt.mjs.map +1 -0
- package/dist/commands/keystore/import.mjs +84 -0
- package/dist/commands/keystore/import.mjs.map +1 -0
- package/dist/commands/keystore/index.mjs +28 -0
- package/dist/commands/keystore/index.mjs.map +1 -0
- package/dist/commands/keystore/info.mjs +62 -0
- package/dist/commands/keystore/info.mjs.map +1 -0
- package/dist/commands/keystore/list.mjs +46 -0
- package/dist/commands/keystore/list.mjs.map +1 -0
- package/dist/commands/keystore/remove.mjs +51 -0
- package/dist/commands/keystore/remove.mjs.map +1 -0
- package/dist/commands/mcp/index.mjs +13 -0
- package/dist/commands/mcp/index.mjs.map +1 -0
- package/dist/commands/mcp/start.mjs +82 -0
- package/dist/commands/mcp/start.mjs.map +1 -0
- package/dist/commands/schema/index.mjs +53 -0
- package/dist/commands/schema/index.mjs.map +1 -0
- package/dist/commands/session-key/create/index.mjs +87 -0
- package/dist/commands/session-key/create/index.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/base.mjs +52 -0
- package/dist/commands/session-key/create/prompts/base.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/call.mjs +85 -0
- package/dist/commands/session-key/create/prompts/call.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/common.mjs +27 -0
- package/dist/commands/session-key/create/prompts/common.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/gas.mjs +19 -0
- package/dist/commands/session-key/create/prompts/gas.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/index.mjs +44 -0
- package/dist/commands/session-key/create/prompts/index.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/rate-limit.mjs +40 -0
- package/dist/commands/session-key/create/prompts/rate-limit.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/signature-caller.mjs +26 -0
- package/dist/commands/session-key/create/prompts/signature-caller.mjs.map +1 -0
- package/dist/commands/session-key/create/prompts/timestamp.mjs +32 -0
- package/dist/commands/session-key/create/prompts/timestamp.mjs.map +1 -0
- package/dist/commands/session-key/index.mjs +26 -0
- package/dist/commands/session-key/index.mjs.map +1 -0
- package/dist/commands/session-key/info.mjs +64 -0
- package/dist/commands/session-key/info.mjs.map +1 -0
- package/dist/commands/session-key/list.mjs +54 -0
- package/dist/commands/session-key/list.mjs.map +1 -0
- package/dist/commands/session-key/remove.mjs +51 -0
- package/dist/commands/session-key/remove.mjs.map +1 -0
- package/dist/commands/session-key/status.mjs +64 -0
- package/dist/commands/session-key/status.mjs.map +1 -0
- package/dist/commands/smart-account/create.mjs +94 -0
- package/dist/commands/smart-account/create.mjs.map +1 -0
- package/dist/commands/smart-account/import.mjs +45 -0
- package/dist/commands/smart-account/import.mjs.map +1 -0
- package/dist/commands/smart-account/index.mjs +30 -0
- package/dist/commands/smart-account/index.mjs.map +1 -0
- package/dist/commands/smart-account/info.mjs +66 -0
- package/dist/commands/smart-account/info.mjs.map +1 -0
- package/dist/commands/smart-account/list.mjs +48 -0
- package/dist/commands/smart-account/list.mjs.map +1 -0
- package/dist/commands/smart-account/remove.mjs +54 -0
- package/dist/commands/smart-account/remove.mjs.map +1 -0
- package/dist/commands/smart-account/status.mjs +63 -0
- package/dist/commands/smart-account/status.mjs.map +1 -0
- package/dist/dto/index.mjs +6 -0
- package/dist/dto/keystore.mjs +44 -0
- package/dist/dto/keystore.mjs.map +1 -0
- package/dist/dto/mcp.mjs +13 -0
- package/dist/dto/mcp.mjs.map +1 -0
- package/dist/dto/session-key.mjs +54 -0
- package/dist/dto/session-key.mjs.map +1 -0
- package/dist/dto/smart-account.mjs +45 -0
- package/dist/dto/smart-account.mjs.map +1 -0
- package/dist/flags/global.mjs +29 -0
- package/dist/flags/global.mjs.map +1 -0
- package/dist/flags/shared.mjs +11 -0
- package/dist/flags/shared.mjs.map +1 -0
- package/dist/helpers/humanize.mjs +153 -0
- package/dist/helpers/humanize.mjs.map +1 -0
- package/dist/helpers/paths.mjs +31 -0
- package/dist/helpers/paths.mjs.map +1 -0
- package/dist/helpers/policy.mjs +89 -0
- package/dist/helpers/policy.mjs.map +1 -0
- package/dist/helpers/pretty.mjs +71 -0
- package/dist/helpers/pretty.mjs.map +1 -0
- package/dist/index.mjs +15 -3264
- package/dist/index.mjs.map +1 -1
- package/dist/layers/config.mjs +124 -0
- package/dist/layers/config.mjs.map +1 -0
- package/dist/layers/index.mjs +11 -0
- package/dist/layers/keystore.mjs +225 -0
- package/dist/layers/keystore.mjs.map +1 -0
- package/dist/layers/mcp-context.mjs +8 -0
- package/dist/layers/mcp-context.mjs.map +1 -0
- package/dist/layers/mcp.mjs +31 -0
- package/dist/layers/mcp.mjs.map +1 -0
- package/dist/layers/output.mjs +21 -0
- package/dist/layers/output.mjs.map +1 -0
- package/dist/layers/prompt.mjs +78 -0
- package/dist/layers/prompt.mjs.map +1 -0
- package/dist/layers/session-key.mjs +218 -0
- package/dist/layers/session-key.mjs.map +1 -0
- package/dist/layers/smart-account.mjs +175 -0
- package/dist/layers/smart-account.mjs.map +1 -0
- package/dist/layers/web3.mjs +83 -0
- package/dist/layers/web3.mjs.map +1 -0
- package/dist/mcp/helpers/common.mjs +9 -0
- package/dist/mcp/helpers/common.mjs.map +1 -0
- package/dist/mcp/helpers/index.mjs +4 -0
- package/dist/mcp/helpers/session-key.mjs +129 -0
- package/dist/mcp/helpers/session-key.mjs.map +1 -0
- package/dist/mcp/index.mjs +43 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/mcp/mcp.mjs +15 -0
- package/dist/mcp/mcp.mjs.map +1 -0
- package/dist/mcp/tools/account/address.mjs +20 -0
- package/dist/mcp/tools/account/address.mjs.map +1 -0
- package/dist/mcp/tools/account/get-balance.mjs +54 -0
- package/dist/mcp/tools/account/get-balance.mjs.map +1 -0
- package/dist/mcp/tools/account/index.mjs +15 -0
- package/dist/mcp/tools/account/index.mjs.map +1 -0
- package/dist/mcp/tools/read/index.mjs +11 -0
- package/dist/mcp/tools/read/index.mjs.map +1 -0
- package/dist/mcp/tools/read/read-contract.mjs +49 -0
- package/dist/mcp/tools/read/read-contract.mjs.map +1 -0
- package/dist/mcp/tools/transaction/execute-transaction.mjs +38 -0
- package/dist/mcp/tools/transaction/execute-transaction.mjs.map +1 -0
- package/dist/mcp/tools/transaction/index.mjs +15 -0
- package/dist/mcp/tools/transaction/index.mjs.map +1 -0
- package/dist/mcp/tools/transaction/native-transfer.mjs +59 -0
- package/dist/mcp/tools/transaction/native-transfer.mjs.map +1 -0
- package/dist/schema/chain.mjs +124 -0
- package/dist/schema/chain.mjs.map +1 -0
- package/dist/schema/common.mjs +29 -0
- package/dist/schema/common.mjs.map +1 -0
- package/dist/schema/index.mjs +5 -0
- package/dist/schema/policy.mjs +114 -0
- package/dist/schema/policy.mjs.map +1 -0
- package/dist/schema/tx.mjs +19 -0
- package/dist/schema/tx.mjs.map +1 -0
- package/dist/types/index.mjs +11 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +10 -11
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { entityName } from "../types/index.mjs";
|
|
3
|
+
import { Context, Data, Effect, FileSystem, Layer, Path } from "effect";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
//#region src/layers/config.ts
|
|
6
|
+
/**
|
|
7
|
+
* Domain error for configuration and entity storage operations.
|
|
8
|
+
*/
|
|
9
|
+
var ConfigManagerError = class extends Data.TaggedError("@namera-ai/cli/ConfigManagerError") {};
|
|
10
|
+
/**
|
|
11
|
+
* Service tag for resolving {@link ConfigManager} from the Effect context.
|
|
12
|
+
*/
|
|
13
|
+
const ConfigManager = Context.Service("@namera-ai/cli/ConfigManager");
|
|
14
|
+
/**
|
|
15
|
+
* Live layer that persists CLI entities in the user's config directory.
|
|
16
|
+
*/
|
|
17
|
+
const layer = Layer.effect(ConfigManager, Effect.gen(function* () {
|
|
18
|
+
const fs = yield* FileSystem.FileSystem;
|
|
19
|
+
const path = yield* Path.Path;
|
|
20
|
+
const getConfigDirPath = () => Effect.gen(function* () {
|
|
21
|
+
const homeDir = yield* Effect.sync(() => os.homedir());
|
|
22
|
+
return path.join(homeDir, ".namera");
|
|
23
|
+
});
|
|
24
|
+
const ensureConfigDirExists = () => Effect.gen(function* () {
|
|
25
|
+
const baseDir = yield* getConfigDirPath();
|
|
26
|
+
const directoriesToCreate = [
|
|
27
|
+
"smart-accounts",
|
|
28
|
+
"session-keys",
|
|
29
|
+
"keystores"
|
|
30
|
+
].map((dir) => path.join(baseDir, dir));
|
|
31
|
+
yield* Effect.forEach(directoriesToCreate, (dirPath) => fs.makeDirectory(dirPath, { recursive: true }).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
32
|
+
code: "InitializationError",
|
|
33
|
+
message: e.message
|
|
34
|
+
})))), { concurrency: "unbounded" });
|
|
35
|
+
});
|
|
36
|
+
const getEntityPath = (entity) => Effect.gen(function* () {
|
|
37
|
+
const baseDir = yield* getConfigDirPath();
|
|
38
|
+
return path.join(baseDir, `${entity.type}s`, entity.alias);
|
|
39
|
+
});
|
|
40
|
+
const checkEntityExists = (entity) => Effect.gen(function* () {
|
|
41
|
+
const entityPath = yield* getEntityPath(entity);
|
|
42
|
+
return yield* fs.exists(entityPath).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
43
|
+
code: e.reason._tag,
|
|
44
|
+
message: e.message
|
|
45
|
+
}))));
|
|
46
|
+
});
|
|
47
|
+
const getEntity = (entity) => Effect.gen(function* () {
|
|
48
|
+
const entityPath = yield* getEntityPath(entity);
|
|
49
|
+
if (yield* checkEntityExists(entity)) {
|
|
50
|
+
const content = yield* fs.readFileString(entityPath);
|
|
51
|
+
return {
|
|
52
|
+
alias: entity.alias,
|
|
53
|
+
content,
|
|
54
|
+
path: entityPath,
|
|
55
|
+
type: entity.type
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return yield* Effect.fail(new ConfigManagerError({
|
|
59
|
+
code: "EntityNotFound",
|
|
60
|
+
message: `${entityName[entity.type]} with alias ${entity.alias} does not exist`
|
|
61
|
+
}));
|
|
62
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
63
|
+
code: e.reason._tag,
|
|
64
|
+
message: e.message
|
|
65
|
+
}))));
|
|
66
|
+
const getEntitiesForType = (type) => Effect.gen(function* () {
|
|
67
|
+
const baseDir = yield* getConfigDirPath();
|
|
68
|
+
const entitiesDir = path.join(baseDir, `${type}s`);
|
|
69
|
+
const effects = (yield* fs.readDirectory(entitiesDir)).map((entityName) => Effect.gen(function* () {
|
|
70
|
+
const entityPath = path.join(entitiesDir, entityName);
|
|
71
|
+
return {
|
|
72
|
+
alias: entityName,
|
|
73
|
+
content: yield* fs.readFileString(entityPath),
|
|
74
|
+
path: entityPath,
|
|
75
|
+
type
|
|
76
|
+
};
|
|
77
|
+
}));
|
|
78
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
79
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
80
|
+
code: e.reason._tag,
|
|
81
|
+
message: e.message
|
|
82
|
+
}))));
|
|
83
|
+
const storeEntity = (entity) => Effect.gen(function* () {
|
|
84
|
+
const entityPath = yield* getEntityPath(entity);
|
|
85
|
+
if (yield* checkEntityExists({
|
|
86
|
+
alias: entity.alias,
|
|
87
|
+
type: entity.type
|
|
88
|
+
})) return yield* Effect.fail(new ConfigManagerError({
|
|
89
|
+
code: "EntityAlreadyExists",
|
|
90
|
+
message: `Entity ${entity.alias} already exists`
|
|
91
|
+
}));
|
|
92
|
+
yield* fs.writeFileString(entityPath, entity.content);
|
|
93
|
+
return {
|
|
94
|
+
alias: entity.alias,
|
|
95
|
+
content: entity.content,
|
|
96
|
+
path: entityPath,
|
|
97
|
+
type: entity.type
|
|
98
|
+
};
|
|
99
|
+
}).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
100
|
+
code: e.reason._tag,
|
|
101
|
+
message: e.message
|
|
102
|
+
}))));
|
|
103
|
+
const removeEntity = (entity) => Effect.gen(function* () {
|
|
104
|
+
const entityPath = yield* getEntityPath(entity);
|
|
105
|
+
yield* fs.remove(entityPath).pipe(Effect.catchTag("PlatformError", (e) => Effect.fail(new ConfigManagerError({
|
|
106
|
+
code: e.reason._tag,
|
|
107
|
+
message: e.message
|
|
108
|
+
}))));
|
|
109
|
+
});
|
|
110
|
+
return ConfigManager.of({
|
|
111
|
+
checkEntityExists,
|
|
112
|
+
ensureConfigDirExists,
|
|
113
|
+
getConfigDirPath,
|
|
114
|
+
getEntitiesForType,
|
|
115
|
+
getEntity,
|
|
116
|
+
getEntityPath,
|
|
117
|
+
storeEntity,
|
|
118
|
+
removeEntity
|
|
119
|
+
});
|
|
120
|
+
}));
|
|
121
|
+
//#endregion
|
|
122
|
+
export { ConfigManager, layer };
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/layers/config.ts"],"sourcesContent":["import os from \"node:os\";\n\nimport { Context, Data, Effect, FileSystem, Layer, Path } from \"effect\";\nimport type { SystemErrorTag } from \"effect/PlatformError\";\n\nimport { type Entity, type EntityType, entityName } from \"@/types\";\n\n/**\n * Effect service for managing the CLI configuration directory and stored entities.\n */\nexport type ConfigManager = {\n /**\n * Ensures the base config directory and entity subdirectories exist.\n */\n ensureConfigDirExists: () => Effect.Effect<void, ConfigManagerError>;\n /**\n * Returns the absolute path to the CLI config directory.\n */\n getConfigDirPath: () => Effect.Effect<string>;\n /**\n * Resolves the absolute storage path for a given entity alias and type.\n *\n * @param entity - Entity type and alias used to build the path.\n */\n getEntityPath: <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) => Effect.Effect<string>;\n /**\n * Checks if an entity exists at the expected storage path.\n *\n * @param entity - Entity type and alias to check.\n */\n checkEntityExists: <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) => Effect.Effect<boolean, ConfigManagerError>;\n /**\n * Loads a single entity by alias and returns its stored content and metadata.\n *\n * @param entity - Entity type and alias to load.\n */\n getEntity: <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) => Effect.Effect<Entity<TEntityType>, ConfigManagerError>;\n /**\n * Lists all entities stored for a given type.\n *\n * @param type - Entity type to enumerate.\n */\n getEntitiesForType: <TEntityType extends EntityType>(\n type: TEntityType,\n ) => Effect.Effect<Entity<TEntityType>[], ConfigManagerError>;\n /**\n * Persists a new entity to disk.\n *\n * @param entity - Entity content and metadata to store.\n */\n storeEntity: <TEntityType extends EntityType>(\n entity: Entity<TEntityType>,\n ) => Effect.Effect<Entity<TEntityType>, ConfigManagerError>;\n /**\n * Removes an entity from disk.\n *\n * @param entity - Entity type and alias to remove.\n */\n removeEntity: <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) => Effect.Effect<void, ConfigManagerError>;\n};\n\n/**\n * Domain error for configuration and entity storage operations.\n */\nexport class ConfigManagerError extends Data.TaggedError(\n \"@namera-ai/cli/ConfigManagerError\",\n)<{\n code:\n | \"InitializationError\"\n | \"BadArgument\"\n | \"EntityNotFound\"\n | \"EntityAlreadyExists\"\n | SystemErrorTag;\n message: string;\n}> {}\n\ntype GetEntityParams<TEntityType extends EntityType> = {\n type: TEntityType;\n alias: string;\n};\n\n/**\n * Service tag for resolving {@link ConfigManager} from the Effect context.\n */\nexport const ConfigManager = Context.Service<ConfigManager>(\n \"@namera-ai/cli/ConfigManager\",\n);\n\n/**\n * Live layer that persists CLI entities in the user's config directory.\n */\nexport const layer = Layer.effect(\n ConfigManager,\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n const getConfigDirPath = () =>\n Effect.gen(function* () {\n const homeDir = yield* Effect.sync(() => os.homedir());\n const baseDir = path.join(homeDir, \".namera\");\n\n return baseDir;\n });\n\n const ensureConfigDirExists = () =>\n Effect.gen(function* () {\n const baseDir = yield* getConfigDirPath();\n\n const subDirs = [\"smart-accounts\", \"session-keys\", \"keystores\"];\n\n const directoriesToCreate = subDirs.map((dir) =>\n path.join(baseDir, dir),\n );\n\n yield* Effect.forEach(\n directoriesToCreate,\n (dirPath) =>\n fs.makeDirectory(dirPath, { recursive: true }).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: \"InitializationError\",\n message: e.message,\n }),\n ),\n ),\n ),\n { concurrency: \"unbounded\" },\n );\n });\n\n const getEntityPath = <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) =>\n Effect.gen(function* () {\n const baseDir = yield* getConfigDirPath();\n const entityPath = path.join(baseDir, `${entity.type}s`, entity.alias);\n return entityPath;\n });\n\n const checkEntityExists = <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) =>\n Effect.gen(function* () {\n const entityPath = yield* getEntityPath(entity);\n const exists = yield* fs.exists(entityPath).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: e.reason._tag,\n message: e.message,\n }),\n ),\n ),\n );\n\n return exists;\n });\n\n const getEntity = <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) =>\n Effect.gen(function* () {\n const entityPath = yield* getEntityPath(entity);\n const exists = yield* checkEntityExists(entity);\n\n if (exists) {\n const content = yield* fs.readFileString(entityPath);\n\n return {\n alias: entity.alias,\n content,\n path: entityPath,\n type: entity.type,\n } satisfies Entity<TEntityType>;\n }\n\n return yield* Effect.fail(\n new ConfigManagerError({\n code: \"EntityNotFound\",\n message: `${entityName[entity.type]} with alias ${entity.alias} does not exist`,\n }),\n );\n }).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: e.reason._tag,\n message: e.message,\n }),\n ),\n ),\n );\n\n const getEntitiesForType = <TEntityType extends EntityType>(\n type: TEntityType,\n ) =>\n Effect.gen(function* () {\n const baseDir = yield* getConfigDirPath();\n\n const entitiesDir = path.join(baseDir, `${type}s`);\n const entities = yield* fs.readDirectory(entitiesDir);\n\n const effects = entities.map((entityName) =>\n Effect.gen(function* () {\n const entityPath = path.join(entitiesDir, entityName);\n const content = yield* fs.readFileString(entityPath);\n\n return {\n alias: entityName,\n content,\n path: entityPath,\n type,\n } satisfies Entity<TEntityType>;\n }),\n );\n\n return yield* Effect.all(effects, { concurrency: \"unbounded\" });\n }).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: e.reason._tag,\n message: e.message,\n }),\n ),\n ),\n );\n\n const storeEntity = <TEntityType extends EntityType>(\n entity: Entity<TEntityType>,\n ) =>\n Effect.gen(function* () {\n const entityPath = yield* getEntityPath(entity);\n\n const exists = yield* checkEntityExists({\n alias: entity.alias,\n type: entity.type,\n });\n\n if (exists) {\n return yield* Effect.fail(\n new ConfigManagerError({\n code: \"EntityAlreadyExists\",\n message: `Entity ${entity.alias} already exists`,\n }),\n );\n }\n\n yield* fs.writeFileString(entityPath, entity.content);\n\n return {\n alias: entity.alias,\n content: entity.content,\n path: entityPath,\n type: entity.type,\n } satisfies Entity<TEntityType>;\n }).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: e.reason._tag,\n message: e.message,\n }),\n ),\n ),\n );\n\n const removeEntity = <TEntityType extends EntityType>(\n entity: GetEntityParams<TEntityType>,\n ) =>\n Effect.gen(function* () {\n const entityPath = yield* getEntityPath(entity);\n\n yield* fs.remove(entityPath).pipe(\n Effect.catchTag(\"PlatformError\", (e) =>\n Effect.fail(\n new ConfigManagerError({\n code: e.reason._tag,\n message: e.message,\n }),\n ),\n ),\n );\n });\n\n return ConfigManager.of({\n checkEntityExists,\n ensureConfigDirExists,\n getConfigDirPath,\n getEntitiesForType,\n getEntity,\n getEntityPath,\n storeEntity,\n removeEntity,\n });\n }),\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./config.mjs";
|
|
3
|
+
import "./prompt.mjs";
|
|
4
|
+
import "./keystore.mjs";
|
|
5
|
+
import "./mcp-context.mjs";
|
|
6
|
+
import "./web3.mjs";
|
|
7
|
+
import "./smart-account.mjs";
|
|
8
|
+
import "./session-key.mjs";
|
|
9
|
+
import "./mcp.mjs";
|
|
10
|
+
import "./output.mjs";
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ConfigManager } from "./config.mjs";
|
|
3
|
+
import { PromptManager } from "./prompt.mjs";
|
|
4
|
+
import { Context, Data, Effect, Layer, Redacted } from "effect";
|
|
5
|
+
import { Wallet } from "@ethereumjs/wallet";
|
|
6
|
+
import { hexToBytes, toHex } from "viem";
|
|
7
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
8
|
+
//#region src/layers/keystore.ts
|
|
9
|
+
/**
|
|
10
|
+
* Service tag for resolving {@link KeystoreManager} from the Effect context.
|
|
11
|
+
*/
|
|
12
|
+
const KeystoreManager = Context.Service("@namera-ai/cli/KeystoreManager");
|
|
13
|
+
/**
|
|
14
|
+
* Domain error for keystore management operations.
|
|
15
|
+
*/
|
|
16
|
+
var KeystoreManagerError = class extends Data.TaggedError("@namera-ai/cli/KeystoreManagerError") {};
|
|
17
|
+
/**
|
|
18
|
+
* Live layer wiring the keystore manager with configuration and prompts.
|
|
19
|
+
*/
|
|
20
|
+
const layer = Layer.effect(KeystoreManager, Effect.gen(function* () {
|
|
21
|
+
const configManager = yield* ConfigManager;
|
|
22
|
+
const promptManager = yield* PromptManager;
|
|
23
|
+
const getKeystore = (params) => Effect.gen(function* () {
|
|
24
|
+
const res = yield* configManager.getEntity({
|
|
25
|
+
alias: params.alias,
|
|
26
|
+
type: "keystore"
|
|
27
|
+
});
|
|
28
|
+
const parsedKeystore = yield* Effect.try({
|
|
29
|
+
catch: () => new KeystoreManagerError({
|
|
30
|
+
code: "KeystoreParseError",
|
|
31
|
+
message: "Unable to parse keystore"
|
|
32
|
+
}),
|
|
33
|
+
try: () => JSON.parse(res.content)
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
alias: res.alias,
|
|
37
|
+
data: {
|
|
38
|
+
...parsedKeystore,
|
|
39
|
+
address: `0x${parsedKeystore.address}`
|
|
40
|
+
},
|
|
41
|
+
path: res.path
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const listKeystores = () => Effect.gen(function* () {
|
|
45
|
+
const effects = (yield* configManager.getEntitiesForType("keystore")).map((entity) => Effect.gen(function* () {
|
|
46
|
+
const parsedKeystore = yield* Effect.try({
|
|
47
|
+
catch: () => new KeystoreManagerError({
|
|
48
|
+
code: "KeystoreParseError",
|
|
49
|
+
message: "Unable to parse keystore"
|
|
50
|
+
}),
|
|
51
|
+
try: () => JSON.parse(entity.content)
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
alias: entity.alias,
|
|
55
|
+
data: {
|
|
56
|
+
...parsedKeystore,
|
|
57
|
+
address: `0x${parsedKeystore.address}`
|
|
58
|
+
},
|
|
59
|
+
path: entity.path
|
|
60
|
+
};
|
|
61
|
+
}));
|
|
62
|
+
return yield* Effect.all(effects, { concurrency: "unbounded" });
|
|
63
|
+
});
|
|
64
|
+
const createKeystore = (params) => Effect.gen(function* () {
|
|
65
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
66
|
+
alias: params.alias,
|
|
67
|
+
type: "keystore"
|
|
68
|
+
});
|
|
69
|
+
if (yield* configManager.checkEntityExists({
|
|
70
|
+
alias: params.alias,
|
|
71
|
+
type: "keystore"
|
|
72
|
+
})) return yield* Effect.fail(new KeystoreManagerError({
|
|
73
|
+
code: "KeystoreAlreadyExists",
|
|
74
|
+
message: `Keystore with alias ${params.alias} already exists`
|
|
75
|
+
}));
|
|
76
|
+
const keystoreString = yield* Effect.tryPromise({
|
|
77
|
+
catch: () => new KeystoreManagerError({
|
|
78
|
+
code: "KeystoreCreationFailed",
|
|
79
|
+
message: "Failed to create keystore"
|
|
80
|
+
}),
|
|
81
|
+
try: () => Wallet.generate().toV3String(params.password)
|
|
82
|
+
});
|
|
83
|
+
yield* configManager.storeEntity({
|
|
84
|
+
alias: params.alias,
|
|
85
|
+
content: keystoreString,
|
|
86
|
+
path: entityPath,
|
|
87
|
+
type: "keystore"
|
|
88
|
+
});
|
|
89
|
+
const keystore = yield* Effect.try({
|
|
90
|
+
catch: () => new KeystoreManagerError({
|
|
91
|
+
code: "KeystoreParseError",
|
|
92
|
+
message: "Unable to parse keystore"
|
|
93
|
+
}),
|
|
94
|
+
try: () => JSON.parse(keystoreString)
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
alias: params.alias,
|
|
98
|
+
data: {
|
|
99
|
+
...keystore,
|
|
100
|
+
address: `0x${keystore.address}`
|
|
101
|
+
},
|
|
102
|
+
path: entityPath
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
const decryptKeystore = (params) => Effect.gen(function* () {
|
|
106
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
107
|
+
const wallet = yield* Effect.tryPromise({
|
|
108
|
+
catch: () => new KeystoreManagerError({
|
|
109
|
+
code: "KeystoreDecryptionFailed",
|
|
110
|
+
message: "Failed to decrypt keystore"
|
|
111
|
+
}),
|
|
112
|
+
try: () => Wallet.fromV3(keystore.data, params.password)
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
address: wallet.getChecksumAddressString(),
|
|
116
|
+
alias: params.alias,
|
|
117
|
+
privateKey: Redacted.make(wallet.getPrivateKeyString()),
|
|
118
|
+
publicKey: wallet.getPublicKeyString()
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const selectKeystore = (params) => Effect.gen(function* () {
|
|
122
|
+
const keystores = yield* listKeystores();
|
|
123
|
+
return yield* promptManager.selectPrompt({
|
|
124
|
+
message: params.message,
|
|
125
|
+
choices: keystores.map((k) => ({
|
|
126
|
+
title: k.alias,
|
|
127
|
+
value: k,
|
|
128
|
+
description: k.data.address
|
|
129
|
+
}))
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
const importKeystore = (params) => Effect.gen(function* () {
|
|
133
|
+
const entityPath = yield* configManager.getEntityPath({
|
|
134
|
+
alias: params.alias,
|
|
135
|
+
type: "keystore"
|
|
136
|
+
});
|
|
137
|
+
if (yield* configManager.checkEntityExists({
|
|
138
|
+
alias: params.alias,
|
|
139
|
+
type: "keystore"
|
|
140
|
+
})) return yield* Effect.fail(new KeystoreManagerError({
|
|
141
|
+
code: "KeystoreAlreadyExists",
|
|
142
|
+
message: `Keystore with alias ${params.alias} already exists`
|
|
143
|
+
}));
|
|
144
|
+
const keystoreString = yield* Effect.tryPromise({
|
|
145
|
+
catch: () => new KeystoreManagerError({
|
|
146
|
+
code: "KeystoreCreationFailed",
|
|
147
|
+
message: "Failed to create keystore"
|
|
148
|
+
}),
|
|
149
|
+
try: () => Wallet.fromPrivateKey(hexToBytes(params.privateKey)).toV3String(params.password)
|
|
150
|
+
});
|
|
151
|
+
yield* configManager.storeEntity({
|
|
152
|
+
alias: params.alias,
|
|
153
|
+
content: keystoreString,
|
|
154
|
+
path: entityPath,
|
|
155
|
+
type: "keystore"
|
|
156
|
+
});
|
|
157
|
+
const keystore = yield* Effect.try({
|
|
158
|
+
catch: () => new KeystoreManagerError({
|
|
159
|
+
code: "KeystoreParseError",
|
|
160
|
+
message: "Unable to parse keystore"
|
|
161
|
+
}),
|
|
162
|
+
try: () => JSON.parse(keystoreString)
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
alias: params.alias,
|
|
166
|
+
data: keystore,
|
|
167
|
+
path: entityPath
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
const removeKeystore = (params) => Effect.gen(function* () {
|
|
171
|
+
if (!(yield* configManager.checkEntityExists({
|
|
172
|
+
alias: params.alias,
|
|
173
|
+
type: "keystore"
|
|
174
|
+
}))) return yield* Effect.fail(new KeystoreManagerError({
|
|
175
|
+
code: "KeystoreNotFound",
|
|
176
|
+
message: `Keystore with alias ${params.alias} does not exist`
|
|
177
|
+
}));
|
|
178
|
+
return yield* configManager.removeEntity({
|
|
179
|
+
alias: params.alias,
|
|
180
|
+
type: "keystore"
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
const getSigner = (params) => Effect.gen(function* () {
|
|
184
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
185
|
+
return privateKeyToAccount(toHex((yield* Effect.tryPromise({
|
|
186
|
+
catch: () => new KeystoreManagerError({
|
|
187
|
+
code: "KeystoreDecryptionFailed",
|
|
188
|
+
message: "Failed to decrypt keystore"
|
|
189
|
+
}),
|
|
190
|
+
try: () => Wallet.fromV3(keystore.data, params.password)
|
|
191
|
+
})).getPrivateKey()));
|
|
192
|
+
});
|
|
193
|
+
const getKeystorePassword = (params) => Effect.gen(function* () {
|
|
194
|
+
const keystore = yield* getKeystore({ alias: params.alias });
|
|
195
|
+
return yield* promptManager.passwordPrompt({
|
|
196
|
+
message: params.message,
|
|
197
|
+
validate: (v) => Effect.gen(function* () {
|
|
198
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
199
|
+
yield* Effect.tryPromise({
|
|
200
|
+
try: () => Wallet.fromV3(keystore.data, v),
|
|
201
|
+
catch: () => new KeystoreManagerError({
|
|
202
|
+
code: "DecryptError",
|
|
203
|
+
message: `Invalid password for keystore ${params.alias}`
|
|
204
|
+
})
|
|
205
|
+
}).pipe(Effect.catch(() => Effect.fail("Invalid password")));
|
|
206
|
+
return v;
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
return KeystoreManager.of({
|
|
211
|
+
createKeystore,
|
|
212
|
+
decryptKeystore,
|
|
213
|
+
getKeystore,
|
|
214
|
+
listKeystores,
|
|
215
|
+
selectKeystore,
|
|
216
|
+
importKeystore,
|
|
217
|
+
removeKeystore,
|
|
218
|
+
getSigner,
|
|
219
|
+
getKeystorePassword
|
|
220
|
+
});
|
|
221
|
+
}));
|
|
222
|
+
//#endregion
|
|
223
|
+
export { KeystoreManager, layer };
|
|
224
|
+
|
|
225
|
+
//# sourceMappingURL=keystore.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keystore.mjs","names":["EthereumJSWallet"],"sources":["../../src/layers/keystore.ts"],"sourcesContent":["import { Wallet as EthereumJSWallet, Wallet } from \"@ethereumjs/wallet\";\nimport { Context, Data, Effect, Layer, Redacted } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport type { Prompt } from \"effect/unstable/cli\";\nimport type { Environment } from \"effect/unstable/cli/Prompt\";\nimport { type Hex, hexToBytes, toHex } from \"viem\";\nimport { type LocalAccount, privateKeyToAccount } from \"viem/accounts\";\n\nimport type {\n CreateKeystoreParams,\n DecryptKeystoreParams,\n DecryptKeystoreResponse,\n GetKeystoreParams,\n GetSignerParams,\n ImportKeystoreParams,\n Keystore,\n KeystoreData,\n RemoveKeystoreParams,\n} from \"@/dto\";\n\nimport { ConfigManager, type ConfigManagerError } from \"./config\";\nimport { PromptManager } from \"./prompt\";\n\n/**\n * Effect service for keystore CRUD, selection, and decryption operations.\n */\nexport type KeystoreManager = {\n /**\n * Loads a keystore by alias and parses it into a typed keystore data object.\n *\n * @param params - Alias for the keystore to load.\n */\n getKeystore: (\n params: GetKeystoreParams,\n ) => Effect.Effect<KeystoreData, ConfigManagerError | KeystoreManagerError>;\n /**\n * Lists all stored keystores, parsing each into a typed keystore data object.\n */\n listKeystores: () => Effect.Effect<\n KeystoreData[],\n ConfigManagerError | KeystoreManagerError\n >;\n /**\n * Opens an interactive prompt to select a keystore from stored entries.\n *\n * @param params - Prompt message.\n */\n selectKeystore: (params: {\n message: string;\n }) => Effect.Effect<\n KeystoreData,\n ConfigManagerError | KeystoreManagerError | QuitError,\n Environment\n >;\n /**\n * Creates a new random wallet, encrypts it, and stores as a keystore.\n *\n * @param params - Alias and password used for the new keystore.\n */\n createKeystore: (\n params: CreateKeystoreParams,\n ) => Effect.Effect<KeystoreData, ConfigManagerError | KeystoreManagerError>;\n /**\n * Decrypts a keystore and returns key material.\n *\n * @param params - Alias and password for the keystore to decrypt.\n */\n decryptKeystore: (\n params: DecryptKeystoreParams,\n ) => Effect.Effect<\n DecryptKeystoreResponse,\n KeystoreManagerError | ConfigManagerError\n >;\n /**\n * Imports a keystore from a private key and stores as a keystore.\n *\n * @param params - Alias and private key for the keystore to import.\n */\n importKeystore: (\n params: ImportKeystoreParams,\n ) => Effect.Effect<\n KeystoreData,\n ConfigManagerError | KeystoreManagerError,\n never\n >;\n /**\n * Removes a keystore from storage.\n *\n * @param params - Alias of the keystore to remove.\n */\n removeKeystore: (params: {\n readonly alias: string;\n }) => Effect.Effect<void, ConfigManagerError | KeystoreManagerError, never>;\n getSigner: (\n params: GetSignerParams,\n ) => Effect.Effect<LocalAccount, ConfigManagerError | KeystoreManagerError>;\n /**\n * Prompts the user to enter the password for a keystore.\n *\n * @param params - Alias of the keystore and message to display.\n * @returns The password for the keystore.\n */\n getKeystorePassword: (params: {\n alias: string;\n message: string;\n }) => Effect.Effect<\n Redacted.Redacted<string>,\n ConfigManagerError | KeystoreManagerError | QuitError,\n Prompt.Environment\n >;\n};\n\n/**\n * Service tag for resolving {@link KeystoreManager} from the Effect context.\n */\nexport const KeystoreManager = Context.Service<KeystoreManager>(\n \"@namera-ai/cli/KeystoreManager\",\n);\n\n/**\n * Domain error for keystore management operations.\n */\nexport class KeystoreManagerError extends Data.TaggedError(\n \"@namera-ai/cli/KeystoreManagerError\",\n)<{\n code:\n | \"KeystoreParseError\"\n | \"KeystoreAlreadyExists\"\n | \"KeystoreCreationFailed\"\n | \"KeystoreDecryptionFailed\"\n | \"KeystoreNotFound\"\n | \"DecryptError\";\n message: string;\n}> {}\n\n/**\n * Live layer wiring the keystore manager with configuration and prompts.\n */\nexport const layer = Layer.effect(\n KeystoreManager,\n Effect.gen(function* () {\n const configManager = yield* ConfigManager;\n const promptManager = yield* PromptManager;\n\n const getKeystore = (params: GetKeystoreParams) =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntity({\n alias: params.alias,\n type: \"keystore\",\n });\n\n const parsedKeystore = yield* Effect.try({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreParseError\",\n message: \"Unable to parse keystore\",\n }),\n try: () => JSON.parse(res.content) as Keystore,\n });\n\n const wallet: KeystoreData = {\n alias: res.alias,\n data: {\n ...parsedKeystore,\n address: `0x${parsedKeystore.address}`,\n },\n path: res.path,\n };\n\n return wallet;\n });\n\n const listKeystores = () =>\n Effect.gen(function* () {\n const res = yield* configManager.getEntitiesForType(\"keystore\");\n\n const effects = res.map((entity) =>\n Effect.gen(function* () {\n const parsedKeystore = yield* Effect.try({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreParseError\",\n message: \"Unable to parse keystore\",\n }),\n try: () => JSON.parse(entity.content) as Keystore,\n });\n\n const wallet: KeystoreData = {\n alias: entity.alias,\n data: {\n ...parsedKeystore,\n address: `0x${parsedKeystore.address}`,\n },\n path: entity.path,\n };\n\n return wallet;\n }),\n );\n\n return yield* Effect.all(effects, { concurrency: \"unbounded\" });\n });\n\n const createKeystore = (params: CreateKeystoreParams) =>\n Effect.gen(function* () {\n const entityPath = yield* configManager.getEntityPath({\n alias: params.alias,\n type: \"keystore\",\n });\n\n // Check if alias is already taken\n const aliasTaken = yield* configManager.checkEntityExists({\n alias: params.alias,\n type: \"keystore\",\n });\n\n if (aliasTaken) {\n return yield* Effect.fail(\n new KeystoreManagerError({\n code: \"KeystoreAlreadyExists\",\n message: `Keystore with alias ${params.alias} already exists`,\n }),\n );\n }\n\n const keystoreString = yield* Effect.tryPromise({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreCreationFailed\",\n message: \"Failed to create keystore\",\n }),\n try: () => EthereumJSWallet.generate().toV3String(params.password),\n });\n\n // Store Entity\n yield* configManager.storeEntity({\n alias: params.alias,\n content: keystoreString,\n path: entityPath,\n type: \"keystore\",\n });\n\n const keystore = yield* Effect.try({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreParseError\",\n message: \"Unable to parse keystore\",\n }),\n try: () => JSON.parse(keystoreString) as Keystore,\n });\n\n const data: KeystoreData = {\n alias: params.alias,\n data: {\n ...keystore,\n address: `0x${keystore.address}`,\n },\n path: entityPath,\n };\n\n return data;\n });\n\n const decryptKeystore = (params: DecryptKeystoreParams) =>\n Effect.gen(function* () {\n const keystore = yield* getKeystore({\n alias: params.alias,\n });\n\n const wallet = yield* Effect.tryPromise({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreDecryptionFailed\",\n message: \"Failed to decrypt keystore\",\n }),\n try: () => EthereumJSWallet.fromV3(keystore.data, params.password),\n });\n\n return {\n address: wallet.getChecksumAddressString(),\n alias: params.alias,\n privateKey: Redacted.make(wallet.getPrivateKeyString()),\n publicKey: wallet.getPublicKeyString(),\n };\n });\n\n const selectKeystore = (params: { message: string }) =>\n Effect.gen(function* () {\n const keystores = yield* listKeystores();\n\n const res = yield* promptManager.selectPrompt({\n message: params.message,\n choices: keystores.map((k) => ({\n title: k.alias,\n value: k,\n description: k.data.address,\n })) satisfies Prompt.SelectChoice<KeystoreData>[],\n });\n\n return res;\n });\n\n const importKeystore = (params: ImportKeystoreParams) =>\n Effect.gen(function* () {\n const entityPath = yield* configManager.getEntityPath({\n alias: params.alias,\n type: \"keystore\",\n });\n\n // Check if alias is already taken\n const aliasTaken = yield* configManager.checkEntityExists({\n alias: params.alias,\n type: \"keystore\",\n });\n\n if (aliasTaken) {\n return yield* Effect.fail(\n new KeystoreManagerError({\n code: \"KeystoreAlreadyExists\",\n message: `Keystore with alias ${params.alias} already exists`,\n }),\n );\n }\n\n const keystoreString = yield* Effect.tryPromise({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreCreationFailed\",\n message: \"Failed to create keystore\",\n }),\n try: () =>\n EthereumJSWallet.fromPrivateKey(\n hexToBytes(params.privateKey as Hex),\n ).toV3String(params.password),\n });\n\n // Store Entity\n yield* configManager.storeEntity({\n alias: params.alias,\n content: keystoreString,\n path: entityPath,\n type: \"keystore\",\n });\n\n const keystore = yield* Effect.try({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreParseError\",\n message: \"Unable to parse keystore\",\n }),\n try: () => JSON.parse(keystoreString) as Keystore,\n });\n\n const data: KeystoreData = {\n alias: params.alias,\n data: keystore,\n path: entityPath,\n };\n\n return data;\n });\n\n const removeKeystore = (params: RemoveKeystoreParams) =>\n Effect.gen(function* () {\n // Check if alias exists\n const exists = yield* configManager.checkEntityExists({\n alias: params.alias,\n type: \"keystore\",\n });\n\n if (!exists) {\n return yield* Effect.fail(\n new KeystoreManagerError({\n code: \"KeystoreNotFound\",\n message: `Keystore with alias ${params.alias} does not exist`,\n }),\n );\n }\n\n return yield* configManager.removeEntity({\n alias: params.alias,\n type: \"keystore\",\n });\n });\n\n const getSigner = (params: GetSignerParams) =>\n Effect.gen(function* () {\n const keystore = yield* getKeystore({ alias: params.alias });\n\n const wallet = yield* Effect.tryPromise({\n catch: () =>\n new KeystoreManagerError({\n code: \"KeystoreDecryptionFailed\",\n message: \"Failed to decrypt keystore\",\n }),\n try: () => EthereumJSWallet.fromV3(keystore.data, params.password),\n });\n\n return privateKeyToAccount(\n toHex(wallet.getPrivateKey()),\n ) as LocalAccount;\n });\n\n const getKeystorePassword = (params: { alias: string; message: string }) =>\n Effect.gen(function* () {\n const keystore = yield* getKeystore({ alias: params.alias });\n\n const password = yield* promptManager.passwordPrompt({\n message: params.message,\n validate: (v) =>\n Effect.gen(function* () {\n if (v.trim() === \"\") {\n return yield* Effect.fail(\"Password cannot be empty\");\n }\n\n yield* Effect.tryPromise({\n try: () => Wallet.fromV3(keystore.data, v),\n catch: () =>\n new KeystoreManagerError({\n code: \"DecryptError\",\n message: `Invalid password for keystore ${params.alias}`,\n }),\n }).pipe(Effect.catch(() => Effect.fail(\"Invalid password\")));\n\n return v;\n }),\n });\n\n return password;\n });\n\n return KeystoreManager.of({\n createKeystore,\n decryptKeystore,\n getKeystore,\n listKeystores,\n selectKeystore,\n importKeystore,\n removeKeystore,\n getSigner,\n getKeystorePassword,\n });\n }),\n);\n"],"mappings":";;;;;;;;;;;AAmHA,MAAa,kBAAkB,QAAQ,QACrC,iCACD;;;;AAKD,IAAa,uBAAb,cAA0C,KAAK,YAC7C,sCACD,CASE;;;;AAKH,MAAa,QAAQ,MAAM,OACzB,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,OAAO;CAC7B,MAAM,gBAAgB,OAAO;CAE7B,MAAM,eAAe,WACnB,OAAO,IAAI,aAAa;EACtB,MAAM,MAAM,OAAO,cAAc,UAAU;GACzC,OAAO,OAAO;GACd,MAAM;GACP,CAAC;EAEF,MAAM,iBAAiB,OAAO,OAAO,IAAI;GACvC,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAW,KAAK,MAAM,IAAI,QAAQ;GACnC,CAAC;AAWF,SAAO;GARL,OAAO,IAAI;GACX,MAAM;IACJ,GAAG;IACH,SAAS,KAAK,eAAe;IAC9B;GACD,MAAM,IAAI;GAGL;GACP;CAEJ,MAAM,sBACJ,OAAO,IAAI,aAAa;EAGtB,MAAM,WAAU,OAFG,cAAc,mBAAmB,WAAW,EAE3C,KAAK,WACvB,OAAO,IAAI,aAAa;GACtB,MAAM,iBAAiB,OAAO,OAAO,IAAI;IACvC,aACE,IAAI,qBAAqB;KACvB,MAAM;KACN,SAAS;KACV,CAAC;IACJ,WAAW,KAAK,MAAM,OAAO,QAAQ;IACtC,CAAC;AAWF,UAAO;IARL,OAAO,OAAO;IACd,MAAM;KACJ,GAAG;KACH,SAAS,KAAK,eAAe;KAC9B;IACD,MAAM,OAAO;IAGR;IACP,CACH;AAED,SAAO,OAAO,OAAO,IAAI,SAAS,EAAE,aAAa,aAAa,CAAC;GAC/D;CAEJ,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,cAAc,cAAc;GACpD,OAAO,OAAO;GACd,MAAM;GACP,CAAC;AAQF,MAAI,OALsB,cAAc,kBAAkB;GACxD,OAAO,OAAO;GACd,MAAM;GACP,CAAC,CAGA,QAAO,OAAO,OAAO,KACnB,IAAI,qBAAqB;GACvB,MAAM;GACN,SAAS,uBAAuB,OAAO,MAAM;GAC9C,CAAC,CACH;EAGH,MAAM,iBAAiB,OAAO,OAAO,WAAW;GAC9C,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAWA,OAAiB,UAAU,CAAC,WAAW,OAAO,SAAS;GACnE,CAAC;AAGF,SAAO,cAAc,YAAY;GAC/B,OAAO,OAAO;GACd,SAAS;GACT,MAAM;GACN,MAAM;GACP,CAAC;EAEF,MAAM,WAAW,OAAO,OAAO,IAAI;GACjC,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAW,KAAK,MAAM,eAAe;GACtC,CAAC;AAWF,SAAO;GARL,OAAO,OAAO;GACd,MAAM;IACJ,GAAG;IACH,SAAS,KAAK,SAAS;IACxB;GACD,MAAM;GAGD;GACP;CAEJ,MAAM,mBAAmB,WACvB,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,OAAO,YAAY,EAClC,OAAO,OAAO,OACf,CAAC;EAEF,MAAM,SAAS,OAAO,OAAO,WAAW;GACtC,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAWA,OAAiB,OAAO,SAAS,MAAM,OAAO,SAAS;GACnE,CAAC;AAEF,SAAO;GACL,SAAS,OAAO,0BAA0B;GAC1C,OAAO,OAAO;GACd,YAAY,SAAS,KAAK,OAAO,qBAAqB,CAAC;GACvD,WAAW,OAAO,oBAAoB;GACvC;GACD;CAEJ,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAO,eAAe;AAWxC,SAAO,OATY,cAAc,aAAa;GAC5C,SAAS,OAAO;GAChB,SAAS,UAAU,KAAK,OAAO;IAC7B,OAAO,EAAE;IACT,OAAO;IACP,aAAa,EAAE,KAAK;IACrB,EAAE;GACJ,CAAC;GAGF;CAEJ,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;EACtB,MAAM,aAAa,OAAO,cAAc,cAAc;GACpD,OAAO,OAAO;GACd,MAAM;GACP,CAAC;AAQF,MAAI,OALsB,cAAc,kBAAkB;GACxD,OAAO,OAAO;GACd,MAAM;GACP,CAAC,CAGA,QAAO,OAAO,OAAO,KACnB,IAAI,qBAAqB;GACvB,MAAM;GACN,SAAS,uBAAuB,OAAO,MAAM;GAC9C,CAAC,CACH;EAGH,MAAM,iBAAiB,OAAO,OAAO,WAAW;GAC9C,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WACEA,OAAiB,eACf,WAAW,OAAO,WAAkB,CACrC,CAAC,WAAW,OAAO,SAAS;GAChC,CAAC;AAGF,SAAO,cAAc,YAAY;GAC/B,OAAO,OAAO;GACd,SAAS;GACT,MAAM;GACN,MAAM;GACP,CAAC;EAEF,MAAM,WAAW,OAAO,OAAO,IAAI;GACjC,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAW,KAAK,MAAM,eAAe;GACtC,CAAC;AAQF,SAAO;GALL,OAAO,OAAO;GACd,MAAM;GACN,MAAM;GAGD;GACP;CAEJ,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;AAOtB,MAAI,EAAC,OALiB,cAAc,kBAAkB;GACpD,OAAO,OAAO;GACd,MAAM;GACP,CAAC,EAGA,QAAO,OAAO,OAAO,KACnB,IAAI,qBAAqB;GACvB,MAAM;GACN,SAAS,uBAAuB,OAAO,MAAM;GAC9C,CAAC,CACH;AAGH,SAAO,OAAO,cAAc,aAAa;GACvC,OAAO,OAAO;GACd,MAAM;GACP,CAAC;GACF;CAEJ,MAAM,aAAa,WACjB,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,OAAO,YAAY,EAAE,OAAO,OAAO,OAAO,CAAC;AAW5D,SAAO,oBACL,OAAM,OAVc,OAAO,WAAW;GACtC,aACE,IAAI,qBAAqB;IACvB,MAAM;IACN,SAAS;IACV,CAAC;GACJ,WAAWA,OAAiB,OAAO,SAAS,MAAM,OAAO,SAAS;GACnE,CAAC,EAGa,eAAe,CAAC,CAC9B;GACD;CAEJ,MAAM,uBAAuB,WAC3B,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,OAAO,YAAY,EAAE,OAAO,OAAO,OAAO,CAAC;AAuB5D,SAAO,OArBiB,cAAc,eAAe;GACnD,SAAS,OAAO;GAChB,WAAW,MACT,OAAO,IAAI,aAAa;AACtB,QAAI,EAAE,MAAM,KAAK,GACf,QAAO,OAAO,OAAO,KAAK,2BAA2B;AAGvD,WAAO,OAAO,WAAW;KACvB,WAAW,OAAO,OAAO,SAAS,MAAM,EAAE;KAC1C,aACE,IAAI,qBAAqB;MACvB,MAAM;MACN,SAAS,iCAAiC,OAAO;MAClD,CAAC;KACL,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,mBAAmB,CAAC,CAAC;AAE5D,WAAO;KACP;GACL,CAAC;GAGF;AAEJ,QAAO,gBAAgB,GAAG;EACxB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;EACF,CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-context.mjs","names":[],"sources":["../../src/layers/mcp-context.ts"],"sourcesContent":["import { Context } from \"effect\";\nimport type { LocalAccount } from \"viem\";\n\nimport type { LocalSmartAccount, SessionKeyData } from \"@/dto\";\n\nexport type McpContext = {\n smartAccount: LocalSmartAccount;\n sessionKeys: (SessionKeyData & {\n signer: LocalAccount;\n })[];\n};\n\nexport const McpContext = Context.Service<McpContext>(\n \"@namera-ai/cli/McpContext\",\n);\n"],"mappings":";;;AAYA,MAAa,aAAa,QAAQ,QAChC,4BACD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpContext } from "./mcp-context.mjs";
|
|
3
|
+
import { startMcpHttpServer, startMcpStdioServer } from "../mcp/index.mjs";
|
|
4
|
+
import { SmartAccountManager } from "./smart-account.mjs";
|
|
5
|
+
import { SessionKeyManager } from "./session-key.mjs";
|
|
6
|
+
import { Context, Effect, Layer } from "effect";
|
|
7
|
+
//#region src/layers/mcp.ts
|
|
8
|
+
const McpManager = Context.Service("@namera-ai/cli/McpManager");
|
|
9
|
+
const layer = Layer.effect(McpManager, Effect.gen(function* () {
|
|
10
|
+
const smartAccountManager = yield* SmartAccountManager;
|
|
11
|
+
const sessionKeyManager = yield* SessionKeyManager;
|
|
12
|
+
const startMcpServer = (params) => Effect.gen(function* () {
|
|
13
|
+
const sa = yield* smartAccountManager.getSmartAccount({ alias: params.smartAccountAlias });
|
|
14
|
+
const sessionKeys = Object.entries(params.sessionKeys);
|
|
15
|
+
const sessionKeysWithSigners = yield* Effect.all(sessionKeys.map(([alias, password]) => sessionKeyManager.getSessionKeySigner({
|
|
16
|
+
alias,
|
|
17
|
+
password
|
|
18
|
+
})));
|
|
19
|
+
const context = McpContext.of({
|
|
20
|
+
smartAccount: sa.data,
|
|
21
|
+
sessionKeys: sessionKeysWithSigners
|
|
22
|
+
});
|
|
23
|
+
if (params.transport === "stdio") yield* startMcpStdioServer().pipe(Effect.provideService(McpContext, context));
|
|
24
|
+
else yield* startMcpHttpServer(params.port ?? 8080).pipe(Effect.provideService(McpContext, context));
|
|
25
|
+
});
|
|
26
|
+
return McpManager.of({ startMcpServer });
|
|
27
|
+
}));
|
|
28
|
+
//#endregion
|
|
29
|
+
export { McpManager, layer };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=mcp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.mjs","names":[],"sources":["../../src/layers/mcp.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport type { ServeError } from \"effect/unstable/http/HttpServerError\";\n\nimport type { StartMcpServerParams } from \"@/dto/mcp\";\nimport { startMcpHttpServer, startMcpStdioServer } from \"@/mcp\";\n\nimport type { ConfigManagerError } from \"./config\";\nimport { McpContext } from \"./mcp-context\";\nimport { SessionKeyManager, type SessionKeyManagerError } from \"./session-key\";\nimport { SmartAccountManager } from \"./smart-account\";\n\nexport type McpManager = {\n startMcpServer: (\n params: StartMcpServerParams,\n ) => Effect.Effect<\n void,\n SessionKeyManagerError | ConfigManagerError | QuitError | ServeError\n >;\n};\n\nexport const McpManager = Context.Service<McpManager>(\n \"@namera-ai/cli/McpManager\",\n);\n\nexport const layer = Layer.effect(\n McpManager,\n Effect.gen(function* () {\n const smartAccountManager = yield* SmartAccountManager;\n const sessionKeyManager = yield* SessionKeyManager;\n\n const startMcpServer = (params: StartMcpServerParams) =>\n Effect.gen(function* () {\n const sa = yield* smartAccountManager.getSmartAccount({\n alias: params.smartAccountAlias,\n });\n\n const sessionKeys = Object.entries(params.sessionKeys);\n\n const sessionKeysWithSigners = yield* Effect.all(\n sessionKeys.map(([alias, password]) =>\n sessionKeyManager.getSessionKeySigner({\n alias,\n password,\n }),\n ),\n );\n\n const context = McpContext.of({\n smartAccount: sa.data,\n sessionKeys: sessionKeysWithSigners,\n });\n\n if (params.transport === \"stdio\") {\n yield* startMcpStdioServer().pipe(\n Effect.provideService(McpContext, context),\n );\n } else {\n yield* startMcpHttpServer(params.port ?? 8080).pipe(\n Effect.provideService(McpContext, context),\n );\n }\n });\n return McpManager.of({ startMcpServer });\n }),\n);\n"],"mappings":";;;;;;;AAqBA,MAAa,aAAa,QAAQ,QAChC,4BACD;AAED,MAAa,QAAQ,MAAM,OACzB,YACA,OAAO,IAAI,aAAa;CACtB,MAAM,sBAAsB,OAAO;CACnC,MAAM,oBAAoB,OAAO;CAEjC,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;EACtB,MAAM,KAAK,OAAO,oBAAoB,gBAAgB,EACpD,OAAO,OAAO,mBACf,CAAC;EAEF,MAAM,cAAc,OAAO,QAAQ,OAAO,YAAY;EAEtD,MAAM,yBAAyB,OAAO,OAAO,IAC3C,YAAY,KAAK,CAAC,OAAO,cACvB,kBAAkB,oBAAoB;GACpC;GACA;GACD,CAAC,CACH,CACF;EAED,MAAM,UAAU,WAAW,GAAG;GAC5B,cAAc,GAAG;GACjB,aAAa;GACd,CAAC;AAEF,MAAI,OAAO,cAAc,QACvB,QAAO,qBAAqB,CAAC,KAC3B,OAAO,eAAe,YAAY,QAAQ,CAC3C;MAED,QAAO,mBAAmB,OAAO,QAAQ,KAAK,CAAC,KAC7C,OAAO,eAAe,YAAY,QAAQ,CAC3C;GAEH;AACJ,QAAO,WAAW,GAAG,EAAE,gBAAgB,CAAC;EACxC,CACH"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { prettyFormat } from "../helpers/pretty.mjs";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
4
|
+
//#region src/layers/output.ts
|
|
5
|
+
/**
|
|
6
|
+
* Service tag for resolving {@link OutputFormatter} from the Effect context.
|
|
7
|
+
*/
|
|
8
|
+
const OutputFormatter = Context.Service("@namera-ai/cli/OutputFormatter");
|
|
9
|
+
/**
|
|
10
|
+
* Live layer that formats output as pretty, JSON, or NDJSON.
|
|
11
|
+
*/
|
|
12
|
+
const layer = Layer.succeed(OutputFormatter, OutputFormatter.of({ format: (data, format) => Effect.gen(function* () {
|
|
13
|
+
if (format === "pretty") return prettyFormat(data);
|
|
14
|
+
if (format === "json") return JSON.stringify(data, null, 2);
|
|
15
|
+
if (Array.isArray(data)) return `${data.map((item) => JSON.stringify(item)).join("\n")}\n`;
|
|
16
|
+
return `${JSON.stringify(data)}\n`;
|
|
17
|
+
}) }));
|
|
18
|
+
//#endregion
|
|
19
|
+
export { OutputFormatter, layer };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=output.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.mjs","names":[],"sources":["../../src/layers/output.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\n\nimport { prettyFormat } from \"@/helpers/pretty\";\n\n/**\n * Effect service for formatting command output into CLI-friendly formats.\n */\nexport type OutputFormatter = {\n /**\n * Formats command output into a CLI-friendly string.\n *\n * @param data - The object or list of objects to format.\n * @param format - Output mode: pretty (human), json, or ndjson.\n */\n format: (\n data: object | object[],\n format: \"pretty\" | \"json\" | \"ndjson\",\n ) => Effect.Effect<string>;\n};\n\n/**\n * Service tag for resolving {@link OutputFormatter} from the Effect context.\n */\nexport const OutputFormatter = Context.Service<OutputFormatter>(\n \"@namera-ai/cli/OutputFormatter\",\n);\n\n/**\n * Live layer that formats output as pretty, JSON, or NDJSON.\n */\nexport const layer = Layer.succeed(\n OutputFormatter,\n OutputFormatter.of({\n format: (data, format) =>\n Effect.gen(function* () {\n if (format === \"pretty\") {\n return prettyFormat(data);\n }\n if (format === \"json\") {\n return JSON.stringify(data, null, 2);\n }\n\n if (Array.isArray(data)) {\n return `${data.map((item) => JSON.stringify(item)).join(\"\\n\")}\\n`;\n }\n\n return `${JSON.stringify(data)}\\n`;\n }),\n }),\n);\n"],"mappings":";;;;;;;AAuBA,MAAa,kBAAkB,QAAQ,QACrC,iCACD;;;;AAKD,MAAa,QAAQ,MAAM,QACzB,iBACA,gBAAgB,GAAG,EACjB,SAAS,MAAM,WACb,OAAO,IAAI,aAAa;AACtB,KAAI,WAAW,SACb,QAAO,aAAa,KAAK;AAE3B,KAAI,WAAW,OACb,QAAO,KAAK,UAAU,MAAM,MAAM,EAAE;AAGtC,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,GAAG,KAAK,KAAK,SAAS,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC;AAGhE,QAAO,GAAG,KAAK,UAAU,KAAK,CAAC;EAC/B,EACL,CAAC,CACH"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { entityName } from "../types/index.mjs";
|
|
3
|
+
import { ConfigManager } from "./config.mjs";
|
|
4
|
+
import { Context, Data, Effect, Layer } from "effect";
|
|
5
|
+
import { Prompt } from "effect/unstable/cli";
|
|
6
|
+
import { hexToBytes, isHex } from "viem";
|
|
7
|
+
//#region src/layers/prompt.ts
|
|
8
|
+
/**
|
|
9
|
+
* Service tag for resolving {@link PromptManager} from the Effect context.
|
|
10
|
+
*/
|
|
11
|
+
const PromptManager = Context.Service("@namera-ai/cli/PromptManager");
|
|
12
|
+
Data.TaggedError("@namera-ai/cli/PromptManagerError");
|
|
13
|
+
/**
|
|
14
|
+
* Live layer that validates and returns interactive prompt input.
|
|
15
|
+
*/
|
|
16
|
+
const layer = Layer.effect(PromptManager, Effect.gen(function* () {
|
|
17
|
+
const configManager = yield* ConfigManager;
|
|
18
|
+
const aliasPrompt = (params) => Effect.gen(function* () {
|
|
19
|
+
return yield* Prompt.text({
|
|
20
|
+
message: params.message,
|
|
21
|
+
validate: (v) => Effect.gen(function* () {
|
|
22
|
+
if (v.trim() === "") return yield* Effect.fail("Alias cannot be empty");
|
|
23
|
+
const exists = yield* configManager.checkEntityExists({
|
|
24
|
+
alias: v,
|
|
25
|
+
type: params.type
|
|
26
|
+
}).pipe(Effect.catchTag("@namera-ai/cli/ConfigManagerError", (e) => Effect.fail(e.message)));
|
|
27
|
+
if (params.aliasType === "new" && exists) return yield* Effect.fail(`${entityName[params.type]} with alias ${v} already exists`);
|
|
28
|
+
if (params.aliasType === "existing" && !exists) return yield* Effect.fail(`${entityName[params.type]} with alias ${v} does not exist`);
|
|
29
|
+
return v;
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const passwordPrompt = (params) => Effect.gen(function* () {
|
|
34
|
+
return yield* Prompt.password({
|
|
35
|
+
message: params.message,
|
|
36
|
+
validate: (v) => Effect.gen(function* () {
|
|
37
|
+
if (v.trim() === "") return yield* Effect.fail("Password cannot be empty");
|
|
38
|
+
if (params.validate) return yield* params.validate(v);
|
|
39
|
+
return v;
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
const selectPrompt = (params) => Effect.gen(function* () {
|
|
44
|
+
return yield* Prompt.select(params);
|
|
45
|
+
});
|
|
46
|
+
const multiSelectPrompt = (params) => Effect.gen(function* () {
|
|
47
|
+
return yield* Prompt.multiSelect(params);
|
|
48
|
+
});
|
|
49
|
+
function hexPrompt(params) {
|
|
50
|
+
return Effect.gen(function* () {
|
|
51
|
+
const validate = (v) => Effect.gen(function* () {
|
|
52
|
+
if (v.trim() === "") return yield* Effect.fail("Hex cannot be empty");
|
|
53
|
+
if (!isHex(v)) return yield* Effect.fail("Invalid hex value");
|
|
54
|
+
if (hexToBytes(v).length !== params.length) return yield* Effect.fail(`Hex value must be ${params.length} bytes`);
|
|
55
|
+
return v;
|
|
56
|
+
});
|
|
57
|
+
if (params.redacted) return yield* Prompt.password({
|
|
58
|
+
message: params.message,
|
|
59
|
+
validate
|
|
60
|
+
});
|
|
61
|
+
return yield* Prompt.text({
|
|
62
|
+
message: params.message,
|
|
63
|
+
validate
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return PromptManager.of({
|
|
68
|
+
aliasPrompt,
|
|
69
|
+
passwordPrompt,
|
|
70
|
+
selectPrompt,
|
|
71
|
+
hexPrompt,
|
|
72
|
+
multiSelectPrompt
|
|
73
|
+
});
|
|
74
|
+
}));
|
|
75
|
+
//#endregion
|
|
76
|
+
export { PromptManager, layer };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=prompt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.mjs","names":[],"sources":["../../src/layers/prompt.ts"],"sourcesContent":["import { Context, Data, Effect, Layer, type Redacted } from \"effect\";\nimport type { QuitError } from \"effect/Terminal\";\nimport { Prompt } from \"effect/unstable/cli\";\nimport { type Hex, hexToBytes, isHex } from \"viem\";\n\nimport { type EntityType, entityName } from \"@/types\";\n\nimport { ConfigManager } from \"./config\";\n\n/**\n * Effect service for interactive CLI prompts.\n */\nexport type PromptManager = {\n /**\n * Prompts for an alias and validates it against existing storage.\n *\n * @param params - Prompt text, entity type, and expected alias semantics.\n */\n aliasPrompt: (\n params: AliasPromptParams,\n ) => Effect.Effect<string, QuitError, Prompt.Environment>;\n /**\n * Prompts for a password, optionally validating the input.\n *\n * @param params - Prompt text and optional validator.\n * @returns The redacted password.\n */\n passwordPrompt: (\n params: PasswordPromptParams,\n ) => Effect.Effect<Redacted.Redacted<string>, QuitError, Prompt.Environment>;\n /**\n * Prompts the user to select a value from a list of choices.\n *\n * @param params - Selection prompt options and choices.\n * @returns The selected value.\n */\n selectPrompt: <const A>(\n params: Prompt.SelectOptions<A>,\n ) => Effect.Effect<A, QuitError, Prompt.Environment>;\n /**\n * Prompts the user to enter a hex value.\n *\n * @param params - Prompt text and expected length of the hex value.\n * @returns The hex value entered by the user.\n */\n hexPrompt: <TRedacted extends boolean>(\n params: HexPromptParams<TRedacted>,\n ) => Effect.Effect<\n TRedacted extends true ? Redacted.Redacted<Hex> : Hex,\n QuitError,\n Prompt.Environment\n >;\n /**\n * Prompt the user to select multiple options from a list.\n *\n * @param params - Prompt options and multi-select options.\n * @returns A list of selected options.\n */\n multiSelectPrompt: <const A>(\n params: Prompt.SelectOptions<A> & Prompt.MultiSelectOptions,\n ) => Effect.Effect<A[], QuitError, Prompt.Environment>;\n};\n\n/**\n * Service tag for resolving {@link PromptManager} from the Effect context.\n */\nexport const PromptManager = Context.Service<PromptManager>(\n \"@namera-ai/cli/PromptManager\",\n);\n\n/**\n * Domain error for prompt-related failures.\n */\nexport class PromptManagerError extends Data.TaggedError(\n \"@namera-ai/cli/PromptManagerError\",\n)<{\n code: \"\";\n message: string;\n}> {}\n\n/**\n * Parameters for prompting a user for an entity alias.\n */\nexport type AliasPromptParams = {\n /**\n * Prompt message shown to the user.\n */\n message: string;\n /**\n * Entity type used to validate alias existence.\n */\n type: EntityType;\n /**\n * Whether the alias must be new or already exist.\n */\n aliasType: \"new\" | \"existing\";\n};\n\n/**\n * Parameters for prompting a user for a password.\n */\nexport type PasswordPromptParams = {\n /**\n * Prompt message shown to the user.\n */\n message: string;\n /**\n * Optional validator returning the original value or a failure message.\n */\n validate?: (v: string) => Effect.Effect<string, string, never>;\n};\n\n/**\n * Parameters for prompting a user for a hex value.\n */\nexport type HexPromptParams<TRedacted extends boolean> = {\n /**\n * Expected length of the hex value.\n */\n length: number;\n /**\n * Prompt message shown to the user.\n */\n message: string;\n /**\n * Whether the hex value should be redacted.\n */\n redacted: TRedacted;\n};\n\n/**\n * Live layer that validates and returns interactive prompt input.\n */\nexport const layer = Layer.effect(\n PromptManager,\n Effect.gen(function* () {\n const configManager = yield* ConfigManager;\n\n const aliasPrompt = (params: AliasPromptParams) =>\n Effect.gen(function* () {\n const alias = yield* Prompt.text({\n message: params.message,\n validate: (v) =>\n Effect.gen(function* () {\n if (v.trim() === \"\") {\n return yield* Effect.fail(\"Alias cannot be empty\");\n }\n\n const exists = yield* configManager\n .checkEntityExists({\n alias: v,\n type: params.type,\n })\n .pipe(\n Effect.catchTag(\"@namera-ai/cli/ConfigManagerError\", (e) =>\n Effect.fail(e.message),\n ),\n );\n\n if (params.aliasType === \"new\" && exists) {\n return yield* Effect.fail(\n `${entityName[params.type]} with alias ${v} already exists`,\n );\n }\n\n if (params.aliasType === \"existing\" && !exists) {\n return yield* Effect.fail(\n `${entityName[params.type]} with alias ${v} does not exist`,\n );\n }\n\n return v;\n }),\n });\n\n return alias;\n });\n\n const passwordPrompt = (params: PasswordPromptParams) =>\n Effect.gen(function* () {\n const password = yield* Prompt.password({\n message: params.message,\n validate: (v) =>\n Effect.gen(function* () {\n if (v.trim() === \"\") {\n return yield* Effect.fail(\"Password cannot be empty\");\n }\n\n if (params.validate) {\n return yield* params.validate(v);\n }\n\n return v;\n }),\n });\n\n return password;\n });\n\n const selectPrompt = <const A>(params: Prompt.SelectOptions<A>) =>\n Effect.gen(function* () {\n return yield* Prompt.select(params);\n });\n\n const multiSelectPrompt = <const A>(\n params: Prompt.SelectOptions<A> & Prompt.MultiSelectOptions,\n ) =>\n Effect.gen(function* () {\n return yield* Prompt.multiSelect(params);\n });\n\n function hexPrompt(\n params: HexPromptParams<true>,\n ): Effect.Effect<Redacted.Redacted<Hex>, QuitError, Prompt.Environment>;\n\n function hexPrompt(\n params: HexPromptParams<false>,\n ): Effect.Effect<Hex, QuitError, Prompt.Environment>;\n\n function hexPrompt(params: HexPromptParams<boolean>) {\n return Effect.gen(function* () {\n const validate = (v: string) =>\n Effect.gen(function* () {\n if (v.trim() === \"\") {\n return yield* Effect.fail(\"Hex cannot be empty\");\n }\n\n if (!isHex(v)) {\n return yield* Effect.fail(\"Invalid hex value\");\n }\n\n const len = hexToBytes(v).length;\n\n if (len !== params.length) {\n return yield* Effect.fail(\n `Hex value must be ${params.length} bytes`,\n );\n }\n\n return v;\n });\n\n if (params.redacted) {\n return yield* Prompt.password({\n message: params.message,\n validate,\n });\n }\n\n return yield* Prompt.text({\n message: params.message,\n validate,\n });\n });\n }\n\n return PromptManager.of({\n aliasPrompt,\n passwordPrompt,\n selectPrompt,\n hexPrompt,\n multiSelectPrompt,\n });\n }),\n);\n"],"mappings":";;;;;;;;;;AAkEA,MAAa,gBAAgB,QAAQ,QACnC,+BACD;AAKuC,KAAK,YAC3C,oCACD;;;;AA0DD,MAAa,QAAQ,MAAM,OACzB,eACA,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,OAAO;CAE7B,MAAM,eAAe,WACnB,OAAO,IAAI,aAAa;AAoCtB,SAAO,OAnCc,OAAO,KAAK;GAC/B,SAAS,OAAO;GAChB,WAAW,MACT,OAAO,IAAI,aAAa;AACtB,QAAI,EAAE,MAAM,KAAK,GACf,QAAO,OAAO,OAAO,KAAK,wBAAwB;IAGpD,MAAM,SAAS,OAAO,cACnB,kBAAkB;KACjB,OAAO;KACP,MAAM,OAAO;KACd,CAAC,CACD,KACC,OAAO,SAAS,sCAAsC,MACpD,OAAO,KAAK,EAAE,QAAQ,CACvB,CACF;AAEH,QAAI,OAAO,cAAc,SAAS,OAChC,QAAO,OAAO,OAAO,KACnB,GAAG,WAAW,OAAO,MAAM,cAAc,EAAE,iBAC5C;AAGH,QAAI,OAAO,cAAc,cAAc,CAAC,OACtC,QAAO,OAAO,OAAO,KACnB,GAAG,WAAW,OAAO,MAAM,cAAc,EAAE,iBAC5C;AAGH,WAAO;KACP;GACL,CAAC;GAGF;CAEJ,MAAM,kBAAkB,WACtB,OAAO,IAAI,aAAa;AAiBtB,SAAO,OAhBiB,OAAO,SAAS;GACtC,SAAS,OAAO;GAChB,WAAW,MACT,OAAO,IAAI,aAAa;AACtB,QAAI,EAAE,MAAM,KAAK,GACf,QAAO,OAAO,OAAO,KAAK,2BAA2B;AAGvD,QAAI,OAAO,SACT,QAAO,OAAO,OAAO,SAAS,EAAE;AAGlC,WAAO;KACP;GACL,CAAC;GAGF;CAEJ,MAAM,gBAAyB,WAC7B,OAAO,IAAI,aAAa;AACtB,SAAO,OAAO,OAAO,OAAO,OAAO;GACnC;CAEJ,MAAM,qBACJ,WAEA,OAAO,IAAI,aAAa;AACtB,SAAO,OAAO,OAAO,YAAY,OAAO;GACxC;CAUJ,SAAS,UAAU,QAAkC;AACnD,SAAO,OAAO,IAAI,aAAa;GAC7B,MAAM,YAAY,MAChB,OAAO,IAAI,aAAa;AACtB,QAAI,EAAE,MAAM,KAAK,GACf,QAAO,OAAO,OAAO,KAAK,sBAAsB;AAGlD,QAAI,CAAC,MAAM,EAAE,CACX,QAAO,OAAO,OAAO,KAAK,oBAAoB;AAKhD,QAFY,WAAW,EAAE,CAAC,WAEd,OAAO,OACjB,QAAO,OAAO,OAAO,KACnB,qBAAqB,OAAO,OAAO,QACpC;AAGH,WAAO;KACP;AAEJ,OAAI,OAAO,SACT,QAAO,OAAO,OAAO,SAAS;IAC5B,SAAS,OAAO;IAChB;IACD,CAAC;AAGJ,UAAO,OAAO,OAAO,KAAK;IACxB,SAAS,OAAO;IAChB;IACD,CAAC;IACF;;AAGJ,QAAO,cAAc,GAAG;EACtB;EACA;EACA;EACA;EACA;EACD,CAAC;EACF,CACH"}
|