@graphorin/cli 0.5.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/bin/graphorin.d.ts +1 -0
- package/dist/bin/graphorin.js +606 -0
- package/dist/bin/graphorin.js.map +1 -0
- package/dist/commands/audit.d.ts +59 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +156 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +48 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +148 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/consolidator.d.ts +49 -0
- package/dist/commands/consolidator.d.ts.map +1 -0
- package/dist/commands/consolidator.js +97 -0
- package/dist/commands/consolidator.js.map +1 -0
- package/dist/commands/doctor.d.ts +64 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +150 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/guard.d.ts +35 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +86 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/index.d.ts +21 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/init.d.ts +46 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +131 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/memory.d.ts +243 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +471 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/migrate-config.d.ts +20 -0
- package/dist/commands/migrate-config.d.ts.map +1 -0
- package/dist/commands/migrate-config.js +48 -0
- package/dist/commands/migrate-config.js.map +1 -0
- package/dist/commands/migrate-export.d.ts +26 -0
- package/dist/commands/migrate-export.d.ts.map +1 -0
- package/dist/commands/migrate-export.js +67 -0
- package/dist/commands/migrate-export.js.map +1 -0
- package/dist/commands/migrate.d.ts +31 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +59 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/pricing.d.ts +66 -0
- package/dist/commands/pricing.d.ts.map +1 -0
- package/dist/commands/pricing.js +128 -0
- package/dist/commands/pricing.js.map +1 -0
- package/dist/commands/secrets.d.ts +96 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +182 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/skills.d.ts +58 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +181 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/start.d.ts +51 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +122 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/storage.d.ts +110 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +274 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/commands/telemetry.d.ts +21 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +75 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/token.d.ts +109 -0
- package/dist/commands/token.d.ts.map +1 -0
- package/dist/commands/token.js +246 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/tools-lint.d.ts +103 -0
- package/dist/commands/tools-lint.d.ts.map +1 -0
- package/dist/commands/tools-lint.js +240 -0
- package/dist/commands/tools-lint.js.map +1 -0
- package/dist/commands/traces.d.ts +33 -0
- package/dist/commands/traces.d.ts.map +1 -0
- package/dist/commands/traces.js +103 -0
- package/dist/commands/traces.js.map +1 -0
- package/dist/commands/triggers.d.ts +49 -0
- package/dist/commands/triggers.d.ts.map +1 -0
- package/dist/commands/triggers.js +129 -0
- package/dist/commands/triggers.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/exit.js +17 -0
- package/dist/internal/exit.js.map +1 -0
- package/dist/internal/load-config.js +84 -0
- package/dist/internal/load-config.js.map +1 -0
- package/dist/internal/offline.d.ts +58 -0
- package/dist/internal/offline.d.ts.map +1 -0
- package/dist/internal/offline.js +72 -0
- package/dist/internal/offline.js.map +1 -0
- package/dist/internal/output.d.ts +56 -0
- package/dist/internal/output.d.ts.map +1 -0
- package/dist/internal/output.js +86 -0
- package/dist/internal/output.js.map +1 -0
- package/dist/internal/prompts.js +49 -0
- package/dist/internal/prompts.js.map +1 -0
- package/dist/internal/store-context.js +69 -0
- package/dist/internal/store-context.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CommonOutputOptions } from "../internal/output.js";
|
|
2
|
+
import { TokenMetadata } from "@graphorin/security";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/token.d.ts
|
|
5
|
+
|
|
6
|
+
/** @stable */
|
|
7
|
+
interface TokenCommonOptions extends CommonOutputOptions {
|
|
8
|
+
readonly config?: string;
|
|
9
|
+
}
|
|
10
|
+
/** @stable */
|
|
11
|
+
interface TokenCreateOptions extends TokenCommonOptions {
|
|
12
|
+
readonly label?: string;
|
|
13
|
+
/** Comma-separated scope list. */
|
|
14
|
+
readonly scopes: ReadonlyArray<string>;
|
|
15
|
+
/** Duration string: `30d`, `12h`, `90m`, `45s`. */
|
|
16
|
+
readonly expiresIn?: string;
|
|
17
|
+
readonly env?: 'live' | 'test';
|
|
18
|
+
}
|
|
19
|
+
/** @stable */
|
|
20
|
+
interface TokenCreateResult {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly label?: string;
|
|
23
|
+
readonly scopes: ReadonlyArray<string>;
|
|
24
|
+
readonly raw: string;
|
|
25
|
+
readonly expiresAt?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a token. Returns the raw value once + the persisted record.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
declare function runTokenCreate(options: TokenCreateOptions): Promise<TokenCreateResult>;
|
|
33
|
+
/** @stable */
|
|
34
|
+
interface TokenListOptions extends TokenCommonOptions {
|
|
35
|
+
readonly includeRevoked?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* List token metadata.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
declare function runTokenList(options?: TokenListOptions): Promise<ReadonlyArray<TokenMetadata>>;
|
|
43
|
+
/** @stable */
|
|
44
|
+
interface TokenRevokeOptions extends TokenCommonOptions {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Revoke a single token.
|
|
49
|
+
*
|
|
50
|
+
* @stable
|
|
51
|
+
*/
|
|
52
|
+
declare function runTokenRevoke(options: TokenRevokeOptions): Promise<TokenMetadata | undefined>;
|
|
53
|
+
/** @stable */
|
|
54
|
+
interface TokenRotateOptions extends TokenCommonOptions {
|
|
55
|
+
readonly id: string;
|
|
56
|
+
readonly env?: 'live' | 'test';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Revoke an existing token and immediately mint a fresh one with the
|
|
60
|
+
* same scopes. Returns the new raw token bytes once.
|
|
61
|
+
*
|
|
62
|
+
* @stable
|
|
63
|
+
*/
|
|
64
|
+
declare function runTokenRotate(options: TokenRotateOptions): Promise<TokenCreateResult>;
|
|
65
|
+
/** @stable */
|
|
66
|
+
interface TokenRekeyOptions extends TokenCommonOptions {
|
|
67
|
+
readonly env?: 'live' | 'test';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Re-issue every active token. Used after a known compromise.
|
|
71
|
+
*
|
|
72
|
+
* @stable
|
|
73
|
+
*/
|
|
74
|
+
declare function runTokenRekey(options?: TokenRekeyOptions): Promise<ReadonlyArray<{
|
|
75
|
+
readonly oldId: string;
|
|
76
|
+
readonly newId: string;
|
|
77
|
+
readonly raw: string;
|
|
78
|
+
}>>;
|
|
79
|
+
/** @stable */
|
|
80
|
+
interface TokenVerifyOptions extends CommonOutputOptions {
|
|
81
|
+
readonly token: string;
|
|
82
|
+
/** Optional override of the expected token prefix. */
|
|
83
|
+
readonly prefix?: string;
|
|
84
|
+
}
|
|
85
|
+
/** @stable */
|
|
86
|
+
interface TokenVerifyResult {
|
|
87
|
+
readonly ok: boolean;
|
|
88
|
+
readonly reason?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Offline checksum verification. Confirms the structural shape, the
|
|
92
|
+
* environment marker, and the CRC checksum but does NOT consult the
|
|
93
|
+
* token store — it only proves the token was minted by a Graphorin
|
|
94
|
+
* helper.
|
|
95
|
+
*
|
|
96
|
+
* @stable
|
|
97
|
+
*/
|
|
98
|
+
declare function runTokenVerify(options: TokenVerifyOptions): TokenVerifyResult;
|
|
99
|
+
/**
|
|
100
|
+
* Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns
|
|
101
|
+
* milliseconds. Throws on invalid input — surfaced as a fail-fast at
|
|
102
|
+
* the CLI boundary.
|
|
103
|
+
*
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
declare function parseDuration(input: string): number;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { TokenCommonOptions, TokenCreateOptions, TokenCreateResult, TokenListOptions, TokenRekeyOptions, TokenRevokeOptions, TokenRotateOptions, TokenVerifyOptions, TokenVerifyResult, parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify };
|
|
109
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","names":[],"sources":["../../src/commands/token.ts"],"sourcesContent":[],"mappings":";;;;;AAgMA;AAWsB,UA5JL,kBAAA,SAA2B,mBA4JR,CAAA;EAAU,SAAA,MAAA,CAAA,EAAA,MAAA;;;AAA4B,UAvJzD,kBAAA,SAA2B,kBAuJ8B,CAAA;EAuCzD,SAAA,KAAA,CAAA,EAAA,MAAkB;EASb;EACX,SAAA,MAAA,EArMQ,aAqMR,CAAA,MAAA,CAAA;EAET;EADC,SAAA,SAAA,CAAA,EAAA,MAAA;EAAO,SAAA,GAAA,CAAA,EAAA,MAAA,GAAA,MAAA;AAqCV;AAOA;AAagB,UAxPC,iBAAA,CAwPuB;EA2BxB,SAAA,EAAA,EAAA,MAAa;;mBAhRV;;;;;;;;;iBAUG,cAAA,UAAwB,qBAAqB,QAAQ;;UA8C1D,gBAAA,SAAyB;;;;;;;;iBASpB,YAAA,WACX,mBACR,QAAQ,cAAc;;UA2BR,kBAAA,SAA2B;;;;;;;;iBAStB,cAAA,UACX,qBACR,QAAQ;;UAsBM,kBAAA,SAA2B;;;;;;;;;;iBAWtB,cAAA,UAAwB,qBAAqB,QAAQ;;UAuC1D,iBAAA,SAA0B;;;;;;;;iBASrB,aAAA,WACX,oBACR,QACD;;;;;;UAoCe,kBAAA,SAA2B;;;;;;UAO3B,iBAAA;;;;;;;;;;;;iBAaD,cAAA,UAAwB,qBAAqB;;;;;;;;iBA2B7C,aAAA"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/output.js";
|
|
2
|
+
import { EXIT_CODES } from "../internal/exit.js";
|
|
3
|
+
import { openStoreContext } from "../internal/store-context.js";
|
|
4
|
+
import { createToken, listTokens, rekeyTokens, revokeToken, rotateToken, verifyOffline } from "@graphorin/security";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/token.ts
|
|
7
|
+
/**
|
|
8
|
+
* `graphorin token` — manage server auth tokens.
|
|
9
|
+
*
|
|
10
|
+
* The subcommand thin-wraps the library functions in
|
|
11
|
+
* `@graphorin/security/auth` (`createToken`, `listTokens`,
|
|
12
|
+
* `revokeToken`, `rotateToken`, `rekeyTokens`, `verifyOffline`). Per
|
|
13
|
+
* Phase 15 § Tokens the surface ships:
|
|
14
|
+
*
|
|
15
|
+
* - `graphorin token create --label <name> --scopes <list> [--expires-in <duration>]`
|
|
16
|
+
* - `graphorin token list`
|
|
17
|
+
* - `graphorin token revoke <id>`
|
|
18
|
+
* - `graphorin token rotate <id>`
|
|
19
|
+
* - `graphorin token rekey`
|
|
20
|
+
* - `graphorin token verify <token>`
|
|
21
|
+
*
|
|
22
|
+
* The raw token bytes are shown to the operator at most once — at the
|
|
23
|
+
* call site of `create` / `rotate` / `rekey`. They are wrapped in a
|
|
24
|
+
* {@link SecretValue} on the way back to keep accidental logging out
|
|
25
|
+
* of the codepath; the CLI prints them via `value.use((s) => ...)` so
|
|
26
|
+
* the bytes never live as a plain string variable longer than needed.
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
const ONE_DAY_MS = 1440 * 60 * 1e3;
|
|
31
|
+
/**
|
|
32
|
+
* Create a token. Returns the raw value once + the persisted record.
|
|
33
|
+
*
|
|
34
|
+
* @stable
|
|
35
|
+
*/
|
|
36
|
+
async function runTokenCreate(options) {
|
|
37
|
+
const ctx = await openStoreContext({
|
|
38
|
+
...options.config !== void 0 ? { config: options.config } : {},
|
|
39
|
+
requirePepper: true
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
if (ctx.pepper === void 0) throw new Error("[graphorin/cli] internal: pepper resolution should have populated ctx.pepper");
|
|
43
|
+
const env = options.env ?? "live";
|
|
44
|
+
const expiresInMs = options.expiresIn !== void 0 ? parseDuration(options.expiresIn) : void 0;
|
|
45
|
+
const created = await createToken({
|
|
46
|
+
tokenStore: ctx.store.authTokens,
|
|
47
|
+
pepper: ctx.pepper,
|
|
48
|
+
env,
|
|
49
|
+
scopes: options.scopes,
|
|
50
|
+
...options.label !== void 0 ? { label: options.label } : {},
|
|
51
|
+
...expiresInMs !== void 0 ? { expiresInMs } : {}
|
|
52
|
+
});
|
|
53
|
+
const raw = await created.raw.use((s) => String(s));
|
|
54
|
+
const out = Object.freeze({
|
|
55
|
+
id: created.record.id,
|
|
56
|
+
...created.record.label !== void 0 ? { label: created.record.label } : {},
|
|
57
|
+
scopes: Object.freeze([...created.record.scopes]),
|
|
58
|
+
raw,
|
|
59
|
+
...created.record.expiresAt !== void 0 ? { expiresAt: created.record.expiresAt } : {}
|
|
60
|
+
});
|
|
61
|
+
emitReport(options, out, () => {
|
|
62
|
+
const print = options.print ?? defaultPrintSink;
|
|
63
|
+
print(brand(`token created (id=${out.id}, scopes=${out.scopes.join(",")})`));
|
|
64
|
+
print(brand(`raw token (shown ONCE):`));
|
|
65
|
+
print(` ${out.raw}`);
|
|
66
|
+
if (out.expiresAt !== void 0) print(brand(`expires at: ${out.expiresAt}`));
|
|
67
|
+
});
|
|
68
|
+
return out;
|
|
69
|
+
} finally {
|
|
70
|
+
await ctx.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* List token metadata.
|
|
75
|
+
*
|
|
76
|
+
* @stable
|
|
77
|
+
*/
|
|
78
|
+
async function runTokenList(options = {}) {
|
|
79
|
+
const ctx = await openStoreContext({ ...options.config !== void 0 ? { config: options.config } : {} });
|
|
80
|
+
try {
|
|
81
|
+
const list = await listTokens(ctx.store.authTokens, { includeRevoked: options.includeRevoked === true });
|
|
82
|
+
emitReport(options, list, () => {
|
|
83
|
+
const print = options.print ?? defaultPrintSink;
|
|
84
|
+
if (list.length === 0) {
|
|
85
|
+
print(brand("no tokens registered."));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
print(brand(`${list.length} token(s):`));
|
|
89
|
+
for (const t of list) print(` ${t.revokedAt !== void 0 ? statusMarker("warn") : statusMarker("ok")} ${t.id} (label=${t.label ?? "-"}, scopes=${t.scopes.join(",")})`);
|
|
90
|
+
});
|
|
91
|
+
return list;
|
|
92
|
+
} finally {
|
|
93
|
+
await ctx.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Revoke a single token.
|
|
98
|
+
*
|
|
99
|
+
* @stable
|
|
100
|
+
*/
|
|
101
|
+
async function runTokenRevoke(options) {
|
|
102
|
+
const ctx = await openStoreContext({ ...options.config !== void 0 ? { config: options.config } : {} });
|
|
103
|
+
try {
|
|
104
|
+
const result = await revokeToken(ctx.store.authTokens, options.id);
|
|
105
|
+
emitReport(options, result ?? null, () => {
|
|
106
|
+
const print = options.print ?? defaultPrintSink;
|
|
107
|
+
if (result === void 0) {
|
|
108
|
+
print(brand(`token '${options.id}' not found.`));
|
|
109
|
+
process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
print(brand(`token '${result.id}' revoked at ${result.revokedAt ?? "<now>"}`));
|
|
113
|
+
});
|
|
114
|
+
return result;
|
|
115
|
+
} finally {
|
|
116
|
+
await ctx.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Revoke an existing token and immediately mint a fresh one with the
|
|
121
|
+
* same scopes. Returns the new raw token bytes once.
|
|
122
|
+
*
|
|
123
|
+
* @stable
|
|
124
|
+
*/
|
|
125
|
+
async function runTokenRotate(options) {
|
|
126
|
+
const ctx = await openStoreContext({
|
|
127
|
+
...options.config !== void 0 ? { config: options.config } : {},
|
|
128
|
+
requirePepper: true
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
if (ctx.pepper === void 0) throw new Error("[graphorin/cli] internal: pepper missing.");
|
|
132
|
+
const env = options.env ?? "live";
|
|
133
|
+
const result = await rotateToken({
|
|
134
|
+
tokenStore: ctx.store.authTokens,
|
|
135
|
+
pepper: ctx.pepper,
|
|
136
|
+
id: options.id,
|
|
137
|
+
env
|
|
138
|
+
});
|
|
139
|
+
const raw = await result.next.raw.use((s) => String(s));
|
|
140
|
+
const out = Object.freeze({
|
|
141
|
+
id: result.next.record.id,
|
|
142
|
+
...result.next.record.label !== void 0 ? { label: result.next.record.label } : {},
|
|
143
|
+
scopes: Object.freeze([...result.next.record.scopes]),
|
|
144
|
+
raw,
|
|
145
|
+
...result.next.record.expiresAt !== void 0 ? { expiresAt: result.next.record.expiresAt } : {}
|
|
146
|
+
});
|
|
147
|
+
emitReport(options, out, () => {
|
|
148
|
+
const print = options.print ?? defaultPrintSink;
|
|
149
|
+
print(brand(`token '${result.old.id}' revoked + replaced by '${out.id}'`));
|
|
150
|
+
print(brand("raw token (shown ONCE):"));
|
|
151
|
+
print(` ${raw}`);
|
|
152
|
+
});
|
|
153
|
+
return out;
|
|
154
|
+
} finally {
|
|
155
|
+
await ctx.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Re-issue every active token. Used after a known compromise.
|
|
160
|
+
*
|
|
161
|
+
* @stable
|
|
162
|
+
*/
|
|
163
|
+
async function runTokenRekey(options = {}) {
|
|
164
|
+
const ctx = await openStoreContext({
|
|
165
|
+
...options.config !== void 0 ? { config: options.config } : {},
|
|
166
|
+
requirePepper: true
|
|
167
|
+
});
|
|
168
|
+
try {
|
|
169
|
+
if (ctx.pepper === void 0) throw new Error("[graphorin/cli] internal: pepper missing.");
|
|
170
|
+
const result = await rekeyTokens({
|
|
171
|
+
tokenStore: ctx.store.authTokens,
|
|
172
|
+
pepper: ctx.pepper,
|
|
173
|
+
env: options.env ?? "live"
|
|
174
|
+
});
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const [oldId, created] of result) {
|
|
177
|
+
const raw = await created.raw.use((s) => String(s));
|
|
178
|
+
out.push(Object.freeze({
|
|
179
|
+
oldId,
|
|
180
|
+
newId: created.record.id,
|
|
181
|
+
raw
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
const frozen = Object.freeze(out);
|
|
185
|
+
emitReport(options, frozen, () => {
|
|
186
|
+
const print = options.print ?? defaultPrintSink;
|
|
187
|
+
print(brand(`rekeyed ${frozen.length} token(s):`));
|
|
188
|
+
for (const row of frozen) {
|
|
189
|
+
print(` - old=${row.oldId} new=${row.newId}`);
|
|
190
|
+
print(` raw: ${row.raw}`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return frozen;
|
|
194
|
+
} finally {
|
|
195
|
+
await ctx.close();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Offline checksum verification. Confirms the structural shape, the
|
|
200
|
+
* environment marker, and the CRC checksum but does NOT consult the
|
|
201
|
+
* token store — it only proves the token was minted by a Graphorin
|
|
202
|
+
* helper.
|
|
203
|
+
*
|
|
204
|
+
* @stable
|
|
205
|
+
*/
|
|
206
|
+
function runTokenVerify(options) {
|
|
207
|
+
const result = verifyOffline(options.token, { ...options.prefix !== void 0 ? { acceptPrefix: options.prefix } : {} });
|
|
208
|
+
const out = Object.freeze({
|
|
209
|
+
ok: result.ok,
|
|
210
|
+
...result.ok ? {} : { reason: result.reason }
|
|
211
|
+
});
|
|
212
|
+
emitReport(options, out, () => {
|
|
213
|
+
const print = options.print ?? defaultPrintSink;
|
|
214
|
+
if (out.ok) {
|
|
215
|
+
print(brand(`token format ${statusMarker("ok")} (offline checksum verified)`));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
print(brand(`token format ${statusMarker("fail")} (${out.reason ?? "unknown reason"})`));
|
|
219
|
+
process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
|
|
220
|
+
});
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns
|
|
225
|
+
* milliseconds. Throws on invalid input — surfaced as a fail-fast at
|
|
226
|
+
* the CLI boundary.
|
|
227
|
+
*
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
function parseDuration(input) {
|
|
231
|
+
const match = /^(\d+)\s*([smhd])$/i.exec(input.trim());
|
|
232
|
+
if (!match) throw new Error(`[graphorin/cli] invalid --expires-in '${input}'. Use the form '<number><s|m|h|d>' (e.g. 30d).`);
|
|
233
|
+
const value = Number.parseInt(match[1], 10);
|
|
234
|
+
const unit = match[2].toLowerCase();
|
|
235
|
+
switch (unit) {
|
|
236
|
+
case "s": return value * 1e3;
|
|
237
|
+
case "m": return value * 6e4;
|
|
238
|
+
case "h": return value * 60 * 6e4;
|
|
239
|
+
case "d": return value * ONE_DAY_MS;
|
|
240
|
+
default: throw new Error(`[graphorin/cli] invalid --expires-in unit '${unit}'.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
export { parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify };
|
|
246
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","names":["out: TokenCreateResult","out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }>","out: TokenVerifyResult"],"sources":["../../src/commands/token.ts"],"sourcesContent":["/**\n * `graphorin token` — manage server auth tokens.\n *\n * The subcommand thin-wraps the library functions in\n * `@graphorin/security/auth` (`createToken`, `listTokens`,\n * `revokeToken`, `rotateToken`, `rekeyTokens`, `verifyOffline`). Per\n * Phase 15 § Tokens the surface ships:\n *\n * - `graphorin token create --label <name> --scopes <list> [--expires-in <duration>]`\n * - `graphorin token list`\n * - `graphorin token revoke <id>`\n * - `graphorin token rotate <id>`\n * - `graphorin token rekey`\n * - `graphorin token verify <token>`\n *\n * The raw token bytes are shown to the operator at most once — at the\n * call site of `create` / `rotate` / `rekey`. They are wrapped in a\n * {@link SecretValue} on the way back to keep accidental logging out\n * of the codepath; the CLI prints them via `value.use((s) => ...)` so\n * the bytes never live as a plain string variable longer than needed.\n *\n * @packageDocumentation\n */\n\nimport {\n createToken,\n listTokens,\n rekeyTokens,\n revokeToken,\n rotateToken,\n type TokenMetadata,\n verifyOffline,\n} from '@graphorin/security';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/** @stable */\nexport interface TokenCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface TokenCreateOptions extends TokenCommonOptions {\n readonly label?: string;\n /** Comma-separated scope list. */\n readonly scopes: ReadonlyArray<string>;\n /** Duration string: `30d`, `12h`, `90m`, `45s`. */\n readonly expiresIn?: string;\n readonly env?: 'live' | 'test';\n}\n\n/** @stable */\nexport interface TokenCreateResult {\n readonly id: string;\n readonly label?: string;\n readonly scopes: ReadonlyArray<string>;\n readonly raw: string;\n readonly expiresAt?: string;\n}\n\n/**\n * Create a token. Returns the raw value once + the persisted record.\n *\n * @stable\n */\nexport async function runTokenCreate(options: TokenCreateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error(\n '[graphorin/cli] internal: pepper resolution should have populated ctx.pepper',\n );\n }\n const env = options.env ?? 'live';\n const expiresInMs =\n options.expiresIn !== undefined ? parseDuration(options.expiresIn) : undefined;\n const created = await createToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env,\n scopes: options.scopes,\n ...(options.label !== undefined ? { label: options.label } : {}),\n ...(expiresInMs !== undefined ? { expiresInMs } : {}),\n });\n const raw = await created.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: created.record.id,\n ...(created.record.label !== undefined ? { label: created.record.label } : {}),\n scopes: Object.freeze([...created.record.scopes]),\n raw,\n ...(created.record.expiresAt !== undefined ? { expiresAt: created.record.expiresAt } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token created (id=${out.id}, scopes=${out.scopes.join(',')})`));\n print(brand(`raw token (shown ONCE):`));\n print(` ${out.raw}`);\n if (out.expiresAt !== undefined) {\n print(brand(`expires at: ${out.expiresAt}`));\n }\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenListOptions extends TokenCommonOptions {\n readonly includeRevoked?: boolean;\n}\n\n/**\n * List token metadata.\n *\n * @stable\n */\nexport async function runTokenList(\n options: TokenListOptions = {},\n): Promise<ReadonlyArray<TokenMetadata>> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const list = await listTokens(ctx.store.authTokens, {\n includeRevoked: options.includeRevoked === true,\n });\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no tokens registered.'));\n return;\n }\n print(brand(`${list.length} token(s):`));\n for (const t of list) {\n const status = t.revokedAt !== undefined ? statusMarker('warn') : statusMarker('ok');\n print(` ${status} ${t.id} (label=${t.label ?? '-'}, scopes=${t.scopes.join(',')})`);\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRevokeOptions extends TokenCommonOptions {\n readonly id: string;\n}\n\n/**\n * Revoke a single token.\n *\n * @stable\n */\nexport async function runTokenRevoke(\n options: TokenRevokeOptions,\n): Promise<TokenMetadata | undefined> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const result = await revokeToken(ctx.store.authTokens, options.id);\n emitReport(options, result ?? null, () => {\n const print = options.print ?? defaultPrintSink;\n if (result === undefined) {\n print(brand(`token '${options.id}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`token '${result.id}' revoked at ${result.revokedAt ?? '<now>'}`));\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRotateOptions extends TokenCommonOptions {\n readonly id: string;\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Revoke an existing token and immediately mint a fresh one with the\n * same scopes. Returns the new raw token bytes once.\n *\n * @stable\n */\nexport async function runTokenRotate(options: TokenRotateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const env = options.env ?? 'live';\n const result = await rotateToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n id: options.id,\n env,\n });\n const raw = await result.next.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: result.next.record.id,\n ...(result.next.record.label !== undefined ? { label: result.next.record.label } : {}),\n scopes: Object.freeze([...result.next.record.scopes]),\n raw,\n ...(result.next.record.expiresAt !== undefined\n ? { expiresAt: result.next.record.expiresAt }\n : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token '${result.old.id}' revoked + replaced by '${out.id}'`));\n print(brand('raw token (shown ONCE):'));\n print(` ${raw}`);\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRekeyOptions extends TokenCommonOptions {\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Re-issue every active token. Used after a known compromise.\n *\n * @stable\n */\nexport async function runTokenRekey(\n options: TokenRekeyOptions = {},\n): Promise<\n ReadonlyArray<{ readonly oldId: string; readonly newId: string; readonly raw: string }>\n> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const result = await rekeyTokens({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env: options.env ?? 'live',\n });\n const out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }> = [];\n for (const [oldId, created] of result) {\n const raw = await created.raw.use((s) => String(s));\n out.push(Object.freeze({ oldId, newId: created.record.id, raw }));\n }\n const frozen = Object.freeze(out);\n emitReport(options, frozen, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`rekeyed ${frozen.length} token(s):`));\n for (const row of frozen) {\n print(` - old=${row.oldId} new=${row.newId}`);\n print(` raw: ${row.raw}`);\n }\n });\n return frozen;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenVerifyOptions extends CommonOutputOptions {\n readonly token: string;\n /** Optional override of the expected token prefix. */\n readonly prefix?: string;\n}\n\n/** @stable */\nexport interface TokenVerifyResult {\n readonly ok: boolean;\n readonly reason?: string;\n}\n\n/**\n * Offline checksum verification. Confirms the structural shape, the\n * environment marker, and the CRC checksum but does NOT consult the\n * token store — it only proves the token was minted by a Graphorin\n * helper.\n *\n * @stable\n */\nexport function runTokenVerify(options: TokenVerifyOptions): TokenVerifyResult {\n const result = verifyOffline(options.token, {\n ...(options.prefix !== undefined ? { acceptPrefix: options.prefix } : {}),\n });\n const out: TokenVerifyResult = Object.freeze({\n ok: result.ok,\n ...(result.ok ? {} : { reason: result.reason }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.ok) {\n print(brand(`token format ${statusMarker('ok')} (offline checksum verified)`));\n return;\n }\n print(brand(`token format ${statusMarker('fail')} (${out.reason ?? 'unknown reason'})`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n}\n\n/**\n * Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns\n * milliseconds. Throws on invalid input — surfaced as a fail-fast at\n * the CLI boundary.\n *\n * @internal\n */\nexport function parseDuration(input: string): number {\n const match = /^(\\d+)\\s*([smhd])$/i.exec(input.trim());\n if (!match) {\n throw new Error(\n `[graphorin/cli] invalid --expires-in '${input}'. Use the form '<number><s|m|h|d>' (e.g. 30d).`,\n );\n }\n const value = Number.parseInt(match[1] as string, 10);\n const unit = (match[2] as string).toLowerCase();\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60_000;\n case 'h':\n return value * 60 * 60_000;\n case 'd':\n return value * ONE_DAY_MS;\n default:\n throw new Error(`[graphorin/cli] invalid --expires-in unit '${unit}'.`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { CommonOutputOptions } from "../internal/output.js";
|
|
2
|
+
import { LintFinding, LintFindingKind } from "@graphorin/eslint-plugin";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/tools-lint.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Counter event emitted per below-threshold tool per invocation. The
|
|
8
|
+
* CLI exposes a configurable sink so observability pipelines (Phase
|
|
9
|
+
* 04) can wire the counter into Prometheus / OTLP without touching
|
|
10
|
+
* the CLI runtime. Default: no-op.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors the `tool.lint.threshold.violations.total{toolName,score,
|
|
13
|
+
* threshold}` contract documented in RB-49 § Counter emission.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
interface ToolsLintThresholdViolation {
|
|
18
|
+
readonly toolName: string;
|
|
19
|
+
readonly score: number;
|
|
20
|
+
readonly threshold: number;
|
|
21
|
+
}
|
|
22
|
+
/** @stable */
|
|
23
|
+
type ToolsLintCounterSink = (event: ToolsLintThresholdViolation) => void;
|
|
24
|
+
/** @stable */
|
|
25
|
+
interface ToolsLintOptions extends CommonOutputOptions {
|
|
26
|
+
/** Optional path to a `tsconfig.json` whose `include` overrides the file glob. */
|
|
27
|
+
readonly config?: string;
|
|
28
|
+
/** Minimum acceptable per-tool score. Default `60`. */
|
|
29
|
+
readonly threshold?: number;
|
|
30
|
+
/** Output format. Default `'text'`. */
|
|
31
|
+
readonly format?: 'text' | 'json';
|
|
32
|
+
/** Optional override of the file glob pattern. */
|
|
33
|
+
readonly source?: string;
|
|
34
|
+
/** Override `cwd`. Default `process.cwd()`. */
|
|
35
|
+
readonly cwd?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Test seam — supply a list of `(file, source)` pairs directly so
|
|
38
|
+
* the test does not need to fish around the filesystem.
|
|
39
|
+
*/
|
|
40
|
+
readonly inlineSources?: ReadonlyArray<{
|
|
41
|
+
readonly file: string;
|
|
42
|
+
readonly source: string;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Optional sink for the `tool.lint.threshold.violations.total`
|
|
46
|
+
* counter (RB-49). The CLI calls this once per below-threshold tool
|
|
47
|
+
* per invocation. Default: no-op.
|
|
48
|
+
*/
|
|
49
|
+
readonly counterSink?: ToolsLintCounterSink;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Documented JSON shape emitted on `--format json`. The shape is
|
|
53
|
+
* stable + forward-compatible with the `@eslint/mcp` industry
|
|
54
|
+
* direction (per-finding `rule` / `severity` / `message` / `location`
|
|
55
|
+
* mirror the ESLint LSP convention).
|
|
56
|
+
*
|
|
57
|
+
* @stable
|
|
58
|
+
*/
|
|
59
|
+
interface ToolsLintReport {
|
|
60
|
+
readonly summary: {
|
|
61
|
+
readonly totalTools: number;
|
|
62
|
+
readonly totalFindings: number;
|
|
63
|
+
readonly threshold: number;
|
|
64
|
+
readonly passed: number;
|
|
65
|
+
readonly failed: number;
|
|
66
|
+
};
|
|
67
|
+
readonly tools: ReadonlyArray<ToolsLintReportTool>;
|
|
68
|
+
}
|
|
69
|
+
/** @stable */
|
|
70
|
+
interface ToolsLintReportTool {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly source: string;
|
|
73
|
+
readonly score: number;
|
|
74
|
+
readonly axes: {
|
|
75
|
+
readonly description: number;
|
|
76
|
+
readonly examples: number;
|
|
77
|
+
readonly parameterNaming: number;
|
|
78
|
+
};
|
|
79
|
+
readonly findings: ReadonlyArray<ToolsLintReportFinding>;
|
|
80
|
+
}
|
|
81
|
+
/** @stable */
|
|
82
|
+
interface ToolsLintReportFinding {
|
|
83
|
+
readonly rule: LintFinding['rule'];
|
|
84
|
+
readonly kind: LintFindingKind;
|
|
85
|
+
readonly severity: LintFinding['severity'];
|
|
86
|
+
readonly message: string;
|
|
87
|
+
readonly location: {
|
|
88
|
+
readonly file: string;
|
|
89
|
+
readonly line: number;
|
|
90
|
+
};
|
|
91
|
+
readonly hint?: string;
|
|
92
|
+
readonly matchedPattern?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Run the discovery + grader pipeline. Returns the structured report
|
|
96
|
+
* the CLI emits to stdout.
|
|
97
|
+
*
|
|
98
|
+
* @stable
|
|
99
|
+
*/
|
|
100
|
+
declare function runToolsLint(options?: ToolsLintOptions): Promise<ToolsLintReport>;
|
|
101
|
+
//#endregion
|
|
102
|
+
export { ToolsLintOptions, ToolsLintReport, runToolsLint };
|
|
103
|
+
//# sourceMappingURL=tools-lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools-lint.d.ts","names":[],"sources":["../../src/commands/tools-lint.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;UA0EiB,2BAAA;;;;;;KAOL,oBAAA,WAA+B;;UAG1B,gBAAA,SAAyB;;;;;;;;;;;;;;;2BAef;;;;;;;;;yBAMF;;;;;;;;;;UAWR,eAAA;;;;;;;;kBAQC,cAAc;;;UAIf,mBAAA;;;;;;;;;qBASI,cAAc;;;UAIlB,sBAAA;iBACA;iBACA;qBACI;;;;;;;;;;;;;;;iBAgBC,YAAA,WAAsB,mBAAwB,QAAQ"}
|