@clipit-ai/cli 0.2.1 → 0.2.3
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/bin/clipit.mjs +72 -17
- package/package.json +1 -1
package/bin/clipit.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import process from 'node:process';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
|
-
const VERSION = '0.2.
|
|
12
|
+
const VERSION = '0.2.3';
|
|
13
13
|
const DEFAULT_BASE_URL = 'https://clipit.dev';
|
|
14
14
|
const DEFAULT_SCOPES = [
|
|
15
15
|
'clippy_agent',
|
|
@@ -724,14 +724,33 @@ async function enforceRunMaxCredits(config, options, functionName) {
|
|
|
724
724
|
async function login(config, options) {
|
|
725
725
|
const { verifier, challenge } = pkcePair();
|
|
726
726
|
const baseUrl = getBaseUrl(config, options);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
727
|
+
let started;
|
|
728
|
+
try {
|
|
729
|
+
started = await apiFetch(config, options, 'POST', '/api/v1/cli-auth/start', {
|
|
730
|
+
codeChallenge: challenge,
|
|
731
|
+
codeChallengeMethod: 'S256',
|
|
732
|
+
requestedScopes: DEFAULT_SCOPES,
|
|
733
|
+
deviceName: os.hostname(),
|
|
734
|
+
cliVersion: VERSION,
|
|
735
|
+
platform: `${process.platform}/${process.arch}`,
|
|
736
|
+
}, { noAuth: true });
|
|
737
|
+
} catch (error) {
|
|
738
|
+
// A login start should never need credentials — a 401/404 here means the
|
|
739
|
+
// server predates the CLI auth API entirely.
|
|
740
|
+
if (error.status === 401 || error.status === 404) {
|
|
741
|
+
throw Object.assign(
|
|
742
|
+
new Error([
|
|
743
|
+
`${baseUrl} does not support browser login yet (its API layer predates this CLI).`,
|
|
744
|
+
'Use an API key instead:',
|
|
745
|
+
` 1. Create one at ${baseUrl} -> Settings -> API Keys -> Connect an Agent`,
|
|
746
|
+
" 2. Store it: printf '%s' \"$CLIPPER_API_KEY\" | clipit auth set-key --stdin",
|
|
747
|
+
' 3. Verify: clipit videos list',
|
|
748
|
+
].join('\n')),
|
|
749
|
+
{ exitCode: EXIT.AUTH, status: error.status, requestId: error.requestId },
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
735
754
|
|
|
736
755
|
if (!options['no-browser']) {
|
|
737
756
|
try {
|
|
@@ -1806,25 +1825,61 @@ async function jobs(config, options, action, args) {
|
|
|
1806
1825
|
}
|
|
1807
1826
|
}
|
|
1808
1827
|
|
|
1809
|
-
function appUrl(config, options, kind, id) {
|
|
1828
|
+
async function appUrl(config, options, kind, id) {
|
|
1810
1829
|
const baseUrl = getBaseUrl(config, options);
|
|
1811
1830
|
if (!kind || kind === 'home') return baseUrl;
|
|
1812
1831
|
if (kind === 'settings') return `${baseUrl}/settings?tab=${encodeURIComponent(options.tab || 'api-keys')}`;
|
|
1813
1832
|
if (kind === 'dashboard') return `${baseUrl}/dashboard`;
|
|
1814
1833
|
if (kind === 'library') return `${baseUrl}/clips`;
|
|
1815
|
-
if (kind === 'editor')
|
|
1834
|
+
if (kind === 'editor') {
|
|
1835
|
+
// The editor reads ?project=, ?video=, ?clip= (VideoEditorPage). Thread any
|
|
1836
|
+
// provided context so `open editor --video <id>` lands on real content rather
|
|
1837
|
+
// than the empty "select a video" state.
|
|
1838
|
+
const params = new URLSearchParams();
|
|
1839
|
+
const project = id || options.project || null;
|
|
1840
|
+
if (project) params.set('project', project);
|
|
1841
|
+
if (options.video || options.videoId) params.set('video', options.video || options.videoId);
|
|
1842
|
+
if (options.clip || options.clipId) params.set('clip', options.clip || options.clipId);
|
|
1843
|
+
const qs = params.toString();
|
|
1844
|
+
return `${baseUrl}/editor${qs ? `?${qs}` : ''}`;
|
|
1845
|
+
}
|
|
1816
1846
|
if (kind === 'pricing') return `${baseUrl}/pricing`;
|
|
1817
1847
|
if (kind === 'credits') return `${baseUrl}/settings/credits`;
|
|
1818
|
-
if (kind === 'clip')
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1848
|
+
if (kind === 'clip') {
|
|
1849
|
+
// A clip lives inside a video. The Clips Review page keys off ?video=<videoId>
|
|
1850
|
+
// — without it the page renders "No video selected", so a bare clip link
|
|
1851
|
+
// dead-ends. Resolve the clip's parent video so the link lands on the clip,
|
|
1852
|
+
// selected. ?clip=<clipId> focuses it (the page reads `clip`, not `clipId`).
|
|
1853
|
+
if (!id) return `${baseUrl}/clips/review`;
|
|
1854
|
+
let videoId = options.video || options.videoId || null;
|
|
1855
|
+
if (!videoId) {
|
|
1856
|
+
try {
|
|
1857
|
+
const clip = await apiFetch(config, options, 'GET', `/api/v1/clips/${encodeURIComponent(id)}`);
|
|
1858
|
+
videoId = clip?.videoId || clip?.video?.id || null;
|
|
1859
|
+
} catch {
|
|
1860
|
+
// Network/auth/not-found — fall through to a clip-only link below.
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const params = new URLSearchParams();
|
|
1864
|
+
if (videoId) params.set('video', videoId);
|
|
1865
|
+
params.set('clip', id);
|
|
1866
|
+
return `${baseUrl}/clips/review?${params.toString()}`;
|
|
1867
|
+
}
|
|
1868
|
+
// A video link should open that video's clips in the Review page (which keys
|
|
1869
|
+
// off ?video=), not the generic /clips library list which has no selection.
|
|
1870
|
+
if (kind === 'video') return `${baseUrl}/clips/review${id ? `?video=${encodeURIComponent(id)}` : ''}`;
|
|
1871
|
+
// The editor lives at /editor and reads ?project= (NOT /editor/projects, which
|
|
1872
|
+
// redirects to /clips and drops the query — the same dead-end class as the
|
|
1873
|
+
// clip/video link bug). There is no surface that consumes a raw sequenceId, so
|
|
1874
|
+
// a sequence link just opens the editor.
|
|
1875
|
+
if (kind === 'project') return `${baseUrl}/editor${id ? `?project=${encodeURIComponent(id)}` : ''}`;
|
|
1876
|
+
if (kind === 'sequence') return `${baseUrl}/editor`;
|
|
1822
1877
|
if (kind === 'route') return `${baseUrl}/${String(id || '').replace(/^\/+/, '')}`;
|
|
1823
1878
|
return baseUrl;
|
|
1824
1879
|
}
|
|
1825
1880
|
|
|
1826
1881
|
async function openCommand(config, options, kind, id) {
|
|
1827
|
-
const url = appUrl(config, options, kind, id);
|
|
1882
|
+
const url = await appUrl(config, options, kind, id);
|
|
1828
1883
|
if (options.print) {
|
|
1829
1884
|
output(url, options);
|
|
1830
1885
|
return;
|
|
@@ -1837,7 +1892,7 @@ async function links(config, options, kind, id) {
|
|
|
1837
1892
|
const result = {
|
|
1838
1893
|
kind: kind || 'home',
|
|
1839
1894
|
id: id || null,
|
|
1840
|
-
appUrl: appUrl(config, options, kind, id),
|
|
1895
|
+
appUrl: await appUrl(config, options, kind, id),
|
|
1841
1896
|
};
|
|
1842
1897
|
if (kind === 'clip' && id && boolOption(options.download)) {
|
|
1843
1898
|
try {
|