@doubledigit/cli 0.11.1 → 0.13.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/README.md +23 -0
- package/dist/commands/actions.d.ts.map +1 -1
- package/dist/commands/actions.js +2 -0
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +467 -0
- package/dist/commands/component-hub-init.d.ts +1 -0
- package/dist/commands/component-hub-init.d.ts.map +1 -1
- package/dist/commands/component-hub-init.js +22 -0
- package/dist/generated/defaults.d.ts +1 -1
- package/dist/generated/defaults.d.ts.map +1 -1
- package/dist/generated/defaults.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -0
- package/dist/lib/actions-client.d.ts +6 -1
- package/dist/lib/actions-client.d.ts.map +1 -1
- package/dist/lib/actions-client.js +25 -3
- package/dist/lib/auth-credential.d.ts +8 -0
- package/dist/lib/auth-credential.d.ts.map +1 -0
- package/dist/lib/auth-credential.js +25 -0
- package/dist/lib/auth-store.d.ts +33 -0
- package/dist/lib/auth-store.d.ts.map +1 -0
- package/dist/lib/auth-store.js +89 -0
- package/dist/lib/component-frameworks.d.ts.map +1 -1
- package/dist/lib/component-frameworks.js +22 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,28 @@ npx @doubledigit/cli@latest actions component-hub register-component --json-file
|
|
|
126
126
|
|
|
127
127
|
Component Hub uses the canonical `component-hub` action command. `init` and `add` are handled locally by the CLI because they create or modify files on the caller's machine. Other actions, such as search and registry lookup, call the configured Double Digit app over HTTP. The old `remotion-hub` action name remains a legacy alias.
|
|
128
128
|
|
|
129
|
+
### Authentication
|
|
130
|
+
|
|
131
|
+
Public catalog actions can still run without credentials. Project-scoped actions and private deployments require either an interactive login or an API key:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx @doubledigit/cli@latest login --url https://your-double-digit-app.example
|
|
135
|
+
npx @doubledigit/cli@latest whoami --url https://your-double-digit-app.example
|
|
136
|
+
npx @doubledigit/cli@latest org list --url https://your-double-digit-app.example
|
|
137
|
+
npx @doubledigit/cli@latest org use acme --url https://your-double-digit-app.example
|
|
138
|
+
npx @doubledigit/cli@latest actions component-hub list-projects --org acme --url https://your-double-digit-app.example
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`dd login` stores a per-host bearer credential under the user config directory (`~/.config/doubledigit/credentials.json` on Linux/macOS, `%APPDATA%\doubledigit\credentials.json` on Windows) with private file permissions where supported. `dd logout` removes the stored host credential.
|
|
142
|
+
|
|
143
|
+
CI and agent automation should use an API key created from the app admin security dashboard at `/admin/security/api-keys`:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
DD_API_KEY=dd_... DD_ORG=acme npx @doubledigit/cli@latest actions component-hub list-projects --url https://your-double-digit-app.example
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Auth header resolution is `DD_API_KEY` or `DD_TOKEN` first, then the stored login token. Active organization resolution is `--org`, then `DD_ORG`, then the stored default selected by `dd org use`.
|
|
150
|
+
|
|
129
151
|
Pass `--framework remotion|hyperframe` when the target framework is known. Remotion remains the default for compatibility and creates Remotion's Hello World starter so `remotion studio` opens with a visible composition. HyperFrames init requires Node.js 22 or newer and uses `npx hyperframes preview` as its dev command.
|
|
130
152
|
|
|
131
153
|
Pass `--skip-framework-create` when adding Component Hub files to an existing project. `--skip-remotion-create` and `--hub-only` remain compatibility aliases.
|
|
@@ -139,6 +161,7 @@ npx skills add crystalphantom/double-digit --skill dd-component-hub --agent '*'
|
|
|
139
161
|
```
|
|
140
162
|
|
|
141
163
|
Pass `--skip-skills` to skip skill installation. If skill installation fails, init still completes the project scaffold and prints exact retry commands. Use `--yes` for agent-run or scripted init flows; `-y` and `--non-interactive` are accepted compatibility aliases and are forwarded to nested framework scaffolds where supported.
|
|
164
|
+
For HyperFrames, non-interactive init accepts the framework skill defaults by installing `animejs` for all agents without prompting.
|
|
142
165
|
|
|
143
166
|
Register a component with simple JSON:
|
|
144
167
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/commands/actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/commands/actions.ts"],"names":[],"mappings":"AAwCA,wBAAgB,uBAAuB,WAKtC;AAuED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE;WACf,MAAM;WAAS,MAAM;IAuBxD;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAGtD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,WAGrD;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,EAAE,YAI9D;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,YAI7D;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuC3D"}
|
package/dist/commands/actions.js
CHANGED
|
@@ -16,6 +16,7 @@ const actionValueOptions = new Set([
|
|
|
16
16
|
'--kind',
|
|
17
17
|
'--limit',
|
|
18
18
|
'--namespace',
|
|
19
|
+
'--org',
|
|
19
20
|
'--preview-type',
|
|
20
21
|
'--query',
|
|
21
22
|
'--json-file',
|
|
@@ -41,6 +42,7 @@ Usage:
|
|
|
41
42
|
|
|
42
43
|
Options:
|
|
43
44
|
--url, --app-url <url> App URL (default: DD_APP_URL, APP_URL, BETTER_AUTH_URL, or ${getDefaultActionsAppUrl()})
|
|
45
|
+
--org <slug> Active organization slug (default: --org, DD_ORG, or stored login default)
|
|
44
46
|
--json <json> JSON request body to send to the action
|
|
45
47
|
--json-file <path> Read the JSON request body from a file
|
|
46
48
|
--query <text> Set input.query
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AuthStorePathsOptions } from '../lib/auth-store.js';
|
|
2
|
+
type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export interface AuthCommandOptions extends AuthStorePathsOptions {
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
fetchImpl?: FetchLike;
|
|
6
|
+
sleep?: (ms: number) => Promise<void>;
|
|
7
|
+
log?: (...parts: unknown[]) => void;
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export declare function login(args: string[], options?: AuthCommandOptions): Promise<void>;
|
|
11
|
+
export declare function logout(args: string[], options?: AuthCommandOptions): Promise<void>;
|
|
12
|
+
export declare function whoami(args: string[], options?: AuthCommandOptions): Promise<void>;
|
|
13
|
+
export declare function org(args: string[], options?: AuthCommandOptions): Promise<void>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AAK9B,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEhF,MAAM,WAAW,kBAAmB,SAAQ,qBAAqB;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAyZD,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwD3F;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc5F;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC5F;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EzF"}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { GENERATED_DEFAULT_ACTIONS_APP_URL } from '../generated/defaults.js';
|
|
3
|
+
import { normalizeAppUrl, resolveActionAppUrl, resolveDefaultActionAppUrl, } from '../lib/actions-client.js';
|
|
4
|
+
import { resolveAuthHeaders } from '../lib/auth-credential.js';
|
|
5
|
+
import { clearStoredCredential, readStoredCredential, writeStoredCredential, } from '../lib/auth-store.js';
|
|
6
|
+
import { DEFAULT_APP_URL, readEnvFile } from '../lib/onboarding.js';
|
|
7
|
+
import { resolveWorkspacePaths } from '../paths.js';
|
|
8
|
+
const CLIENT_ID = 'dd-cli';
|
|
9
|
+
const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
10
|
+
const HELP = `
|
|
11
|
+
dd auth — Authenticate the Double Digit CLI
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
dd login [--url <url>]
|
|
15
|
+
dd logout [--url <url>]
|
|
16
|
+
dd whoami [--url <url>] [--json]
|
|
17
|
+
dd org list [--url <url>] [--json]
|
|
18
|
+
dd org use <slug> [--url <url>]
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--url, --app-url <url> App URL (default: DD_APP_URL, APP_URL, BETTER_AUTH_URL, or ${getDefaultAuthAppUrl()})
|
|
22
|
+
--json Print machine-readable output where supported
|
|
23
|
+
`;
|
|
24
|
+
function getDefaultAuthAppUrl() {
|
|
25
|
+
return resolveDefaultActionAppUrl({
|
|
26
|
+
generatedUrl: GENERATED_DEFAULT_ACTIONS_APP_URL,
|
|
27
|
+
localDefaultUrl: DEFAULT_APP_URL,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function readAuthEnvFiles(paths) {
|
|
31
|
+
const env = {};
|
|
32
|
+
for (const envPath of [
|
|
33
|
+
path.join(paths.root, '.env'),
|
|
34
|
+
path.join(paths.mainAppDir, '.env'),
|
|
35
|
+
path.join(paths.root, '.env.local'),
|
|
36
|
+
path.join(paths.mainAppDir, '.env.local'),
|
|
37
|
+
]) {
|
|
38
|
+
Object.assign(env, readEnvFile(envPath));
|
|
39
|
+
}
|
|
40
|
+
return env;
|
|
41
|
+
}
|
|
42
|
+
function tryResolveWorkspacePaths() {
|
|
43
|
+
try {
|
|
44
|
+
return resolveWorkspacePaths();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function parseAuthArgs(args) {
|
|
51
|
+
const positionals = [];
|
|
52
|
+
let appUrl;
|
|
53
|
+
let json = false;
|
|
54
|
+
let help = false;
|
|
55
|
+
for (let i = 0; i < args.length; i++) {
|
|
56
|
+
const arg = args[i];
|
|
57
|
+
if (arg === '--help' || arg === '-h') {
|
|
58
|
+
help = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (arg === '--json') {
|
|
62
|
+
json = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!arg.startsWith('--')) {
|
|
66
|
+
positionals.push(arg);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const [flag, inlineValue] = arg.split(/=(.*)/s, 2);
|
|
70
|
+
if (flag !== '--url' && flag !== '--app-url') {
|
|
71
|
+
throw new Error(`Unknown option: ${flag}`);
|
|
72
|
+
}
|
|
73
|
+
if (inlineValue !== undefined) {
|
|
74
|
+
appUrl = inlineValue;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const value = args[i + 1];
|
|
78
|
+
if (!value || value.startsWith('--')) {
|
|
79
|
+
throw new Error(`${flag} requires a value`);
|
|
80
|
+
}
|
|
81
|
+
appUrl = value;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
return { appUrl, json, help, positionals };
|
|
85
|
+
}
|
|
86
|
+
function resolveAuthAppUrl(parsed) {
|
|
87
|
+
const paths = tryResolveWorkspacePaths();
|
|
88
|
+
return normalizeAppUrl(resolveActionAppUrl({
|
|
89
|
+
explicitUrl: parsed.appUrl,
|
|
90
|
+
fileEnv: paths ? readAuthEnvFiles(paths) : {},
|
|
91
|
+
defaultUrl: getDefaultAuthAppUrl(),
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
async function parseJsonResponse(response, url) {
|
|
95
|
+
const text = await response.text();
|
|
96
|
+
if (!text)
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(text);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
throw new Error(`Request to ${url} failed with HTTP ${response.status}`);
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Expected JSON response from ${url}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function objectPayload(payload) {
|
|
109
|
+
return payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
110
|
+
? payload
|
|
111
|
+
: {};
|
|
112
|
+
}
|
|
113
|
+
function readString(payload, ...keys) {
|
|
114
|
+
for (const key of keys) {
|
|
115
|
+
const value = payload[key];
|
|
116
|
+
if (typeof value === 'string' && value.trim())
|
|
117
|
+
return value;
|
|
118
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function readNumber(payload, fallback, ...keys) {
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
const value = payload[key];
|
|
126
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
return fallback;
|
|
130
|
+
}
|
|
131
|
+
function getErrorCode(payload) {
|
|
132
|
+
const object = objectPayload(payload);
|
|
133
|
+
return readString(object, 'error', 'code') ?? 'request_failed';
|
|
134
|
+
}
|
|
135
|
+
function getErrorMessage(payload, fallback) {
|
|
136
|
+
const object = objectPayload(payload);
|
|
137
|
+
return readString(object, 'error_description', 'message', 'error') ?? fallback;
|
|
138
|
+
}
|
|
139
|
+
async function fetchJson(fetchImpl, url, init) {
|
|
140
|
+
let response;
|
|
141
|
+
try {
|
|
142
|
+
response = await fetchImpl(url, init);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
146
|
+
throw new Error(`Unable to reach ${url}: ${detail}. Set DD_APP_URL or pass --url.`);
|
|
147
|
+
}
|
|
148
|
+
return { response, payload: await parseJsonResponse(response, url) };
|
|
149
|
+
}
|
|
150
|
+
function normalizeDeviceCode(payload) {
|
|
151
|
+
const object = objectPayload(payload);
|
|
152
|
+
const deviceCode = readString(object, 'device_code', 'deviceCode');
|
|
153
|
+
const userCode = readString(object, 'user_code', 'userCode');
|
|
154
|
+
const verificationUri = readString(object, 'verification_uri', 'verificationUri');
|
|
155
|
+
if (!deviceCode || !userCode || !verificationUri) {
|
|
156
|
+
throw new Error('Device authorization response did not include a device code, user code, and verification URL.');
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
deviceCode,
|
|
160
|
+
userCode,
|
|
161
|
+
verificationUri,
|
|
162
|
+
verificationUriComplete: readString(object, 'verification_uri_complete', 'verificationUriComplete'),
|
|
163
|
+
expiresIn: readNumber(object, 600, 'expires_in', 'expiresIn'),
|
|
164
|
+
interval: readNumber(object, 5, 'interval'),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function normalizeTokenSuccess(payload) {
|
|
168
|
+
const object = objectPayload(payload);
|
|
169
|
+
const accessToken = readString(object, 'access_token', 'accessToken');
|
|
170
|
+
if (!accessToken) {
|
|
171
|
+
throw new Error('Device authorization token response did not include an access token.');
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
accessToken,
|
|
175
|
+
expiresIn: readNumber(object, 0, 'expires_in', 'expiresIn') || undefined,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function normalizeSessionIdentity(payload) {
|
|
179
|
+
const object = objectPayload(payload);
|
|
180
|
+
const userPayload = objectPayload(object.user);
|
|
181
|
+
const sessionPayload = objectPayload(object.session);
|
|
182
|
+
const user = {};
|
|
183
|
+
const id = readString(userPayload, 'id');
|
|
184
|
+
const email = readString(userPayload, 'email');
|
|
185
|
+
if (id)
|
|
186
|
+
user.id = id;
|
|
187
|
+
if (email)
|
|
188
|
+
user.email = email;
|
|
189
|
+
const activeOrganizationId = readString(sessionPayload, 'activeOrganizationId');
|
|
190
|
+
return {
|
|
191
|
+
...(Object.keys(user).length > 0 ? { user } : {}),
|
|
192
|
+
session: activeOrganizationId ? { activeOrganizationId } : undefined,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function normalizeOrganizations(payload) {
|
|
196
|
+
const source = Array.isArray(payload)
|
|
197
|
+
? payload
|
|
198
|
+
: Array.isArray(objectPayload(payload).organizations)
|
|
199
|
+
? objectPayload(payload).organizations
|
|
200
|
+
: [];
|
|
201
|
+
return source.map((entry) => {
|
|
202
|
+
const object = objectPayload(entry);
|
|
203
|
+
return {
|
|
204
|
+
id: readString(object, 'id'),
|
|
205
|
+
slug: readString(object, 'slug'),
|
|
206
|
+
name: readString(object, 'name'),
|
|
207
|
+
};
|
|
208
|
+
}).filter((organization) => organization.id || organization.slug || organization.name);
|
|
209
|
+
}
|
|
210
|
+
async function fetchSession(appUrl, headers, fetchImpl) {
|
|
211
|
+
const url = `${appUrl}/api/auth/get-session`;
|
|
212
|
+
const { response, payload } = await fetchJson(fetchImpl, url, {
|
|
213
|
+
method: 'GET',
|
|
214
|
+
headers,
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok || !payload) {
|
|
217
|
+
throw new Error(getErrorMessage(payload, 'Not signed in.'));
|
|
218
|
+
}
|
|
219
|
+
const identity = normalizeSessionIdentity(payload);
|
|
220
|
+
if (!identity.user?.id && !identity.user?.email) {
|
|
221
|
+
throw new Error('Not signed in.');
|
|
222
|
+
}
|
|
223
|
+
return identity;
|
|
224
|
+
}
|
|
225
|
+
async function fetchOrganizations(appUrl, headers, fetchImpl) {
|
|
226
|
+
const url = `${appUrl}/api/auth/organization/list`;
|
|
227
|
+
const { response, payload } = await fetchJson(fetchImpl, url, {
|
|
228
|
+
method: 'GET',
|
|
229
|
+
headers,
|
|
230
|
+
});
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(getErrorMessage(payload, 'Unable to list organizations.'));
|
|
233
|
+
}
|
|
234
|
+
return normalizeOrganizations(payload);
|
|
235
|
+
}
|
|
236
|
+
async function resolveLoginDefaultOrg({ existingDefaultOrg, activeOrganizationId, appUrl, headers, fetchImpl, }) {
|
|
237
|
+
if (existingDefaultOrg) {
|
|
238
|
+
return existingDefaultOrg;
|
|
239
|
+
}
|
|
240
|
+
if (!activeOrganizationId) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const organizations = await fetchOrganizations(appUrl, headers, fetchImpl);
|
|
245
|
+
const match = organizations.find((organization) => organization.id === activeOrganizationId);
|
|
246
|
+
return match?.slug ?? match?.id ?? activeOrganizationId;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return activeOrganizationId;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function requestDeviceCode(appUrl, fetchImpl) {
|
|
253
|
+
const url = `${appUrl}/api/auth/device/code`;
|
|
254
|
+
const { response, payload } = await fetchJson(fetchImpl, url, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers: { 'Content-Type': 'application/json' },
|
|
257
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
throw new Error(getErrorMessage(payload, 'Unable to start device authorization.'));
|
|
261
|
+
}
|
|
262
|
+
return normalizeDeviceCode(payload);
|
|
263
|
+
}
|
|
264
|
+
async function pollDeviceToken({ appUrl, deviceCode, expiresIn, interval, fetchImpl, sleep, now, }) {
|
|
265
|
+
const url = `${appUrl}/api/auth/device/token`;
|
|
266
|
+
const deadline = now() + expiresIn * 1000;
|
|
267
|
+
let intervalMs = Math.max(1, interval) * 1000;
|
|
268
|
+
while (now() < deadline) {
|
|
269
|
+
const { response, payload } = await fetchJson(fetchImpl, url, {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
headers: { 'Content-Type': 'application/json' },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
grant_type: DEVICE_GRANT_TYPE,
|
|
274
|
+
device_code: deviceCode,
|
|
275
|
+
client_id: CLIENT_ID,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
if (response.ok) {
|
|
279
|
+
return normalizeTokenSuccess(payload);
|
|
280
|
+
}
|
|
281
|
+
const code = getErrorCode(payload);
|
|
282
|
+
if (code === 'authorization_pending') {
|
|
283
|
+
await sleep(intervalMs);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (code === 'slow_down') {
|
|
287
|
+
intervalMs += 5000;
|
|
288
|
+
await sleep(intervalMs);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (code === 'expired_token') {
|
|
292
|
+
throw new Error('Device authorization expired. Run `dd login` again.');
|
|
293
|
+
}
|
|
294
|
+
if (code === 'access_denied') {
|
|
295
|
+
throw new Error('Device authorization was denied.');
|
|
296
|
+
}
|
|
297
|
+
throw new Error(getErrorMessage(payload, 'Device authorization failed.'));
|
|
298
|
+
}
|
|
299
|
+
throw new Error('Device authorization expired. Run `dd login` again.');
|
|
300
|
+
}
|
|
301
|
+
export async function login(args, options = {}) {
|
|
302
|
+
const parsed = parseAuthArgs(args);
|
|
303
|
+
const log = options.log ?? console.log;
|
|
304
|
+
if (parsed.help) {
|
|
305
|
+
log(HELP);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (parsed.positionals.length > 0) {
|
|
309
|
+
throw new Error(`Unexpected positional argument: ${parsed.positionals[0]}`);
|
|
310
|
+
}
|
|
311
|
+
const appUrl = resolveAuthAppUrl(parsed);
|
|
312
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
313
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
314
|
+
const now = options.now ?? Date.now;
|
|
315
|
+
const code = await requestDeviceCode(appUrl, fetchImpl);
|
|
316
|
+
log(`Visit: ${code.verificationUriComplete ?? code.verificationUri}`);
|
|
317
|
+
log(`Code: ${code.userCode}`);
|
|
318
|
+
log('Waiting for approval...');
|
|
319
|
+
const token = await pollDeviceToken({
|
|
320
|
+
appUrl,
|
|
321
|
+
deviceCode: code.deviceCode,
|
|
322
|
+
expiresIn: code.expiresIn,
|
|
323
|
+
interval: code.interval,
|
|
324
|
+
fetchImpl,
|
|
325
|
+
sleep,
|
|
326
|
+
now,
|
|
327
|
+
});
|
|
328
|
+
const existing = readStoredCredential({ appUrl, env: options.env, platform: options.platform, homedir: options.homedir });
|
|
329
|
+
const authHeaders = { Authorization: `Bearer ${token.accessToken}` };
|
|
330
|
+
const identity = await fetchSession(appUrl, authHeaders, fetchImpl);
|
|
331
|
+
const defaultOrg = await resolveLoginDefaultOrg({
|
|
332
|
+
existingDefaultOrg: existing?.defaultOrg,
|
|
333
|
+
activeOrganizationId: identity.session?.activeOrganizationId,
|
|
334
|
+
appUrl,
|
|
335
|
+
headers: authHeaders,
|
|
336
|
+
fetchImpl,
|
|
337
|
+
});
|
|
338
|
+
writeStoredCredential({
|
|
339
|
+
appUrl,
|
|
340
|
+
env: options.env,
|
|
341
|
+
platform: options.platform,
|
|
342
|
+
homedir: options.homedir,
|
|
343
|
+
credential: {
|
|
344
|
+
token: token.accessToken,
|
|
345
|
+
...(defaultOrg ? { defaultOrg } : {}),
|
|
346
|
+
...(identity.user ? { user: identity.user } : {}),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
const label = identity.user?.email ?? identity.user?.id ?? 'current user';
|
|
350
|
+
log(`Signed in to ${appUrl} as ${label}.`);
|
|
351
|
+
}
|
|
352
|
+
export async function logout(args, options = {}) {
|
|
353
|
+
const parsed = parseAuthArgs(args);
|
|
354
|
+
const log = options.log ?? console.log;
|
|
355
|
+
if (parsed.help) {
|
|
356
|
+
log(HELP);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (parsed.positionals.length > 0) {
|
|
360
|
+
throw new Error(`Unexpected positional argument: ${parsed.positionals[0]}`);
|
|
361
|
+
}
|
|
362
|
+
const appUrl = resolveAuthAppUrl(parsed);
|
|
363
|
+
clearStoredCredential({ appUrl, env: options.env, platform: options.platform, homedir: options.homedir });
|
|
364
|
+
log(`Signed out of ${appUrl}.`);
|
|
365
|
+
}
|
|
366
|
+
export async function whoami(args, options = {}) {
|
|
367
|
+
const parsed = parseAuthArgs(args);
|
|
368
|
+
const log = options.log ?? console.log;
|
|
369
|
+
if (parsed.help) {
|
|
370
|
+
log(HELP);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (parsed.positionals.length > 0) {
|
|
374
|
+
throw new Error(`Unexpected positional argument: ${parsed.positionals[0]}`);
|
|
375
|
+
}
|
|
376
|
+
const appUrl = resolveAuthAppUrl(parsed);
|
|
377
|
+
const headers = resolveAuthHeaders({
|
|
378
|
+
appUrl,
|
|
379
|
+
env: options.env,
|
|
380
|
+
platform: options.platform,
|
|
381
|
+
homedir: options.homedir,
|
|
382
|
+
});
|
|
383
|
+
if (!headers.Authorization && !headers['x-api-key']) {
|
|
384
|
+
throw new Error('Not signed in. Run `dd login` or set DD_API_KEY.');
|
|
385
|
+
}
|
|
386
|
+
const identity = await fetchSession(appUrl, headers, options.fetchImpl ?? fetch);
|
|
387
|
+
if (parsed.json) {
|
|
388
|
+
log(JSON.stringify({ appUrl, ...identity }, null, 2));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const stored = readStoredCredential({ appUrl, env: options.env, platform: options.platform, homedir: options.homedir });
|
|
392
|
+
log(`Signed in to ${appUrl}`);
|
|
393
|
+
log(`User: ${identity.user?.email ?? identity.user?.id ?? 'unknown'}`);
|
|
394
|
+
if (stored?.defaultOrg) {
|
|
395
|
+
log(`Default organization: ${stored.defaultOrg}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
export async function org(args, options = {}) {
|
|
399
|
+
const parsed = parseAuthArgs(args);
|
|
400
|
+
const log = options.log ?? console.log;
|
|
401
|
+
const subcommand = parsed.positionals[0] ?? 'list';
|
|
402
|
+
if (parsed.help) {
|
|
403
|
+
log(HELP);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const appUrl = resolveAuthAppUrl(parsed);
|
|
407
|
+
const headers = resolveAuthHeaders({
|
|
408
|
+
appUrl,
|
|
409
|
+
env: options.env,
|
|
410
|
+
platform: options.platform,
|
|
411
|
+
homedir: options.homedir,
|
|
412
|
+
});
|
|
413
|
+
if (!headers.Authorization && !headers['x-api-key']) {
|
|
414
|
+
throw new Error('Not signed in. Run `dd login` or set DD_API_KEY.');
|
|
415
|
+
}
|
|
416
|
+
if (subcommand === 'list') {
|
|
417
|
+
if (parsed.positionals.length > 1) {
|
|
418
|
+
throw new Error(`Unexpected positional argument: ${parsed.positionals[1]}`);
|
|
419
|
+
}
|
|
420
|
+
const organizations = await fetchOrganizations(appUrl, headers, options.fetchImpl ?? fetch);
|
|
421
|
+
if (parsed.json) {
|
|
422
|
+
log(JSON.stringify({ appUrl, organizations }, null, 2));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (organizations.length === 0) {
|
|
426
|
+
log('No organizations found.');
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
for (const organization of organizations) {
|
|
430
|
+
const slug = organization.slug ? ` (${organization.slug})` : '';
|
|
431
|
+
const id = organization.id ? ` [${organization.id}]` : '';
|
|
432
|
+
log(`${organization.name ?? organization.slug ?? organization.id}${slug}${id}`);
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (subcommand === 'use') {
|
|
437
|
+
const slug = parsed.positionals[1];
|
|
438
|
+
if (!slug) {
|
|
439
|
+
throw new Error('Usage: dd org use <slug>');
|
|
440
|
+
}
|
|
441
|
+
if (parsed.positionals.length > 2) {
|
|
442
|
+
throw new Error(`Unexpected positional argument: ${parsed.positionals[2]}`);
|
|
443
|
+
}
|
|
444
|
+
const stored = readStoredCredential({ appUrl, env: options.env, platform: options.platform, homedir: options.homedir });
|
|
445
|
+
if (!stored?.token) {
|
|
446
|
+
throw new Error('No stored login found. Run `dd login` before saving a default organization.');
|
|
447
|
+
}
|
|
448
|
+
const organizations = await fetchOrganizations(appUrl, headers, options.fetchImpl ?? fetch);
|
|
449
|
+
const match = organizations.find((organization) => organization.slug === slug || organization.id === slug);
|
|
450
|
+
if (!match) {
|
|
451
|
+
throw new Error(`Organization not found or not available to this user: ${slug}`);
|
|
452
|
+
}
|
|
453
|
+
writeStoredCredential({
|
|
454
|
+
appUrl,
|
|
455
|
+
env: options.env,
|
|
456
|
+
platform: options.platform,
|
|
457
|
+
homedir: options.homedir,
|
|
458
|
+
credential: {
|
|
459
|
+
...stored,
|
|
460
|
+
defaultOrg: match.slug ?? match.id ?? slug,
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
log(`Default organization set to ${match.slug ?? match.id ?? slug}.`);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
throw new Error(`Unknown org subcommand: ${subcommand}`);
|
|
467
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RunComponentHubInitResult } from '../lib/component-hub-init.js';
|
|
2
2
|
export declare function buildComponentHubInitHelp(): string;
|
|
3
|
+
export declare function buildComponentHubInitNextSteps(result: RunComponentHubInitResult): string[];
|
|
3
4
|
export declare function runComponentHubInitCommand(args: string[]): RunComponentHubInitResult | undefined;
|
|
4
5
|
//# sourceMappingURL=component-hub-init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-hub-init.d.ts","sourceRoot":"","sources":["../../src/commands/component-hub-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAuFnG,wBAAgB,yBAAyB,WAgCxC;
|
|
1
|
+
{"version":3,"file":"component-hub-init.d.ts","sourceRoot":"","sources":["../../src/commands/component-hub-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAuFnG,wBAAgB,yBAAyB,WAgCxC;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,EAAE,CAyB1F;AAuCD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,yBAAyB,GAAG,SAAS,CA8BhG"}
|
|
@@ -102,6 +102,24 @@ Examples:
|
|
|
102
102
|
npx @doubledigit/cli@latest actions component-hub init my-video --registry https://double-digit.example
|
|
103
103
|
`;
|
|
104
104
|
}
|
|
105
|
+
export function buildComponentHubInitNextSteps(result) {
|
|
106
|
+
const ddComponentHubSkillInstalled = result.skills.commands.some((command) => command.success && command.args.includes('dd-component-hub'));
|
|
107
|
+
const lines = [
|
|
108
|
+
'Next steps:',
|
|
109
|
+
` 1. cd ${result.projectDir}`,
|
|
110
|
+
];
|
|
111
|
+
if (result.skills.skipped) {
|
|
112
|
+
lines.push(' 2. Install the project skills when you want agent guidance, or rerun init without --skip-skills.');
|
|
113
|
+
}
|
|
114
|
+
else if (ddComponentHubSkillInstalled) {
|
|
115
|
+
lines.push(' 2. Start your AI coding agent from this directory so it loads .agents/skills/dd-component-hub.');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
lines.push(' 2. Retry the failed skill installs above, then start your AI coding agent from this directory.');
|
|
119
|
+
}
|
|
120
|
+
lines.push(' 3. First agent prompt:', ` "Use dd-component-hub. Search Component Hub for a ${result.framework} component before building anything custom."`, ` 4. Preview locally: ${result.devCommand}`);
|
|
121
|
+
return lines;
|
|
122
|
+
}
|
|
105
123
|
function printInit(result) {
|
|
106
124
|
console.log(`Initialized Component Hub in ${result.projectDir}`);
|
|
107
125
|
console.log(`Framework: ${result.framework}`);
|
|
@@ -132,6 +150,10 @@ function printInit(result) {
|
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
152
|
}
|
|
153
|
+
console.log('');
|
|
154
|
+
for (const line of buildComponentHubInitNextSteps(result)) {
|
|
155
|
+
console.log(line);
|
|
156
|
+
}
|
|
135
157
|
}
|
|
136
158
|
export function runComponentHubInitCommand(args) {
|
|
137
159
|
const parsed = parseArgs(args);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const GENERATED_DEFAULT_ACTIONS_APP_URL = "https://
|
|
1
|
+
export declare const GENERATED_DEFAULT_ACTIONS_APP_URL = "https://doubledigit.vercel.app";
|
|
2
2
|
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/generated/defaults.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/generated/defaults.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,mCAAmC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const GENERATED_DEFAULT_ACTIONS_APP_URL = "https://
|
|
1
|
+
export const GENERATED_DEFAULT_ACTIONS_APP_URL = "https://doubledigit.vercel.app";
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* without importing the full extension-management dependency graph first.
|
|
7
7
|
*/
|
|
8
8
|
declare const command: string, args: string[];
|
|
9
|
-
declare const HELP = "\n@doubledigit/cli \u2014 Manage extensions and local setup\n\nCommands:\n doctor Check local prerequisites and project health\n onboard Prepare local development and start the app\n run Start the local app with automatic DB bootstrap\n dev Start DB + app without migrations or seed data\n db <subcommand> Database helpers (status, migrate, create)\n actions <app> [action] Discover and invoke micro-app actions\n create <name> Scaffold a new micro-app from the template\n init <project-name> Scaffold a new Double Digit project\n add|install <source> Install an extension from GitHub or a marketplace\n sync Regenerate micro-apps.ts from dd-apps.config.json\n enable <name> Enable a micro-app (updates config + runs sync)\n disable <name> Disable a micro-app (updates config + runs sync)\n uninstall|remove <name> Completely remove a micro-app\n list List all discovered micro-apps with enabled/disabled status\n info <name> Show detailed info about an installed extension\n outdated Check for outdated marketplace extensions\n reconcile Detect drift between lock file, marketplace, and local files\n marketplace <sub> Manage marketplace registrations (add/list/update/remove)\n browse [marketplace] Browse available extensions in registered marketplaces\n\nOptions:\n --help, -h Show this help message\n\nExamples:\n dd doctor\n dd onboard --yes\n dd onboard # setup + start the dev server\n dd onboard --no-run # setup only\n dd init my-project # scaffold and bootstrap a fresh project\n dd init my-project --run # scaffold + bootstrap + start\n dd init my-project --skip-install --no-git\n dd run\n dd dev\n dd db status\n npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --yes\n npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --skip-skills\n npx @doubledigit/cli@latest actions component-hub search-components --framework remotion --query \"animated chart\"\n dd create invoice-tracker\n dd add gh:owner/repo/extensions/micro-apps/habit-tracker\n dd add habit-tracker@community\n dd info habit-tracker\n dd reconcile\n dd marketplace add digitaldouble/dd-marketplace\n dd browse community\n DD_APPS=tasks,agent-v2 dd sync\n dd sync --profile necto-component-hub\n";
|
|
9
|
+
declare const HELP = "\n@doubledigit/cli \u2014 Manage extensions and local setup\n\nCommands:\n doctor Check local prerequisites and project health\n onboard Prepare local development and start the app\n run Start the local app with automatic DB bootstrap\n dev Start DB + app without migrations or seed data\n db <subcommand> Database helpers (status, migrate, create)\n login Sign in with browser-based device authorization\n logout Remove stored CLI credentials\n whoami Show the signed-in CLI user\n org <subcommand> List or select the default organization\n actions <app> [action] Discover and invoke micro-app actions\n create <name> Scaffold a new micro-app from the template\n init <project-name> Scaffold a new Double Digit project\n add|install <source> Install an extension from GitHub or a marketplace\n sync Regenerate micro-apps.ts from dd-apps.config.json\n enable <name> Enable a micro-app (updates config + runs sync)\n disable <name> Disable a micro-app (updates config + runs sync)\n uninstall|remove <name> Completely remove a micro-app\n list List all discovered micro-apps with enabled/disabled status\n info <name> Show detailed info about an installed extension\n outdated Check for outdated marketplace extensions\n reconcile Detect drift between lock file, marketplace, and local files\n marketplace <sub> Manage marketplace registrations (add/list/update/remove)\n browse [marketplace] Browse available extensions in registered marketplaces\n\nOptions:\n --help, -h Show this help message\n\nExamples:\n dd doctor\n dd onboard --yes\n dd onboard # setup + start the dev server\n dd onboard --no-run # setup only\n dd init my-project # scaffold and bootstrap a fresh project\n dd init my-project --run # scaffold + bootstrap + start\n dd init my-project --skip-install --no-git\n dd run\n dd dev\n dd login --url https://component-hub.example.com\n dd whoami\n dd org list\n dd org use my-org\n dd db status\n npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --yes\n npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --skip-skills\n npx @doubledigit/cli@latest actions component-hub search-components --framework remotion --query \"animated chart\"\n dd create invoice-tracker\n dd add gh:owner/repo/extensions/micro-apps/habit-tracker\n dd add habit-tracker@community\n dd info habit-tracker\n dd reconcile\n dd marketplace add digitaldouble/dd-marketplace\n dd browse community\n DD_APPS=tasks,agent-v2 dd sync\n dd sync --profile necto-component-hub\n";
|
|
10
10
|
declare function requireArg(value: string | undefined, usage: string): string;
|
|
11
11
|
declare function runAddCommand(rawArgs: string[]): Promise<void>;
|
|
12
12
|
declare function main(): Promise<void>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,QAAA,MAAW,OAAO,UAAK,IAAI,UAAgB,CAAC;AAE5C,QAAA,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,QAAA,MAAW,OAAO,UAAK,IAAI,UAAgB,CAAC;AAE5C,QAAA,MAAM,IAAI,o0FA0DT,CAAC;AAEF,iBAAS,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,iBAAe,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB7D;AAED,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA8JnC"}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,10 @@ Commands:
|
|
|
16
16
|
run Start the local app with automatic DB bootstrap
|
|
17
17
|
dev Start DB + app without migrations or seed data
|
|
18
18
|
db <subcommand> Database helpers (status, migrate, create)
|
|
19
|
+
login Sign in with browser-based device authorization
|
|
20
|
+
logout Remove stored CLI credentials
|
|
21
|
+
whoami Show the signed-in CLI user
|
|
22
|
+
org <subcommand> List or select the default organization
|
|
19
23
|
actions <app> [action] Discover and invoke micro-app actions
|
|
20
24
|
create <name> Scaffold a new micro-app from the template
|
|
21
25
|
init <project-name> Scaffold a new Double Digit project
|
|
@@ -44,6 +48,10 @@ Examples:
|
|
|
44
48
|
dd init my-project --skip-install --no-git
|
|
45
49
|
dd run
|
|
46
50
|
dd dev
|
|
51
|
+
dd login --url https://component-hub.example.com
|
|
52
|
+
dd whoami
|
|
53
|
+
dd org list
|
|
54
|
+
dd org use my-org
|
|
47
55
|
dd db status
|
|
48
56
|
npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --yes
|
|
49
57
|
npx @doubledigit/cli@latest actions component-hub init my-video --framework remotion --skip-skills
|
|
@@ -116,6 +124,26 @@ async function main() {
|
|
|
116
124
|
await db(args);
|
|
117
125
|
break;
|
|
118
126
|
}
|
|
127
|
+
case 'login': {
|
|
128
|
+
const { login } = await import('./commands/auth.js');
|
|
129
|
+
await login(args);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'logout': {
|
|
133
|
+
const { logout } = await import('./commands/auth.js');
|
|
134
|
+
await logout(args);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'whoami': {
|
|
138
|
+
const { whoami } = await import('./commands/auth.js');
|
|
139
|
+
await whoami(args);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'org': {
|
|
143
|
+
const { org } = await import('./commands/auth.js');
|
|
144
|
+
await org(args);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
119
147
|
case 'actions':
|
|
120
148
|
case 'action': {
|
|
121
149
|
const { actions } = await import('./commands/actions.js');
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type ResolveAuthHeadersOptions } from './auth-credential.js';
|
|
1
2
|
export interface ParsedActionsArgs {
|
|
2
3
|
microApp?: string;
|
|
3
4
|
actionName?: string;
|
|
4
5
|
extraPositionals: string[];
|
|
5
6
|
appUrl?: string;
|
|
7
|
+
org?: string;
|
|
6
8
|
input: Record<string, unknown>;
|
|
7
9
|
help: boolean;
|
|
8
10
|
}
|
|
@@ -10,6 +12,9 @@ export interface ActionRequest {
|
|
|
10
12
|
url: string;
|
|
11
13
|
init: RequestInit;
|
|
12
14
|
}
|
|
15
|
+
export interface BuildActionRequestOptions extends Pick<ResolveAuthHeadersOptions, 'env' | 'platform' | 'homedir'> {
|
|
16
|
+
org?: string;
|
|
17
|
+
}
|
|
13
18
|
export interface ResolveActionAppUrlOptions {
|
|
14
19
|
explicitUrl?: string;
|
|
15
20
|
env?: Record<string, string | undefined>;
|
|
@@ -26,6 +31,6 @@ export declare function resolveActionAppUrl({ explicitUrl, env, fileEnv, default
|
|
|
26
31
|
export declare function parseActionsArgs(args: string[]): ParsedActionsArgs;
|
|
27
32
|
export declare function normalizeAppUrl(appUrl: string): string;
|
|
28
33
|
export declare function isLocalAppUrl(appUrl: string): boolean;
|
|
29
|
-
export declare function buildActionRequest(parsed: Pick<ParsedActionsArgs, 'microApp' | 'actionName' | 'input'>, appUrl: string): ActionRequest;
|
|
34
|
+
export declare function buildActionRequest(parsed: Pick<ParsedActionsArgs, 'microApp' | 'actionName' | 'input' | 'org'>, appUrl: string, options?: BuildActionRequestOptions): ActionRequest;
|
|
30
35
|
export declare function fetchActionRequest(request: ActionRequest): Promise<unknown>;
|
|
31
36
|
//# sourceMappingURL=actions-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions-client.d.ts","sourceRoot":"","sources":["../../src/lib/actions-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,MAAM,WAAW,iCAAiC;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,0BAA0B,CAAC,EACzC,GAAiB,EACjB,YAAY,EACZ,eAAe,GAChB,EAAE,iCAAiC,UAKnC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,GAAiB,EACjB,OAAY,EACZ,UAAU,GACX,EAAE,0BAA0B,UAS5B;AA4ED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"actions-client.d.ts","sourceRoot":"","sources":["../../src/lib/actions-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAE1F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,yBAAyB,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;IAChH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,MAAM,WAAW,iCAAiC;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,0BAA0B,CAAC,EACzC,GAAiB,EACjB,YAAY,EACZ,eAAe,GAChB,EAAE,iCAAiC,UAKnC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,GAAiB,EACjB,OAAY,EACZ,UAAU,GACX,EAAE,0BAA0B,UAS5B;AA4ED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAmElE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,UAS7C;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,WAK3C;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,KAAK,CAAC,EAC5E,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,yBAA8B,GACtC,aAAa,CAiCf;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCjF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolveAuthHeaders } from './auth-credential.js';
|
|
2
3
|
function readUrlValue(value) {
|
|
3
4
|
const trimmed = value?.trim();
|
|
4
5
|
return trimmed || undefined;
|
|
@@ -90,6 +91,7 @@ export function parseActionsArgs(args) {
|
|
|
90
91
|
const input = {};
|
|
91
92
|
const positional = [];
|
|
92
93
|
let appUrl;
|
|
94
|
+
let org;
|
|
93
95
|
let help = false;
|
|
94
96
|
for (let i = 0; i < args.length; i++) {
|
|
95
97
|
const arg = args[i];
|
|
@@ -112,6 +114,9 @@ export function parseActionsArgs(args) {
|
|
|
112
114
|
if (flag === '--url' || flag === '--app-url') {
|
|
113
115
|
appUrl = readValue();
|
|
114
116
|
}
|
|
117
|
+
else if (flag === '--org') {
|
|
118
|
+
org = readValue();
|
|
119
|
+
}
|
|
115
120
|
else if (flag === '--json') {
|
|
116
121
|
Object.assign(input, parseJsonValue(readValue(), '--json'));
|
|
117
122
|
}
|
|
@@ -154,6 +159,7 @@ export function parseActionsArgs(args) {
|
|
|
154
159
|
actionName: positional[1],
|
|
155
160
|
extraPositionals: positional.slice(2),
|
|
156
161
|
appUrl,
|
|
162
|
+
org,
|
|
157
163
|
input,
|
|
158
164
|
help,
|
|
159
165
|
};
|
|
@@ -174,23 +180,36 @@ export function isLocalAppUrl(appUrl) {
|
|
|
174
180
|
|| parsed.hostname === '127.0.0.1'
|
|
175
181
|
|| parsed.hostname === '::1';
|
|
176
182
|
}
|
|
177
|
-
export function buildActionRequest(parsed, appUrl) {
|
|
183
|
+
export function buildActionRequest(parsed, appUrl, options = {}) {
|
|
178
184
|
if (!parsed.microApp) {
|
|
179
185
|
throw new Error('missing micro-app name');
|
|
180
186
|
}
|
|
181
187
|
const base = normalizeAppUrl(appUrl);
|
|
188
|
+
const authHeaders = resolveAuthHeaders({
|
|
189
|
+
appUrl: base,
|
|
190
|
+
org: parsed.org,
|
|
191
|
+
env: options.env,
|
|
192
|
+
platform: options.platform,
|
|
193
|
+
homedir: options.homedir,
|
|
194
|
+
});
|
|
182
195
|
const actionPath = parsed.actionName
|
|
183
196
|
? `/${encodeURIComponent(parsed.actionName)}`
|
|
184
197
|
: '';
|
|
198
|
+
const requestHeaders = parsed.actionName
|
|
199
|
+
? { 'Content-Type': 'application/json', ...authHeaders }
|
|
200
|
+
: authHeaders;
|
|
185
201
|
return {
|
|
186
202
|
url: `${base}/api/${encodeURIComponent(parsed.microApp)}/actions${actionPath}`,
|
|
187
203
|
init: parsed.actionName
|
|
188
204
|
? {
|
|
189
205
|
method: 'POST',
|
|
190
|
-
headers:
|
|
206
|
+
headers: requestHeaders,
|
|
191
207
|
body: JSON.stringify(parsed.input),
|
|
192
208
|
}
|
|
193
|
-
: {
|
|
209
|
+
: {
|
|
210
|
+
method: 'GET',
|
|
211
|
+
...(Object.keys(requestHeaders).length > 0 ? { headers: requestHeaders } : {}),
|
|
212
|
+
},
|
|
194
213
|
};
|
|
195
214
|
}
|
|
196
215
|
export async function fetchActionRequest(request) {
|
|
@@ -220,6 +239,9 @@ export async function fetchActionRequest(request) {
|
|
|
220
239
|
const error = payload && typeof payload === 'object' && 'error' in payload
|
|
221
240
|
? String(payload.error)
|
|
222
241
|
: `Request failed with HTTP ${response.status}`;
|
|
242
|
+
if (response.status === 401) {
|
|
243
|
+
throw new Error(`${error}. Run \`dd login\` or set DD_API_KEY.`);
|
|
244
|
+
}
|
|
223
245
|
throw new Error(error);
|
|
224
246
|
}
|
|
225
247
|
return payload;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AuthStorePathsOptions } from './auth-store.js';
|
|
2
|
+
export interface ResolveAuthHeadersOptions extends AuthStorePathsOptions {
|
|
3
|
+
appUrl: string;
|
|
4
|
+
org?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveAuthHeaders({ appUrl, org, env, ...storeOptions }: ResolveAuthHeadersOptions): Record<string, string>;
|
|
8
|
+
//# sourceMappingURL=auth-credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-credential.d.ts","sourceRoot":"","sources":["../../src/lib/auth-credential.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAEnF,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,GAAG,EACH,GAAiB,EACjB,GAAG,YAAY,EAChB,EAAE,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBpD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readStoredCredential } from './auth-store.js';
|
|
2
|
+
export function resolveAuthHeaders({ appUrl, org, env = process.env, ...storeOptions }) {
|
|
3
|
+
const headers = {};
|
|
4
|
+
const apiKey = readTrimmedValue(env.DD_API_KEY) ?? readTrimmedValue(env.DD_TOKEN);
|
|
5
|
+
const storedCredential = apiKey
|
|
6
|
+
? undefined
|
|
7
|
+
: readStoredCredential({ appUrl, env, ...storeOptions });
|
|
8
|
+
if (apiKey) {
|
|
9
|
+
headers['x-api-key'] = apiKey;
|
|
10
|
+
}
|
|
11
|
+
else if (storedCredential?.token) {
|
|
12
|
+
headers.Authorization = `Bearer ${storedCredential.token}`;
|
|
13
|
+
}
|
|
14
|
+
const resolvedOrg = readTrimmedValue(org)
|
|
15
|
+
?? readTrimmedValue(env.DD_ORG)
|
|
16
|
+
?? readTrimmedValue(storedCredential?.defaultOrg);
|
|
17
|
+
if (resolvedOrg) {
|
|
18
|
+
headers['x-active-organization'] = resolvedOrg;
|
|
19
|
+
}
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
22
|
+
function readTrimmedValue(value) {
|
|
23
|
+
const trimmed = value?.trim();
|
|
24
|
+
return trimmed || undefined;
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface StoredAuthUser {
|
|
2
|
+
id?: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface StoredHostCredential {
|
|
6
|
+
token: string;
|
|
7
|
+
defaultOrg?: string;
|
|
8
|
+
user?: StoredAuthUser;
|
|
9
|
+
}
|
|
10
|
+
export interface AuthStorePathsOptions {
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
platform?: NodeJS.Platform;
|
|
13
|
+
homedir?: () => string;
|
|
14
|
+
}
|
|
15
|
+
export interface ReadStoredCredentialOptions extends AuthStorePathsOptions {
|
|
16
|
+
appUrl: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WriteStoredCredentialOptions extends AuthStorePathsOptions {
|
|
19
|
+
appUrl: string;
|
|
20
|
+
credential: StoredHostCredential;
|
|
21
|
+
}
|
|
22
|
+
export interface ClearStoredCredentialOptions extends AuthStorePathsOptions {
|
|
23
|
+
appUrl: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function getAuthStorePaths({ env, platform, homedir, }?: AuthStorePathsOptions): {
|
|
26
|
+
configDir: string;
|
|
27
|
+
credentialsPath: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function getAuthHostKey(appUrl: string): string;
|
|
30
|
+
export declare function readStoredCredential({ appUrl, ...options }: ReadStoredCredentialOptions): StoredHostCredential | undefined;
|
|
31
|
+
export declare function writeStoredCredential({ appUrl, credential, ...options }: WriteStoredCredentialOptions): void;
|
|
32
|
+
export declare function clearStoredCredential({ appUrl, ...options }: ClearStoredCredentialOptions): void;
|
|
33
|
+
//# sourceMappingURL=auth-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../../src/lib/auth-store.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAMD,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA4B,SAAQ,qBAAqB;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,4BAA6B,SAAQ,qBAAqB;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,oBAAoB,CAAC;CAClC;AAED,MAAM,WAAW,4BAA6B,SAAQ,qBAAqB;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,iBAAiB,CAAC,EAChC,GAAiB,EACjB,QAA2B,EAC3B,OAAoB,GACrB,GAAE,qBAA0B;;;EAS5B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,UAE5C;AAwDD,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,GAAG,OAAO,EACX,EAAE,2BAA2B,GAAG,oBAAoB,GAAG,SAAS,CAGhE;AAED,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,UAAU,EACV,GAAG,OAAO,EACX,EAAE,4BAA4B,GAAG,IAAI,CAIrC;AAED,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,GAAG,OAAO,EACX,EAAE,4BAA4B,GAAG,IAAI,CAWrC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const CONFIG_DIR_NAME = 'doubledigit';
|
|
5
|
+
const CREDENTIALS_FILE_NAME = 'credentials.json';
|
|
6
|
+
function readTrimmedValue(value) {
|
|
7
|
+
const trimmed = value?.trim();
|
|
8
|
+
return trimmed || undefined;
|
|
9
|
+
}
|
|
10
|
+
export function getAuthStorePaths({ env = process.env, platform = process.platform, homedir = os.homedir, } = {}) {
|
|
11
|
+
const configRoot = platform === 'win32'
|
|
12
|
+
? readTrimmedValue(env.APPDATA) ?? path.join(homedir(), 'AppData', 'Roaming')
|
|
13
|
+
: readTrimmedValue(env.XDG_CONFIG_HOME) ?? path.join(homedir(), '.config');
|
|
14
|
+
const configDir = path.join(configRoot, CONFIG_DIR_NAME);
|
|
15
|
+
return {
|
|
16
|
+
configDir,
|
|
17
|
+
credentialsPath: path.join(configDir, CREDENTIALS_FILE_NAME),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function getAuthHostKey(appUrl) {
|
|
21
|
+
return new URL(normalizeAuthStoreAppUrl(appUrl)).origin.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
function normalizeAuthStoreAppUrl(appUrl) {
|
|
24
|
+
const trimmed = appUrl.trim().replace(/\/+$/, '');
|
|
25
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
if (/^(localhost|127(?:\.\d{1,3}){3}|\[::1\])(?::|\/|$)/i.test(trimmed)) {
|
|
29
|
+
return `http://${trimmed}`;
|
|
30
|
+
}
|
|
31
|
+
return `https://${trimmed}`;
|
|
32
|
+
}
|
|
33
|
+
function readAuthStoreFile(options = {}) {
|
|
34
|
+
const { credentialsPath } = getAuthStorePaths(options);
|
|
35
|
+
try {
|
|
36
|
+
const raw = readFileSync(credentialsPath, 'utf8');
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
39
|
+
return { hosts: {} };
|
|
40
|
+
}
|
|
41
|
+
const hosts = parsed.hosts;
|
|
42
|
+
if (!hosts || typeof hosts !== 'object' || Array.isArray(hosts)) {
|
|
43
|
+
return { hosts: {} };
|
|
44
|
+
}
|
|
45
|
+
return { hosts: hosts };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code === 'ENOENT') {
|
|
49
|
+
return { hosts: {} };
|
|
50
|
+
}
|
|
51
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
52
|
+
throw new Error(`Unable to read CLI credentials store: ${detail}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function writeAuthStoreFile(file, options = {}) {
|
|
56
|
+
const { configDir, credentialsPath } = getAuthStorePaths(options);
|
|
57
|
+
mkdirSync(configDir, { recursive: true });
|
|
58
|
+
writeFileSync(credentialsPath, `${JSON.stringify(file, null, 2)}\n`, {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
mode: 0o600,
|
|
61
|
+
});
|
|
62
|
+
if ((options.platform ?? process.platform) !== 'win32') {
|
|
63
|
+
try {
|
|
64
|
+
chmodSync(credentialsPath, 0o600);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Best-effort on filesystems that do not support POSIX permissions.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function readStoredCredential({ appUrl, ...options }) {
|
|
72
|
+
const file = readAuthStoreFile(options);
|
|
73
|
+
return file.hosts[getAuthHostKey(appUrl)];
|
|
74
|
+
}
|
|
75
|
+
export function writeStoredCredential({ appUrl, credential, ...options }) {
|
|
76
|
+
const file = readAuthStoreFile(options);
|
|
77
|
+
file.hosts[getAuthHostKey(appUrl)] = credential;
|
|
78
|
+
writeAuthStoreFile(file, options);
|
|
79
|
+
}
|
|
80
|
+
export function clearStoredCredential({ appUrl, ...options }) {
|
|
81
|
+
const file = readAuthStoreFile(options);
|
|
82
|
+
delete file.hosts[getAuthHostKey(appUrl)];
|
|
83
|
+
if (Object.keys(file.hosts).length === 0) {
|
|
84
|
+
const { credentialsPath } = getAuthStorePaths(options);
|
|
85
|
+
rmSync(credentialsPath, { force: true });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
writeAuthStoreFile(file, options);
|
|
89
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-frameworks.d.ts","sourceRoot":"","sources":["../../src/lib/component-frameworks.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,YAAY,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,oBAAoB,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,cAAc,EAAE,OAAO,CAAA;KAAE,KAAK;QAChF,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,EAAE,CAAC;KAChB,CAAC;IACF,cAAc,EAAE,CAAC,OAAO,EAAE;QAAE,cAAc,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC;QAC9D,OAAO,EAAE,KAAK,CAAC;QACf,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;KACjB,KAAK,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"component-frameworks.d.ts","sourceRoot":"","sources":["../../src/lib/component-frameworks.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,YAAY,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,oBAAoB,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,cAAc,EAAE,OAAO,CAAA;KAAE,KAAK;QAChF,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,EAAE,CAAC;KAChB,CAAC;IACF,cAAc,EAAE,CAAC,OAAO,EAAE;QAAE,cAAc,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC;QAC9D,OAAO,EAAE,KAAK,CAAC;QACf,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;KACjB,KAAK,MAAM,CAAC;CACd;AA0ED,eAAO,MAAM,0BAA0B,EAAE,yBAAyB,EAuDjE,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,oBAAoB,CASzE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,yBAAyB,CAKtF;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,yBAAyB,EAAE,WAAW,SAAwB,QAKrH"}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
function
|
|
3
|
-
if (!nonInteractive
|
|
2
|
+
function addNonInteractiveSkillFlags(command, nonInteractive, options = {}) {
|
|
3
|
+
if (!nonInteractive)
|
|
4
4
|
return command;
|
|
5
|
+
const args = [...command.args];
|
|
6
|
+
const displayParts = [command.display];
|
|
7
|
+
if (!args.includes('--skill') && options.defaultSkill) {
|
|
8
|
+
args.push('--skill', options.defaultSkill);
|
|
9
|
+
displayParts.push('--skill', options.defaultSkill);
|
|
10
|
+
}
|
|
11
|
+
if (!args.includes('--all') && !args.includes('--agent') && !args.includes('-a')) {
|
|
12
|
+
args.push('--agent', '*');
|
|
13
|
+
displayParts.push('--agent', "'*'");
|
|
14
|
+
}
|
|
15
|
+
if (!args.includes('--all') && !args.includes('--yes') && !args.includes('-y')) {
|
|
16
|
+
args.push('--yes');
|
|
17
|
+
displayParts.push('--yes');
|
|
18
|
+
}
|
|
5
19
|
return {
|
|
6
20
|
...command,
|
|
7
|
-
args
|
|
8
|
-
display:
|
|
21
|
+
args,
|
|
22
|
+
display: displayParts.join(' '),
|
|
9
23
|
};
|
|
10
24
|
}
|
|
11
25
|
function normalizeSlashes(value) {
|
|
@@ -57,7 +71,7 @@ export const componentFrameworkAdapters = [
|
|
|
57
71
|
args: ['skills', 'add', 'remotion-dev/skills', '--all'],
|
|
58
72
|
display: 'npx skills add remotion-dev/skills --all',
|
|
59
73
|
},
|
|
60
|
-
|
|
74
|
+
addNonInteractiveSkillFlags({
|
|
61
75
|
command: 'npx',
|
|
62
76
|
args: ['skills', 'add', 'crystalphantom/double-digit', '--skill', 'dd-component-hub'],
|
|
63
77
|
display: 'npx skills add crystalphantom/double-digit --skill dd-component-hub',
|
|
@@ -82,12 +96,12 @@ export const componentFrameworkAdapters = [
|
|
|
82
96
|
],
|
|
83
97
|
}),
|
|
84
98
|
skillsCommands: ({ nonInteractive }) => [
|
|
85
|
-
|
|
99
|
+
addNonInteractiveSkillFlags({
|
|
86
100
|
command: 'npx',
|
|
87
101
|
args: ['skills', 'add', 'heygen-com/hyperframes'],
|
|
88
102
|
display: 'npx skills add heygen-com/hyperframes',
|
|
89
|
-
}, nonInteractive),
|
|
90
|
-
|
|
103
|
+
}, nonInteractive, { defaultSkill: 'animejs' }),
|
|
104
|
+
addNonInteractiveSkillFlags({
|
|
91
105
|
command: 'npx',
|
|
92
106
|
args: ['skills', 'add', 'crystalphantom/double-digit', '--skill', 'dd-component-hub'],
|
|
93
107
|
display: 'npx skills add crystalphantom/double-digit --skill dd-component-hub',
|