cicy-code 2.3.316 → 2.3.317

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.
Files changed (2) hide show
  1. package/bin/cicy-code.js +191 -10
  2. package/package.json +5 -5
package/bin/cicy-code.js CHANGED
@@ -14,10 +14,16 @@
14
14
  // and we abort. Utility invocations (--help/--version, `skill …`) skip the
15
15
  // port dance entirely — `npx cicy-code --version` must never touch :8008.
16
16
  const { spawn, execSync } = require('child_process');
17
+ const crypto = require('crypto');
17
18
  const fs = require('fs');
19
+ const https = require('https');
20
+ const os = require('os');
21
+ const path = require('path');
18
22
 
19
- const args = process.argv.slice(2);
23
+ const rawArgs = process.argv.slice(2);
24
+ const { email: cloudEmail, args } = takeEmailArg(rawArgs);
20
25
  const PORT = process.env.PORT || '8008';
26
+ const CLOUD_ORIGIN = (process.env.CICY_CLOUD_ORIGIN || 'https://cicy-ai.com').replace(/\/$/, '');
21
27
 
22
28
  // Package name uses "windows", not the process.platform value "win32":
23
29
  // npm's spam filter rejects (403) new package names containing win32.
@@ -43,17 +49,192 @@ const isUtility =
43
49
  args.some((a) => a === '-h' || a === '--help' || a === '-v' || a === '--version') ||
44
50
  args[0] === 'skill';
45
51
 
46
- if (!isUtility) ensurePortFree(PORT);
47
-
48
- const child = spawn(binPath, args, {
49
- stdio: 'inherit',
50
- env: { ...process.env, PORT },
51
- });
52
- child.on('exit', (code, signal) => {
53
- if (signal) process.kill(process.pid, signal);
54
- else process.exit(code == null ? 0 : code);
52
+ main().catch((err) => {
53
+ console.error(`cicy-code: ${err && err.message ? err.message : err}`);
54
+ process.exit(1);
55
55
  });
56
56
 
57
+ async function main() {
58
+ let cloudEnv = {};
59
+ if (cloudEmail) cloudEnv = await cloudLogin(cloudEmail);
60
+ if (!isUtility) ensurePortFree(PORT);
61
+ const child = spawn(binPath, args, {
62
+ stdio: 'inherit',
63
+ env: { ...process.env, ...cloudEnv, PORT },
64
+ });
65
+ child.on('exit', (code, signal) => {
66
+ if (signal) process.kill(process.pid, signal);
67
+ else process.exit(code == null ? 0 : code);
68
+ });
69
+ }
70
+
71
+ function takeEmailArg(input) {
72
+ const output = [];
73
+ let email = '';
74
+ for (let i = 0; i < input.length; i += 1) {
75
+ const value = input[i];
76
+ if (value === '--email') {
77
+ email = String(input[i + 1] || '').trim().toLowerCase();
78
+ i += 1;
79
+ } else if (value.startsWith('--email=')) {
80
+ email = value.slice('--email='.length).trim().toLowerCase();
81
+ } else {
82
+ output.push(value);
83
+ }
84
+ }
85
+ if (email && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
86
+ throw new Error('invalid --email address');
87
+ }
88
+ return { email, args: output };
89
+ }
90
+
91
+ async function cloudLogin(email) {
92
+ const credentialPath = cloudCredentialPath();
93
+ const saved = readJSON(credentialPath);
94
+ const instanceId = String(saved.instance_id || '').trim() ||
95
+ `code-${crypto.randomBytes(18).toString('hex')}`;
96
+ let token = saved.email === email ? String(saved.token || '') : '';
97
+
98
+ if (token) {
99
+ try {
100
+ await registerCloudInstance(token, instanceId);
101
+ console.log(`cicy-code: CiCy Cloud connected as ${email}`);
102
+ return cloudEnvironment(email, instanceId, token);
103
+ } catch {
104
+ token = '';
105
+ }
106
+ }
107
+
108
+ const state = crypto.randomBytes(32).toString('hex');
109
+ await requestJSON('/api/auth/email/request', {
110
+ method: 'POST',
111
+ body: { email, state, flow: 'desktop_poll', lang: preferredLanguage() },
112
+ });
113
+ console.log(`cicy-code: login email sent to ${email}`);
114
+ console.log('cicy-code: click the link in the email; waiting for confirmation…');
115
+
116
+ const deadline = Date.now() + 15 * 60 * 1000;
117
+ while (Date.now() < deadline) {
118
+ await delay(2000);
119
+ const result = await requestJSON(`/api/auth/desktop/poll?state=${encodeURIComponent(state)}`);
120
+ if (result.status === 'pending') continue;
121
+ if (result.status !== 'ready' || !result.token) {
122
+ throw new Error('email login expired; start cicy-code again');
123
+ }
124
+ token = String(result.token);
125
+ await registerCloudInstance(token, instanceId);
126
+ writeCredential(credentialPath, {
127
+ email,
128
+ instance_id: instanceId,
129
+ token,
130
+ cloud_origin: CLOUD_ORIGIN,
131
+ updated_at: new Date().toISOString(),
132
+ });
133
+ console.log(`cicy-code: login successful; this device is now bound to ${email}`);
134
+ return cloudEnvironment(email, instanceId, token);
135
+ }
136
+ throw new Error('email login timed out; start cicy-code again');
137
+ }
138
+
139
+ async function registerCloudInstance(token, instanceId) {
140
+ await requestJSON('/api/code/instances/register', {
141
+ method: 'POST',
142
+ token,
143
+ body: {
144
+ instanceId,
145
+ platform: isColab() ? 'colab' : process.platform,
146
+ arch: process.arch,
147
+ runtime: isColab() ? 'colab' : 'native',
148
+ systemLanguage: preferredLanguage(),
149
+ },
150
+ });
151
+ }
152
+
153
+ function cloudEnvironment(email, instanceId, token) {
154
+ return {
155
+ CICY_CLOUD_ORIGIN: CLOUD_ORIGIN,
156
+ CICY_CLOUD_EMAIL: email,
157
+ CICY_CLOUD_INSTANCE_ID: instanceId,
158
+ CICY_CLOUD_TOKEN: token,
159
+ };
160
+ }
161
+
162
+ function cloudCredentialPath() {
163
+ return path.join(cloudHome(), 'db', 'cloud-device.json');
164
+ }
165
+
166
+ function cloudHome() {
167
+ return process.env.CICY_HOME || path.join(os.homedir(), 'cicy-ai');
168
+ }
169
+
170
+ function isColab() {
171
+ return process.platform === 'linux' && (
172
+ Boolean(process.env.COLAB_RELEASE_TAG) ||
173
+ Boolean(process.env.COLAB_GPU) ||
174
+ fs.existsSync('/content')
175
+ );
176
+ }
177
+
178
+ function readJSON(file) {
179
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return {}; }
180
+ }
181
+
182
+ function writeCredential(file, value) {
183
+ writeJSONAtomic(file, value);
184
+ }
185
+
186
+ function writeJSONAtomic(file, value) {
187
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
188
+ const temporary = `${file}.${process.pid}.tmp`;
189
+ fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
190
+ fs.renameSync(temporary, file);
191
+ try { fs.chmodSync(file, 0o600); } catch {}
192
+ }
193
+
194
+ function preferredLanguage() {
195
+ const lang = String(process.env.LANG || '').toLowerCase();
196
+ if (lang.startsWith('ja')) return 'ja';
197
+ if (lang.startsWith('fr')) return 'fr';
198
+ if (lang.startsWith('en')) return 'en';
199
+ return 'zh';
200
+ }
201
+
202
+ function delay(ms) {
203
+ return new Promise((resolve) => setTimeout(resolve, ms));
204
+ }
205
+
206
+ function requestJSON(route, options = {}) {
207
+ return new Promise((resolve, reject) => {
208
+ const url = new URL(route, CLOUD_ORIGIN);
209
+ const body = options.body ? Buffer.from(JSON.stringify(options.body)) : null;
210
+ const req = https.request(url, {
211
+ method: options.method || 'GET',
212
+ headers: {
213
+ accept: 'application/json',
214
+ ...(body ? { 'content-type': 'application/json', 'content-length': body.length } : {}),
215
+ ...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
216
+ },
217
+ timeout: 15000,
218
+ }, (res) => {
219
+ const chunks = [];
220
+ res.on('data', (chunk) => chunks.push(chunk));
221
+ res.on('end', () => {
222
+ let data;
223
+ try { data = JSON.parse(Buffer.concat(chunks).toString('utf8')); }
224
+ catch { return reject(new Error(`CiCy Cloud returned HTTP ${res.statusCode}`)); }
225
+ if (res.statusCode < 200 || res.statusCode >= 300) {
226
+ return reject(new Error(data.error || `CiCy Cloud returned HTTP ${res.statusCode}`));
227
+ }
228
+ resolve(data);
229
+ });
230
+ });
231
+ req.on('timeout', () => req.destroy(new Error('CiCy Cloud request timed out')));
232
+ req.on('error', reject);
233
+ if (body) req.write(body);
234
+ req.end();
235
+ });
236
+ }
237
+
57
238
  // --- dev.py-style port hygiene --------------------------------------------
58
239
 
59
240
  function ensurePortFree(port) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.3.316",
6
+ "version": "2.3.317",
7
7
  "description": "CiCy Code - AI-powered development environment",
8
8
  "author": {
9
9
  "name": "cicybot",
@@ -16,10 +16,10 @@
16
16
  "bin/cicy-code.js"
17
17
  ],
18
18
  "optionalDependencies": {
19
- "cicy-code-darwin-arm64": "2.3.316",
20
- "cicy-code-darwin-x64": "2.3.316",
21
- "cicy-code-linux-arm64": "2.3.316",
22
- "cicy-code-linux-x64": "2.3.316"
19
+ "cicy-code-darwin-arm64": "2.3.317",
20
+ "cicy-code-darwin-x64": "2.3.317",
21
+ "cicy-code-linux-arm64": "2.3.317",
22
+ "cicy-code-linux-x64": "2.3.317"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",