@enbox/gitd 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/cli/agent.js +66 -11
- package/dist/esm/cli/agent.js.map +1 -1
- package/dist/esm/cli/commands/auth.js +265 -0
- package/dist/esm/cli/commands/auth.js.map +1 -0
- package/dist/esm/cli/commands/migrate.js +151 -6
- package/dist/esm/cli/commands/migrate.js.map +1 -1
- package/dist/esm/cli/main.js +72 -2
- package/dist/esm/cli/main.js.map +1 -1
- package/dist/esm/git-remote/credential-main.js +34 -8
- package/dist/esm/git-remote/credential-main.js.map +1 -1
- package/dist/esm/profiles/config.js +156 -0
- package/dist/esm/profiles/config.js.map +1 -0
- package/dist/types/cli/agent.d.ts +27 -4
- package/dist/types/cli/agent.d.ts.map +1 -1
- package/dist/types/cli/commands/auth.d.ts +23 -0
- package/dist/types/cli/commands/auth.d.ts.map +1 -0
- package/dist/types/cli/commands/migrate.d.ts.map +1 -1
- package/dist/types/git-remote/credential-main.d.ts +2 -1
- package/dist/types/git-remote/credential-main.d.ts.map +1 -1
- package/dist/types/profiles/config.d.ts +69 -0
- package/dist/types/profiles/config.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/cli/agent.ts +95 -11
- package/src/cli/commands/auth.ts +290 -0
- package/src/cli/commands/migrate.ts +166 -6
- package/src/cli/main.ts +78 -2
- package/src/git-remote/credential-main.ts +41 -8
- package/src/profiles/config.ts +188 -0
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* helper = /path/to/git-remote-did-credential
|
|
17
17
|
*
|
|
18
18
|
* Environment:
|
|
19
|
-
* GITD_PASSWORD
|
|
19
|
+
* GITD_PASSWORD — vault password for the local agent
|
|
20
|
+
* ENBOX_PROFILE — (optional) profile name override
|
|
20
21
|
*
|
|
21
22
|
* @module
|
|
22
23
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-main.d.ts","sourceRoot":"","sources":["../../../src/git-remote/credential-main.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"credential-main.d.ts","sourceRoot":"","sources":["../../../src/git-remote/credential-main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile configuration — persistent config at `~/.enbox/config.json`.
|
|
3
|
+
*
|
|
4
|
+
* Manages the set of named identity profiles and the default selection.
|
|
5
|
+
* This module handles reading, writing, and resolving profiles across
|
|
6
|
+
* multiple selection sources (flag, env, git config, global default).
|
|
7
|
+
*
|
|
8
|
+
* Storage layout:
|
|
9
|
+
* ~/.enbox/
|
|
10
|
+
* config.json Global config (this module)
|
|
11
|
+
* profiles/
|
|
12
|
+
* <name>/DATA/AGENT/... Per-profile Web5 agent stores
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/** Base directory for all enbox data. Override with `ENBOX_HOME`. */
|
|
17
|
+
export declare function enboxHome(): string;
|
|
18
|
+
/** Path to the global config file. */
|
|
19
|
+
export declare function configPath(): string;
|
|
20
|
+
/** Base directory for all profile agent data. */
|
|
21
|
+
export declare function profilesDir(): string;
|
|
22
|
+
/** Path to a specific profile's agent data directory. */
|
|
23
|
+
export declare function profileDataPath(name: string): string;
|
|
24
|
+
/** Metadata about a single profile stored in config.json. */
|
|
25
|
+
export type ProfileEntry = {
|
|
26
|
+
/** Display name for the profile. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** The DID URI associated with this profile. */
|
|
29
|
+
did: string;
|
|
30
|
+
/** ISO 8601 timestamp when the profile was created. */
|
|
31
|
+
createdAt: string;
|
|
32
|
+
};
|
|
33
|
+
/** Top-level config.json shape. */
|
|
34
|
+
export type EnboxConfig = {
|
|
35
|
+
/** Schema version for forward-compatibility. */
|
|
36
|
+
version: number;
|
|
37
|
+
/** Name of the default profile. */
|
|
38
|
+
defaultProfile: string;
|
|
39
|
+
/** Map of profile name → metadata. */
|
|
40
|
+
profiles: Record<string, ProfileEntry>;
|
|
41
|
+
};
|
|
42
|
+
/** Read the global config. Returns a default if the file doesn't exist. */
|
|
43
|
+
export declare function readConfig(): EnboxConfig;
|
|
44
|
+
/** Write the global config atomically. */
|
|
45
|
+
export declare function writeConfig(config: EnboxConfig): void;
|
|
46
|
+
/** Add or update a profile entry in the global config. */
|
|
47
|
+
export declare function upsertProfile(name: string, entry: ProfileEntry): void;
|
|
48
|
+
/** Remove a profile entry from the global config. */
|
|
49
|
+
export declare function removeProfile(name: string): void;
|
|
50
|
+
/** List all profile names. */
|
|
51
|
+
export declare function listProfiles(): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Resolve which profile to use.
|
|
54
|
+
*
|
|
55
|
+
* Precedence (highest to lowest):
|
|
56
|
+
* 1. `--profile <name>` flag (passed as `flagProfile`)
|
|
57
|
+
* 2. `ENBOX_PROFILE` environment variable
|
|
58
|
+
* 3. `.git/config` → `[enbox] profile = <name>`
|
|
59
|
+
* 4. `~/.enbox/config.json` → `defaultProfile`
|
|
60
|
+
* 5. First (and only) profile, if exactly one exists
|
|
61
|
+
*
|
|
62
|
+
* Returns `null` when no profile can be resolved (i.e. none exist).
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveProfile(flagProfile?: string): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Write the `[enbox] profile` setting to the current repo's `.git/config`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function setGitConfigProfile(name: string): void;
|
|
69
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/profiles/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,sCAAsC;AACtC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,iDAAiD;AACjD,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAMD,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG;IACzB,oCAAoC;IACpC,IAAI,EAAG,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAG,MAAM,CAAC;IACb,uDAAuD;IACvD,SAAS,EAAG,MAAM,CAAC;CACpB,CAAC;AAEF,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG;IACxB,gDAAgD;IAChD,OAAO,EAAG,MAAM,CAAC;IACjB,mCAAmC;IACnC,cAAc,EAAG,MAAM,CAAC;IACxB,sCAAsC;IACtC,QAAQ,EAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACzC,CAAC;AAMF,4EAA4E;AAC5E,wBAAgB,UAAU,IAAI,WAAW,CAOxC;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAIrD;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAUrE;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAWhD;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAEvC;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBlE;AAoBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKtD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enbox/gitd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Decentralized forge (GitHub alternative) built on DWN protocols",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/index.js",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"bun": ">=1.0.0"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
+
"@clack/prompts": "^1.0.1",
|
|
94
95
|
"@enbox/api": "0.3.0",
|
|
95
96
|
"@enbox/crypto": "0.0.6",
|
|
96
97
|
"@enbox/dids": "0.0.7",
|
package/src/cli/agent.ts
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
* the existing vault and return a ready-to-use `TypedWeb5` instance for
|
|
7
7
|
* each protocol.
|
|
8
8
|
*
|
|
9
|
+
* Agent data is stored under the resolved profile path:
|
|
10
|
+
* `~/.enbox/profiles/<profile>/DATA/AGENT/`
|
|
11
|
+
*
|
|
9
12
|
* @module
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
import type { TypedWeb5 } from '@enbox/api';
|
|
13
16
|
|
|
14
17
|
import { Web5 } from '@enbox/api';
|
|
18
|
+
import { Web5UserAgent } from '@enbox/agent';
|
|
15
19
|
|
|
16
20
|
import type { ForgeCiSchemaMap } from '../ci.js';
|
|
17
21
|
import type { ForgeIssuesSchemaMap } from '../issues.js';
|
|
@@ -58,6 +62,24 @@ export type AgentContext = {
|
|
|
58
62
|
web5 : Web5;
|
|
59
63
|
};
|
|
60
64
|
|
|
65
|
+
/** Options for connecting to the agent. */
|
|
66
|
+
export type ConnectOptions = {
|
|
67
|
+
/** Vault password. */
|
|
68
|
+
password : string;
|
|
69
|
+
/**
|
|
70
|
+
* Agent data path. When provided, the agent stores all data under
|
|
71
|
+
* this directory instead of the default `DATA/AGENT` relative to CWD.
|
|
72
|
+
*
|
|
73
|
+
* The profile system sets this to `~/.enbox/profiles/<name>/DATA/AGENT`.
|
|
74
|
+
*/
|
|
75
|
+
dataPath? : string;
|
|
76
|
+
/**
|
|
77
|
+
* Optional recovery phrase (12-word BIP-39 mnemonic) for initializing
|
|
78
|
+
* a new vault. When omitted, a new phrase is generated automatically.
|
|
79
|
+
*/
|
|
80
|
+
recoveryPhrase? : string;
|
|
81
|
+
};
|
|
82
|
+
|
|
61
83
|
// ---------------------------------------------------------------------------
|
|
62
84
|
// Agent bootstrap
|
|
63
85
|
// ---------------------------------------------------------------------------
|
|
@@ -65,24 +87,85 @@ export type AgentContext = {
|
|
|
65
87
|
/**
|
|
66
88
|
* Connect to the local Web5 agent, initializing on first launch.
|
|
67
89
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
90
|
+
* When `dataPath` is provided, the agent's persistent data lives there.
|
|
91
|
+
* Otherwise, it falls back to `DATA/AGENT` relative to CWD (legacy).
|
|
92
|
+
*
|
|
93
|
+
* Sync is disabled — the CLI operates against the local DWN only.
|
|
71
94
|
*/
|
|
72
|
-
export async function connectAgent(
|
|
73
|
-
const {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
export async function connectAgent(options: ConnectOptions): Promise<AgentContext & { recoveryPhrase?: string }> {
|
|
96
|
+
const { password, dataPath, recoveryPhrase: inputPhrase } = options;
|
|
97
|
+
|
|
98
|
+
let agent: Web5UserAgent;
|
|
99
|
+
let recoveryPhrase: string | undefined;
|
|
100
|
+
|
|
101
|
+
if (dataPath) {
|
|
102
|
+
// Profile-based: create agent with explicit data path.
|
|
103
|
+
agent = await Web5UserAgent.create({ dataPath });
|
|
104
|
+
|
|
105
|
+
if (await agent.firstLaunch()) {
|
|
106
|
+
recoveryPhrase = await agent.initialize({
|
|
107
|
+
password,
|
|
108
|
+
recoveryPhrase : inputPhrase,
|
|
109
|
+
dwnEndpoints : ['https://enbox-dwn.fly.dev'],
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
await agent.start({ password });
|
|
113
|
+
|
|
114
|
+
// Ensure at least one identity exists.
|
|
115
|
+
const identities = await agent.identity.list();
|
|
116
|
+
let identity = identities[0];
|
|
117
|
+
if (!identity) {
|
|
118
|
+
identity = await agent.identity.create({
|
|
119
|
+
didMethod : 'dht',
|
|
120
|
+
metadata : { name: 'Default' },
|
|
121
|
+
didOptions : {
|
|
122
|
+
services: [{
|
|
123
|
+
id : 'dwn',
|
|
124
|
+
type : 'DecentralizedWebNode',
|
|
125
|
+
serviceEndpoint : ['https://enbox-dwn.fly.dev'],
|
|
126
|
+
enc : '#enc',
|
|
127
|
+
sig : '#sig',
|
|
128
|
+
}],
|
|
129
|
+
verificationMethods: [
|
|
130
|
+
{ algorithm: 'Ed25519', id: 'sig', purposes: ['assertionMethod', 'authentication'] },
|
|
131
|
+
{ algorithm: 'X25519', id: 'enc', purposes: ['keyAgreement'] },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await Web5.connect({
|
|
138
|
+
agent,
|
|
139
|
+
connectedDid : identity.did.uri,
|
|
140
|
+
sync : 'off',
|
|
141
|
+
});
|
|
77
142
|
|
|
78
|
-
|
|
143
|
+
return bindProtocols(result.web5, result.did, recoveryPhrase);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Legacy: let Web5.connect() manage the agent (uses CWD-relative path).
|
|
147
|
+
const result = await Web5.connect({ password, sync: 'off' });
|
|
148
|
+
|
|
149
|
+
if (result.recoveryPhrase) {
|
|
79
150
|
console.log('');
|
|
80
151
|
console.log(' Recovery phrase (save this — it cannot be shown again):');
|
|
81
|
-
console.log(` ${recoveryPhrase}`);
|
|
152
|
+
console.log(` ${result.recoveryPhrase}`);
|
|
82
153
|
console.log('');
|
|
83
154
|
}
|
|
84
155
|
|
|
85
|
-
|
|
156
|
+
return bindProtocols(result.web5, result.did, result.recoveryPhrase);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Internal helpers
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/** Bind typed protocol handles and configure all protocols. */
|
|
164
|
+
async function bindProtocols(
|
|
165
|
+
web5: Web5,
|
|
166
|
+
did: string,
|
|
167
|
+
recoveryPhrase?: string,
|
|
168
|
+
): Promise<AgentContext & { recoveryPhrase?: string }> {
|
|
86
169
|
const repo = web5.using(ForgeRepoProtocol);
|
|
87
170
|
const refs = web5.using(ForgeRefsProtocol);
|
|
88
171
|
const issues = web5.using(ForgeIssuesProtocol);
|
|
@@ -113,5 +196,6 @@ export async function connectAgent(password: string): Promise<AgentContext> {
|
|
|
113
196
|
return {
|
|
114
197
|
did, repo, refs, issues, patches, ci, releases,
|
|
115
198
|
registry, social, notifications, wiki, org, web5,
|
|
199
|
+
recoveryPhrase,
|
|
116
200
|
};
|
|
117
201
|
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gitd auth` — identity and profile management.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* gitd auth Show current identity info
|
|
6
|
+
* gitd auth login Create or import an identity
|
|
7
|
+
* gitd auth list List all profiles
|
|
8
|
+
* gitd auth use <profile> Set profile for current repo
|
|
9
|
+
* gitd auth use <profile> --global Set default profile
|
|
10
|
+
* gitd auth export [profile] Export portable identity
|
|
11
|
+
* gitd auth import Import from recovery phrase
|
|
12
|
+
* gitd auth logout [profile] Remove a profile
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AgentContext } from '../agent.js';
|
|
18
|
+
|
|
19
|
+
import * as p from '@clack/prompts';
|
|
20
|
+
|
|
21
|
+
import { connectAgent } from '../agent.js';
|
|
22
|
+
import {
|
|
23
|
+
enboxHome,
|
|
24
|
+
listProfiles,
|
|
25
|
+
profileDataPath,
|
|
26
|
+
readConfig,
|
|
27
|
+
resolveProfile,
|
|
28
|
+
setGitConfigProfile,
|
|
29
|
+
upsertProfile,
|
|
30
|
+
writeConfig,
|
|
31
|
+
} from '../../profiles/config.js';
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Sub-command dispatch
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The auth command can run without a pre-existing AgentContext (for `login`,
|
|
39
|
+
* `list`), but some sub-commands need one (for `export`). We accept
|
|
40
|
+
* ctx as optional and connect lazily when needed.
|
|
41
|
+
*/
|
|
42
|
+
export async function authCommand(ctx: AgentContext | null, args: string[]): Promise<void> {
|
|
43
|
+
const sub = args[0];
|
|
44
|
+
|
|
45
|
+
switch (sub) {
|
|
46
|
+
case 'login': return authLogin();
|
|
47
|
+
case 'list': return authList();
|
|
48
|
+
case 'use': return authUse(args.slice(1));
|
|
49
|
+
case 'logout': return authLogout(args.slice(1));
|
|
50
|
+
default: return authInfo(ctx);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// auth (no subcommand) — show current identity
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
async function authInfo(ctx: AgentContext | null): Promise<void> {
|
|
59
|
+
const config = readConfig();
|
|
60
|
+
const profileName = resolveProfile();
|
|
61
|
+
|
|
62
|
+
if (!profileName || !config.profiles[profileName]) {
|
|
63
|
+
p.log.warn('No identity configured. Run `gitd auth login` to get started.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const entry = config.profiles[profileName];
|
|
68
|
+
|
|
69
|
+
p.log.info(`Profile: ${profileName}${config.defaultProfile === profileName ? ' (default)' : ''}`);
|
|
70
|
+
p.log.info(`DID: ${entry.did}`);
|
|
71
|
+
p.log.info(`Created: ${entry.createdAt}`);
|
|
72
|
+
p.log.info(`Data: ${profileDataPath(profileName)}`);
|
|
73
|
+
|
|
74
|
+
if (ctx) {
|
|
75
|
+
p.log.info(`Connected: ${ctx.did}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// auth login — create or import identity
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
async function authLogin(): Promise<void> {
|
|
84
|
+
p.intro('Identity setup');
|
|
85
|
+
|
|
86
|
+
const action = await p.select({
|
|
87
|
+
message : 'What would you like to do?',
|
|
88
|
+
options : [
|
|
89
|
+
{ value: 'create', label: 'Create a new identity' },
|
|
90
|
+
{ value: 'import', label: 'Import from recovery phrase' },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (p.isCancel(action)) {
|
|
95
|
+
p.cancel('Cancelled.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const name = await p.text({
|
|
100
|
+
message : 'Name this profile:',
|
|
101
|
+
placeholder : 'personal',
|
|
102
|
+
validate(val) {
|
|
103
|
+
if (!val?.trim()) { return 'Profile name is required.'; }
|
|
104
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(val)) { return 'Only letters, numbers, hyphens, and underscores.'; }
|
|
105
|
+
const existing = listProfiles();
|
|
106
|
+
if (existing.includes(val)) { return `Profile "${val}" already exists.`; }
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (p.isCancel(name)) {
|
|
111
|
+
p.cancel('Cancelled.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const profileName = (name as string).trim();
|
|
116
|
+
|
|
117
|
+
const password = await p.password({
|
|
118
|
+
message: 'Choose a password for your vault:',
|
|
119
|
+
validate(val) {
|
|
120
|
+
if (!val || (val as string).length < 4) { return 'Password must be at least 4 characters.'; }
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (p.isCancel(password)) {
|
|
125
|
+
p.cancel('Cancelled.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let recoveryInput: string | undefined;
|
|
130
|
+
|
|
131
|
+
if (action === 'import') {
|
|
132
|
+
const phrase = await p.text({
|
|
133
|
+
message : 'Enter your 12-word recovery phrase:',
|
|
134
|
+
placeholder : 'abandon ability able about above absent ...',
|
|
135
|
+
validate(val) {
|
|
136
|
+
if (!val) { return 'Recovery phrase is required.'; }
|
|
137
|
+
const words = val.trim().split(/\s+/);
|
|
138
|
+
if (words.length !== 12) { return 'Recovery phrase must be exactly 12 words.'; }
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (p.isCancel(phrase)) {
|
|
143
|
+
p.cancel('Cancelled.');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
recoveryInput = (phrase as string).trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const spin = p.spinner();
|
|
151
|
+
spin.start('Creating identity...');
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const dataPath = profileDataPath(profileName);
|
|
155
|
+
const result = await connectAgent({
|
|
156
|
+
password : password as string,
|
|
157
|
+
dataPath,
|
|
158
|
+
recoveryPhrase : recoveryInput,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Save profile metadata.
|
|
162
|
+
upsertProfile(profileName, {
|
|
163
|
+
name : profileName,
|
|
164
|
+
did : result.did,
|
|
165
|
+
createdAt : new Date().toISOString(),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
spin.stop('Identity created!');
|
|
169
|
+
|
|
170
|
+
p.log.success(`DID: ${result.did}`);
|
|
171
|
+
p.log.success(`Profile: ${profileName} (${dataPath})`);
|
|
172
|
+
|
|
173
|
+
if (result.recoveryPhrase) {
|
|
174
|
+
p.log.warn('');
|
|
175
|
+
p.log.warn('Your recovery phrase (write this down!):');
|
|
176
|
+
p.log.warn(` ${result.recoveryPhrase}`);
|
|
177
|
+
p.log.warn('');
|
|
178
|
+
p.log.warn('This phrase can recover your identity if you lose your password.');
|
|
179
|
+
p.log.warn('Store it securely — it will NOT be shown again.');
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
spin.stop('Failed.');
|
|
183
|
+
p.log.error(`Failed to create identity: ${(err as Error).message}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
p.outro('You\'re all set! Run `gitd whoami` to verify.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// auth list — list all profiles
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
function authList(): void {
|
|
195
|
+
const config = readConfig();
|
|
196
|
+
const names = Object.keys(config.profiles);
|
|
197
|
+
|
|
198
|
+
if (names.length === 0) {
|
|
199
|
+
p.log.warn('No profiles found. Run `gitd auth login` to create one.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
p.log.info(`Profiles (${enboxHome()}):\n`);
|
|
204
|
+
|
|
205
|
+
for (const name of names) {
|
|
206
|
+
const entry = config.profiles[name];
|
|
207
|
+
const isDefault = config.defaultProfile === name ? ' (default)' : '';
|
|
208
|
+
p.log.info(` ${name}${isDefault}`);
|
|
209
|
+
p.log.info(` DID: ${entry.did}`);
|
|
210
|
+
p.log.info(` Created: ${entry.createdAt}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// auth use — set active profile
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
async function authUse(args: string[]): Promise<void> {
|
|
219
|
+
const name = args[0];
|
|
220
|
+
const isGlobal = args.includes('--global');
|
|
221
|
+
|
|
222
|
+
if (!name) {
|
|
223
|
+
p.log.error('Usage: gitd auth use <profile> [--global]');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const config = readConfig();
|
|
228
|
+
if (!config.profiles[name]) {
|
|
229
|
+
p.log.error(`Profile "${name}" not found. Run \`gitd auth list\` to see available profiles.`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (isGlobal) {
|
|
234
|
+
config.defaultProfile = name;
|
|
235
|
+
writeConfig(config);
|
|
236
|
+
p.log.success(`Default profile set to "${name}".`);
|
|
237
|
+
} else {
|
|
238
|
+
try {
|
|
239
|
+
setGitConfigProfile(name);
|
|
240
|
+
p.log.success(`Profile "${name}" set for this repository.`);
|
|
241
|
+
} catch {
|
|
242
|
+
p.log.error('Not in a git repository. Use --global to set the default profile.');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// auth logout — remove a profile
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
async function authLogout(args: string[]): Promise<void> {
|
|
253
|
+
let name = args[0];
|
|
254
|
+
|
|
255
|
+
if (!name) {
|
|
256
|
+
name = resolveProfile() ?? '';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!name) {
|
|
260
|
+
p.log.error('No profile specified and no default profile found.');
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const config = readConfig();
|
|
265
|
+
if (!config.profiles[name]) {
|
|
266
|
+
p.log.error(`Profile "${name}" not found.`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const confirm = await p.confirm({
|
|
271
|
+
message: `Remove profile "${name}"? This will delete all local data for this identity.`,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
275
|
+
p.cancel('Cancelled.');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Remove from config (we don't delete the data directory — user can do that).
|
|
280
|
+
delete config.profiles[name];
|
|
281
|
+
if (config.defaultProfile === name) {
|
|
282
|
+
const remaining = Object.keys(config.profiles);
|
|
283
|
+
config.defaultProfile = remaining[0] ?? '';
|
|
284
|
+
}
|
|
285
|
+
writeConfig(config);
|
|
286
|
+
|
|
287
|
+
p.log.success(`Profile "${name}" removed.`);
|
|
288
|
+
p.log.info(`Data directory preserved at: ${profileDataPath(name)}`);
|
|
289
|
+
p.log.info('Delete it manually if you want to free disk space.');
|
|
290
|
+
}
|