taskmonkey-cli 0.10.1 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskmonkey-cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "TaskMonkey CLI — Remote dev tools for tenant config editing and tool testing",
5
5
  "bin": {
6
6
  "tm": "./bin/tm.js",
@@ -1,15 +1,21 @@
1
- import { readdirSync, readFileSync } from 'fs';
2
- import { join, relative } from 'path';
1
+ import { readdirSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { join, relative, dirname } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
5
  import { loadConfig } from '../config.js';
6
6
  import { createClient } from '../lib/api.js';
7
7
 
8
+ // Skip runtime dirs and the local CLI metadata when collecting files to upload.
9
+ const SKIP_TOP_LEVEL = new Set(['logs', 'tmp', '.claude', '.git', 'node_modules', 'docs', 'shared']);
10
+
8
11
  function collectFiles(dir, base = dir) {
9
12
  const files = {};
10
13
  const entries = readdirSync(dir, { withFileTypes: true });
11
14
 
12
15
  for (const entry of entries) {
16
+ if (dir === base && SKIP_TOP_LEVEL.has(entry.name)) {
17
+ continue;
18
+ }
13
19
  const fullPath = join(dir, entry.name);
14
20
  if (entry.isDirectory()) {
15
21
  Object.assign(files, collectFiles(fullPath, base));
@@ -22,6 +28,55 @@ function collectFiles(dir, base = dir) {
22
28
  return files;
23
29
  }
24
30
 
31
+ /**
32
+ * Pulls logs/ and tmp/ from the server into the local working copy so the
33
+ * developer can read them with normal file tools (cat, grep, Claude, etc.).
34
+ *
35
+ * Text files come back as utf-8 strings; binary files come back as base64
36
+ * under `binary_files`. We write both verbatim.
37
+ *
38
+ * Failures here are non-fatal — the sync itself was already successful.
39
+ */
40
+ export async function pullRuntime(tenantDir, { quiet = false } = {}) {
41
+ const client = createClient();
42
+ let runtime;
43
+ try {
44
+ runtime = await client.get('/api/tenant/runtime');
45
+ } catch (err) {
46
+ if (!quiet) {
47
+ console.log(chalk.gray(` (runtime pull skipped: ${err.message})`));
48
+ }
49
+ return 0;
50
+ }
51
+
52
+ let written = 0;
53
+ const writeOne = (relPath, content, isBase64) => {
54
+ const fullPath = join(tenantDir, relPath);
55
+ mkdirSync(dirname(fullPath), { recursive: true });
56
+ if (isBase64) {
57
+ writeFileSync(fullPath, Buffer.from(content, 'base64'));
58
+ } else {
59
+ writeFileSync(fullPath, content);
60
+ }
61
+ written++;
62
+ };
63
+
64
+ // Some servers serialize empty PHP arrays as JSON [] instead of {}.
65
+ // Treat plain arrays as empty since they have no path-keyed entries.
66
+ const asMap = (v) => (v && typeof v === 'object' && !Array.isArray(v)) ? v : {};
67
+ for (const [relPath, content] of Object.entries(asMap(runtime.files))) {
68
+ writeOne(relPath, content, false);
69
+ }
70
+ for (const [relPath, content] of Object.entries(asMap(runtime.binary_files))) {
71
+ writeOne(relPath, content, true);
72
+ }
73
+
74
+ if (written > 0 && !quiet) {
75
+ console.log(chalk.gray(` ↓ ${written} runtime files (logs/, tmp/)`));
76
+ }
77
+ return written;
78
+ }
79
+
25
80
  export async function sync(options = {}) {
26
81
  const config = loadConfig();
27
82
  if (!config) {
@@ -88,6 +143,9 @@ export async function sync(options = {}) {
88
143
  console.log(chalk.red(` ${err.path}: ${err.error}`));
89
144
  }
90
145
  }
146
+
147
+ // Mirror server-side logs/ and tmp/ into the working copy for debugging.
148
+ await pullRuntime(tenantDir);
91
149
  } catch (err) {
92
150
  spinner.fail(err.message);
93
151
  process.exit(1);
@@ -20,9 +20,19 @@ export async function watch() {
20
20
  let syncTimer = null;
21
21
  const pendingDeletes = [];
22
22
 
23
+ // Watch only .php files, and explicitly ignore the runtime dirs that
24
+ // sync() writes back into the working copy — otherwise pullRuntime would
25
+ // re-trigger sync in an endless loop.
23
26
  const watcher = chokidar.watch(join(tenantDir, '**/*.php'), {
24
27
  ignoreInitial: true,
25
28
  awaitWriteFinish: { stabilityThreshold: 300 },
29
+ ignored: [
30
+ /(^|[\/\\])logs[\/\\]/,
31
+ /(^|[\/\\])tmp[\/\\]/,
32
+ /(^|[\/\\])\.claude[\/\\]/,
33
+ /(^|[\/\\])\.git[\/\\]/,
34
+ /(^|[\/\\])node_modules[\/\\]/,
35
+ ],
26
36
  });
27
37
 
28
38
  watcher.on('add', (path) => {