taskdex 0.1.3 → 0.1.5

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 CHANGED
@@ -38,6 +38,11 @@ This will clone the repo and immediately run interactive terminal setup.
38
38
  Setup now defaults to `dev-client` runtime, can build the native app (`expo run:ios` / `expo run:android`), and then starts Expo in `--dev-client` mode.
39
39
  For iOS builds, setup asks whether to run on a real iPhone or Simulator, and supports choosing a specific iPhone device name.
40
40
  When a `pnpm-lock.yaml` is present in `mobile`, the CLI installs mobile dependencies with pnpm automatically.
41
+ Setup writes runtime bridge env values into `mobile/.env.local`, sets `CODEX_CWD` for bridge agent workspace, and can pass `OPENAI_API_KEY` if provided.
42
+ Setup also includes Convex mode selection:
43
+ - `new` runs `convex dev --once` automatically
44
+ - `existing` prompts for Convex env values and writes them
45
+ - `skip` leaves Convex untouched (bridge/local state still works, cloud persistence may be limited)
41
46
 
42
47
  For an already-cloned repo:
43
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskdex",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Terminal-first installer and launcher for Taskdex mobile + bridge",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import crypto from 'node:crypto';
4
4
  import { spawn } from 'node:child_process';
5
- import { existsSync, readdirSync } from 'node:fs';
5
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import process from 'node:process';
@@ -174,6 +174,34 @@ function parseInstallChoice(input) {
174
174
  throw new Error('Install choice must be Y or N.');
175
175
  }
176
176
 
177
+ function parseConvexMode(input) {
178
+ const normalized = input.trim().toLowerCase() || 'new';
179
+ if (['new', 'setup', 'create'].includes(normalized)) return 'new';
180
+ if (['existing', 'manual'].includes(normalized)) return 'existing';
181
+ if (['skip', 'none'].includes(normalized)) return 'skip';
182
+ throw new Error('Convex setup mode must be "new", "existing", or "skip".');
183
+ }
184
+
185
+ function upsertEnvVars(envFilePath, values) {
186
+ const keys = Object.keys(values);
187
+ const original = existsSync(envFilePath) ? readFileSync(envFilePath, 'utf8') : '';
188
+ const lines = original ? original.split(/\r?\n/) : [];
189
+ const seen = new Set();
190
+ const updated = lines.map((line) => {
191
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
192
+ if (!match) return line;
193
+ const key = match[1];
194
+ if (!keys.includes(key)) return line;
195
+ seen.add(key);
196
+ return `${key}=${values[key]}`;
197
+ });
198
+ for (const key of keys) {
199
+ if (!seen.has(key)) updated.push(`${key}=${values[key]}`);
200
+ }
201
+ const nextContent = `${updated.filter((line) => line !== '').join('\n')}\n`;
202
+ writeFileSync(envFilePath, nextContent, 'utf8');
203
+ }
204
+
177
205
  function wait(ms) {
178
206
  return new Promise((resolve) => setTimeout(resolve, ms));
179
207
  }
@@ -188,10 +216,22 @@ async function readSetupConfig() {
188
216
  const runtimeInput = await rl.question('App runtime (dev-client/expo-go) [dev-client]: ');
189
217
  const installInput = await rl.question('Install npm dependencies first? [Y/n]: ');
190
218
  const runtime = parseRuntime(runtimeInput);
219
+ const convexModeInput = await rl.question('Convex setup (new/existing/skip) [new]: ');
220
+ const convexMode = parseConvexMode(convexModeInput);
221
+ let convexUrlInput = '';
222
+ let convexDeploymentInput = '';
223
+ let convexSiteUrlInput = '';
224
+ if (convexMode === 'existing') {
225
+ convexUrlInput = (await rl.question('EXPO_PUBLIC_CONVEX_URL: ')).trim();
226
+ convexDeploymentInput = (await rl.question('CONVEX_DEPLOYMENT (optional): ')).trim();
227
+ convexSiteUrlInput = (await rl.question('EXPO_PUBLIC_CONVEX_SITE_URL (optional): ')).trim();
228
+ }
191
229
  let buildDevClient = false;
192
230
  let buildPlatform = process.platform === 'darwin' ? 'ios' : 'android';
193
231
  let iosTarget = 'iphone';
194
232
  let iosDeviceName = '';
233
+ const openAiApiKey = (await rl.question('OPENAI_API_KEY (optional, Enter to use current shell/Codex login): ')).trim();
234
+ const workspaceInput = (await rl.question('Agent workspace path [repo root]: ')).trim();
195
235
  if (runtime === 'dev-client') {
196
236
  const buildInput = await rl.question('Build native development client now? [Y/n]: ');
197
237
  buildDevClient = parseInstallChoice(buildInput);
@@ -213,10 +253,16 @@ async function readSetupConfig() {
213
253
  apiKey: apiKeyInput.trim() || crypto.randomBytes(24).toString('hex'),
214
254
  expoMode: parseExpoMode(expoModeInput),
215
255
  runtime,
256
+ convexMode,
257
+ convexUrlInput,
258
+ convexDeploymentInput,
259
+ convexSiteUrlInput,
216
260
  buildDevClient,
217
261
  buildPlatform,
218
262
  iosTarget,
219
263
  iosDeviceName,
264
+ openAiApiKey,
265
+ workspaceInput,
220
266
  installDeps: parseInstallChoice(installInput),
221
267
  };
222
268
  } finally {
@@ -236,16 +282,31 @@ async function runInteractiveSetup(rootDir) {
236
282
  apiKey,
237
283
  expoMode,
238
284
  runtime,
285
+ convexMode,
286
+ convexUrlInput,
287
+ convexDeploymentInput,
288
+ convexSiteUrlInput,
239
289
  buildDevClient,
240
290
  buildPlatform,
241
291
  iosTarget,
242
292
  iosDeviceName,
293
+ openAiApiKey,
294
+ workspaceInput,
243
295
  installDeps,
244
296
  } = await readSetupConfig();
245
297
  const bridgeUrl = `ws://${getLocalIPv4()}:${port}`;
298
+ const workspacePath = workspaceInput ? path.resolve(workspaceInput) : rootDir;
299
+
300
+ if (!existsSync(workspacePath)) {
301
+ throw new Error(`Workspace path does not exist: ${workspacePath}`);
302
+ }
303
+
304
+ const envFilePath = path.join(mobileDir, '.env.local');
246
305
 
247
306
  console.log(`\nBridge URL: ${bridgeUrl}`);
248
307
  console.log(`Bridge API key: ${apiKey}`);
308
+ console.log(`Agent workspace: ${workspacePath}`);
309
+ console.log(`Convex mode: ${convexMode}`);
249
310
  console.log(`Expo mode: ${expoMode}\n`);
250
311
 
251
312
  if (installDeps) {
@@ -257,6 +318,28 @@ async function runInteractiveSetup(rootDir) {
257
318
  await installMobileDependencies(mobileDir);
258
319
  }
259
320
 
321
+ if (convexMode === 'new') {
322
+ console.log('\nSetting up Convex deployment...\n');
323
+ await runCommand(npxCmd, ['-y', 'convex', 'dev', '--once'], { cwd: mobileDir });
324
+ }
325
+
326
+ if (convexMode === 'existing') {
327
+ if (!convexUrlInput) {
328
+ throw new Error('EXPO_PUBLIC_CONVEX_URL is required when Convex mode is "existing".');
329
+ }
330
+ const convexValues = {
331
+ EXPO_PUBLIC_CONVEX_URL: convexUrlInput,
332
+ ...(convexDeploymentInput ? { CONVEX_DEPLOYMENT: convexDeploymentInput } : {}),
333
+ ...(convexSiteUrlInput ? { EXPO_PUBLIC_CONVEX_SITE_URL: convexSiteUrlInput } : {}),
334
+ };
335
+ upsertEnvVars(envFilePath, convexValues);
336
+ }
337
+
338
+ upsertEnvVars(envFilePath, {
339
+ EXPO_PUBLIC_BRIDGE_URL: bridgeUrl,
340
+ EXPO_PUBLIC_BRIDGE_API_KEY: apiKey,
341
+ });
342
+
260
343
  console.log('\nStarting bridge server...\n');
261
344
  const bridgeProcess = spawn(npmCmd, ['run', 'dev'], {
262
345
  cwd: bridgeDir,
@@ -265,6 +348,8 @@ async function runInteractiveSetup(rootDir) {
265
348
  ...process.env,
266
349
  PORT: String(port),
267
350
  API_KEY: apiKey,
351
+ CODEX_CWD: workspacePath,
352
+ ...(openAiApiKey ? { OPENAI_API_KEY: openAiApiKey } : {}),
268
353
  },
269
354
  });
270
355