@papercraneai/cli 1.2.0 → 1.4.1
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/papercrane.js +93 -21
- package/lib/config.js +28 -0
- package/package.json +3 -2
package/bin/papercrane.js
CHANGED
|
@@ -4,11 +4,19 @@ import { Command } from 'commander';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import readline from 'readline';
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
|
+
import { ProxyAgent, setGlobalDispatcher } from 'undici';
|
|
7
8
|
import { setApiKey, clearConfig, isLoggedIn, setDefaultWorkspace, getDefaultWorkspace } from '../lib/config.js';
|
|
8
9
|
import { validateApiKey } from '../lib/cloud-client.js';
|
|
9
10
|
import { listFunctions, getFunction, runFunction, formatDescribe, formatDescribeRoot, formatFlat, formatResult, formatUnconnected } from '../lib/function-client.js';
|
|
10
11
|
import { listWorkspaces, resolveWorkspaceId, getFileTree, readFile, writeFile, editFile, deleteFile, getLocalWorkspacePath, pullWorkspace, pushWorkspace } from '../lib/environment-client.js';
|
|
11
12
|
|
|
13
|
+
// Configure proxy support for environments like Claude's container
|
|
14
|
+
// Node.js native fetch() doesn't respect HTTP_PROXY/HTTPS_PROXY env vars
|
|
15
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
|
|
16
|
+
if (proxyUrl) {
|
|
17
|
+
setGlobalDispatcher(new ProxyAgent(proxyUrl));
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
const program = new Command();
|
|
13
21
|
|
|
14
22
|
program
|
|
@@ -21,9 +29,11 @@ program
|
|
|
21
29
|
.description('Login to Papercrane. Opens browser for authentication, or use --api-key for direct login.')
|
|
22
30
|
.option('--api-key <key>', 'API key for direct login (skips browser)')
|
|
23
31
|
.option('--url <url>', 'API base URL (saves to config)')
|
|
32
|
+
.option('--no-wait', 'Print auth URL and exit immediately (for AI assistants)')
|
|
33
|
+
.option('--check', 'Check if pending login was completed')
|
|
24
34
|
.action(async (options) => {
|
|
25
35
|
try {
|
|
26
|
-
const { getApiBaseUrl, setApiBaseUrl } = await import('../lib/config.js');
|
|
36
|
+
const { getApiBaseUrl, setApiBaseUrl, getPendingSession, setPendingSession, clearPendingSession } = await import('../lib/config.js');
|
|
27
37
|
|
|
28
38
|
// Resolve API base URL: --url flag > config > default
|
|
29
39
|
let baseUrl;
|
|
@@ -51,40 +61,102 @@ program
|
|
|
51
61
|
return;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
// --check: Check if pending login was completed
|
|
65
|
+
if (options.check) {
|
|
66
|
+
const pending = await getPendingSession();
|
|
67
|
+
if (!pending) {
|
|
68
|
+
console.log(chalk.yellow('No pending login session. Run: papercrane login --no-wait'));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const statusRes = await fetch(`${pending.baseUrl}/api/cli-auth/status?session=${encodeURIComponent(pending.session)}`);
|
|
73
|
+
|
|
74
|
+
if (statusRes.ok) {
|
|
75
|
+
const data = await statusRes.json();
|
|
76
|
+
if (data.status === 'authorized') {
|
|
77
|
+
await setApiKey(data.key);
|
|
78
|
+
await setApiBaseUrl(pending.baseUrl);
|
|
79
|
+
await clearPendingSession();
|
|
80
|
+
console.log(chalk.green(`✓ Logged in as ${data.email}`));
|
|
81
|
+
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json\n`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(chalk.yellow('Not yet authorized. Please click the login URL, then run: papercrane login --check'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
// Browser-based login flow
|
|
55
|
-
const {
|
|
56
|
-
|
|
91
|
+
const { generateState } = await import('../lib/auth-server.js');
|
|
92
|
+
|
|
93
|
+
const session = generateState();
|
|
57
94
|
|
|
58
|
-
|
|
59
|
-
const
|
|
95
|
+
// Initialize session on server
|
|
96
|
+
const initRes = await fetch(`${baseUrl}/api/cli-auth/init`, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/json' },
|
|
99
|
+
body: JSON.stringify({ session })
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!initRes.ok) {
|
|
103
|
+
throw new Error('Failed to initialize login session');
|
|
104
|
+
}
|
|
60
105
|
|
|
61
|
-
const authUrl = `${baseUrl}/cli-auth?
|
|
106
|
+
const authUrl = `${baseUrl}/cli-auth?session=${session}`;
|
|
107
|
+
|
|
108
|
+
// --no-wait: Print URL and exit immediately (for AI assistants)
|
|
109
|
+
if (options.noWait) {
|
|
110
|
+
await setPendingSession({ session, baseUrl });
|
|
111
|
+
console.log(chalk.cyan('\nOpen this URL to authenticate:\n'));
|
|
112
|
+
console.log(` ${authUrl}\n`);
|
|
113
|
+
console.log(chalk.dim('After authorizing, run: papercrane login --check\n'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
62
116
|
|
|
63
|
-
console.log(chalk.cyan('
|
|
64
|
-
console.log(
|
|
117
|
+
console.log(chalk.cyan('\nOpen this URL to authenticate:\n'));
|
|
118
|
+
console.log(` ${authUrl}\n`);
|
|
65
119
|
|
|
66
|
-
// Try to open browser
|
|
120
|
+
// Try to open browser automatically
|
|
67
121
|
try {
|
|
122
|
+
const open = (await import('open')).default;
|
|
68
123
|
await open(authUrl);
|
|
124
|
+
console.log(chalk.dim('(Browser opened automatically)'));
|
|
69
125
|
} catch {
|
|
70
|
-
console.log(chalk.yellow('Could not open browser automatically.'));
|
|
71
|
-
console.log(chalk.yellow('Please open this URL manually:\n'));
|
|
72
|
-
console.log(chalk.cyan(` ${authUrl}\n`));
|
|
126
|
+
console.log(chalk.yellow('Could not open browser automatically. Please click or copy the URL above.'));
|
|
73
127
|
}
|
|
74
128
|
|
|
75
|
-
console.log(chalk.dim('
|
|
129
|
+
console.log(chalk.dim('\nWaiting for authorization... (press Ctrl+C to cancel)\n'));
|
|
76
130
|
|
|
77
|
-
//
|
|
78
|
-
const
|
|
131
|
+
// Poll for authorization
|
|
132
|
+
const pollInterval = 2000; // 2 seconds
|
|
133
|
+
const timeout = 120000; // 2 minutes
|
|
134
|
+
const startTime = Date.now();
|
|
79
135
|
|
|
80
|
-
|
|
81
|
-
|
|
136
|
+
while (Date.now() - startTime < timeout) {
|
|
137
|
+
const statusRes = await fetch(`${baseUrl}/api/cli-auth/status?session=${encodeURIComponent(session)}`);
|
|
82
138
|
|
|
83
|
-
|
|
84
|
-
|
|
139
|
+
if (statusRes.ok) {
|
|
140
|
+
const data = await statusRes.json();
|
|
141
|
+
|
|
142
|
+
if (data.status === 'authorized') {
|
|
143
|
+
// Save the API key
|
|
144
|
+
await setApiKey(data.key);
|
|
145
|
+
await setApiBaseUrl(baseUrl);
|
|
146
|
+
|
|
147
|
+
console.log(chalk.green(`✓ Logged in as ${data.email}`));
|
|
148
|
+
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json\n`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
} else if (statusRes.status === 404) {
|
|
152
|
+
throw new Error('Session expired. Please try again.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Wait before next poll
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
157
|
+
}
|
|
85
158
|
|
|
86
|
-
|
|
87
|
-
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json\n`));
|
|
159
|
+
throw new Error('Authentication timed out');
|
|
88
160
|
} catch (error) {
|
|
89
161
|
if (error.message.includes('timed out')) {
|
|
90
162
|
console.error(chalk.red('\nAuthentication timed out. Please try again.'));
|
package/lib/config.js
CHANGED
|
@@ -123,3 +123,31 @@ export async function clearDefaultWorkspace() {
|
|
|
123
123
|
delete config.defaultWorkspaceId;
|
|
124
124
|
await saveConfig(config);
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get pending login session (for --no-wait / --check flow)
|
|
129
|
+
* @returns {Promise<{session: string, baseUrl: string}|null>}
|
|
130
|
+
*/
|
|
131
|
+
export async function getPendingSession() {
|
|
132
|
+
const config = await loadConfig();
|
|
133
|
+
return config.pendingSession || null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Set pending login session
|
|
138
|
+
* @param {{session: string, baseUrl: string}} pendingSession
|
|
139
|
+
*/
|
|
140
|
+
export async function setPendingSession(pendingSession) {
|
|
141
|
+
const config = await loadConfig();
|
|
142
|
+
config.pendingSession = pendingSession;
|
|
143
|
+
await saveConfig(config);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear pending login session
|
|
148
|
+
*/
|
|
149
|
+
export async function clearPendingSession() {
|
|
150
|
+
const config = await loadConfig();
|
|
151
|
+
delete config.pendingSession;
|
|
152
|
+
await saveConfig(config);
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papercraneai/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "CLI tool for managing OAuth credentials for LLM integrations",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"axios": "^1.6.0",
|
|
26
26
|
"chalk": "^4.1.2",
|
|
27
27
|
"inquirer": "^8.2.6",
|
|
28
|
-
"open": "^8.4.2"
|
|
28
|
+
"open": "^8.4.2",
|
|
29
|
+
"undici": "^6.0.0"
|
|
29
30
|
}
|
|
30
31
|
}
|