@masslessai/push-todo 3.7.1 → 3.7.2

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/lib/api.js CHANGED
@@ -298,28 +298,6 @@ export async function registerProject(gitRemote, keywords = [], description = ''
298
298
  return true;
299
299
  }
300
300
 
301
- /**
302
- * Validate machine registration.
303
- *
304
- * @param {string} machineId - Machine identifier
305
- * @returns {Promise<Object>} Validation result
306
- */
307
- export async function validateMachine(machineId) {
308
- const response = await apiRequest('validate-machine', {
309
- method: 'POST',
310
- body: JSON.stringify({
311
- machine_id: machineId
312
- })
313
- });
314
-
315
- if (!response.ok) {
316
- const text = await response.text();
317
- throw new Error(`Machine validation failed: ${text}`);
318
- }
319
-
320
- const data = await response.json();
321
- return data;
322
- }
323
301
 
324
302
  /**
325
303
  * Get the current CLI version from the server.
package/lib/cli.js CHANGED
@@ -89,7 +89,6 @@ ${bold('CONNECT OPTIONS:')}
89
89
  --check-version Check for updates (JSON output)
90
90
  --update Update to latest version
91
91
  --validate-key Validate API key (JSON output)
92
- --validate-machine Validate machine registration (JSON output)
93
92
  --validate-project Validate project registration (JSON output)
94
93
  --store-e2ee-key <key> Import E2EE encryption key
95
94
  --description <text> Project description (with connect)
@@ -134,7 +133,6 @@ const options = {
134
133
  'check-version': { type: 'boolean' },
135
134
  'update': { type: 'boolean' },
136
135
  'validate-key': { type: 'boolean' },
137
- 'validate-machine': { type: 'boolean' },
138
136
  'validate-project': { type: 'boolean' },
139
137
  'store-e2ee-key': { type: 'string' },
140
138
  'description': { type: 'string' }
package/lib/connect.js CHANGED
@@ -618,30 +618,6 @@ async function validateApiKeyStatus() {
618
618
  return { status: 'invalid', message: result.reason };
619
619
  }
620
620
 
621
- /**
622
- * Validate machine registration.
623
- */
624
- async function validateMachineStatus() {
625
- const machineId = getMachineId();
626
- const machineName = getMachineName();
627
-
628
- try {
629
- const result = await api.validateMachine(machineId);
630
- return {
631
- status: 'valid',
632
- machineId,
633
- machineName,
634
- ...result
635
- };
636
- } catch (error) {
637
- return {
638
- status: 'error',
639
- machineId,
640
- machineName,
641
- message: error.message
642
- };
643
- }
644
- }
645
621
 
646
622
  /**
647
623
  * Validate project registration (full validation with warnings).
@@ -742,11 +718,7 @@ function validateProjectStatus() {
742
718
  * Get device name for registration.
743
719
  */
744
720
  function getDeviceName() {
745
- try {
746
- return require('os').hostname() || 'Unknown Device';
747
- } catch {
748
- return 'Unknown Device';
749
- }
721
+ return getMachineName() || 'Unknown Device';
750
722
  }
751
723
 
752
724
  /**
@@ -1090,13 +1062,6 @@ export async function runConnect(options = {}) {
1090
1062
  return;
1091
1063
  }
1092
1064
 
1093
- // Handle --validate-machine (JSON output)
1094
- if (options['validate-machine'] || options.validateMachine) {
1095
- const result = await validateMachineStatus();
1096
- console.log(JSON.stringify(result, null, 2));
1097
- return;
1098
- }
1099
-
1100
1065
  // Handle --validate-project (JSON output)
1101
1066
  if (options['validate-project'] || options.validateProject) {
1102
1067
  const result = validateProjectStatus();
@@ -1191,13 +1156,8 @@ export async function runConnect(options = {}) {
1191
1156
  }
1192
1157
  }
1193
1158
 
1194
- // Validate and show machine ID
1195
- const machineInfo = await validateMachineStatus();
1196
- if (machineInfo.status === 'valid') {
1197
- console.log(` Machine: ${machineInfo.machineName}`);
1198
- } else {
1199
- console.log(` ⚠️ Machine ID: ${machineInfo.message}`);
1200
- }
1159
+ // Show machine info
1160
+ console.log(` Machine: ${getMachineName()}`);
1201
1161
  console.log(' ' + '='.repeat(40));
1202
1162
  console.log('');
1203
1163
 
@@ -1274,13 +1234,8 @@ export async function runConnect(options = {}) {
1274
1234
  }
1275
1235
  }
1276
1236
 
1277
- // Validate and show machine ID
1278
- const machineInfo = await validateMachineStatus();
1279
- if (machineInfo.status === 'valid') {
1280
- console.log(` Machine: ${machineInfo.machineName}`);
1281
- } else {
1282
- console.log(` ⚠️ Machine ID: ${machineInfo.message}`);
1283
- }
1237
+ // Show machine info
1238
+ console.log(` Machine: ${getMachineName()}`);
1284
1239
  console.log(' ' + '='.repeat(40));
1285
1240
  console.log('');
1286
1241
  console.log(' Your iOS app will sync this automatically.');
@@ -1297,7 +1252,6 @@ export {
1297
1252
  checkVersion,
1298
1253
  doUpdate,
1299
1254
  validateApiKeyStatus,
1300
- validateMachineStatus,
1301
1255
  validateProjectStatus,
1302
1256
  validateProjectInfo,
1303
1257
  setupE2EE,
@@ -197,7 +197,7 @@ function getInstalledVersion() {
197
197
  * Auto-restarts daemon if version mismatch detected.
198
198
  *
199
199
  * This prevents stale daemons from running after npm package updates.
200
- * See: /docs/20260204_daemon_heartbeat_status_indicator_implementation_plan.md
200
+ * See: /docs/20260204_daemon_heartbeat_status_indicator_implementation_plan.md (machine_registry table)
201
201
  */
202
202
  export function ensureDaemonRunning() {
203
203
  const status = getDaemonStatus();
package/lib/daemon.js CHANGED
@@ -297,8 +297,8 @@ async function fetchQueuedTasks() {
297
297
  const projects = getListedProjects();
298
298
  const gitRemotes = Object.keys(projects);
299
299
 
300
- // Add heartbeat headers for daemon status tracking
301
- // See: /docs/20260204_daemon_heartbeat_status_indicator_implementation_plan.md
300
+ // Add machine registry headers for daemon status tracking
301
+ // See: /docs/20260204_daemon_heartbeat_status_indicator_implementation_plan.md (machine_registry table)
302
302
  const heartbeatHeaders = {};
303
303
  if (machineId && gitRemotes.length > 0) {
304
304
  heartbeatHeaders['X-Machine-Id'] = machineId;
@@ -711,6 +711,43 @@ function extractSessionIdFromStdout(proc, buffer) {
711
711
  return null;
712
712
  }
713
713
 
714
+ // ==================== Semantic Summary Extraction ====================
715
+
716
+ /**
717
+ * Generate a semantic summary by resuming the Claude session in --print mode.
718
+ * Claude already has full context of what it did — we just ask it to summarize.
719
+ * Uses execFileSync (not execSync) to avoid shell injection.
720
+ *
721
+ * @param {string} worktreePath - Path to the git worktree where Claude ran
722
+ * @param {string} sessionId - Claude session ID
723
+ * @returns {string|null} A short semantic summary, or null if extraction fails
724
+ */
725
+ function extractSemanticSummary(worktreePath, sessionId) {
726
+ if (!worktreePath || !sessionId) return null;
727
+
728
+ try {
729
+ const result = execFileSync('claude', [
730
+ '--resume', sessionId,
731
+ '--print',
732
+ 'Summarize what you accomplished in 1-2 sentences. Be specific about outcomes (what was built, fixed, drafted), not process. If you failed or got stuck, explain what went wrong. If no code changes were made, say so. Do not use markdown.'
733
+ ], {
734
+ cwd: worktreePath,
735
+ timeout: 30000,
736
+ stdio: ['ignore', 'pipe', 'pipe']
737
+ });
738
+
739
+ const summary = result.toString().trim();
740
+ if (summary && summary.length > 0) {
741
+ // Cap at 300 chars to keep it concise
742
+ return summary.length > 300 ? summary.slice(0, 297) + '...' : summary;
743
+ }
744
+ return null;
745
+ } catch (error) {
746
+ log(`Semantic summary extraction failed: ${error.message}`);
747
+ return null;
748
+ }
749
+ }
750
+
714
751
  // ==================== Task Execution ====================
715
752
 
716
753
  function updateTaskDetail(displayNumber, updates) {
@@ -918,24 +955,32 @@ function handleTaskCompletion(displayNumber, exitCode) {
918
955
 
919
956
  log(`Task #${displayNumber} completed with code ${exitCode} (${duration}s)`);
920
957
 
921
- if (exitCode === 0) {
922
- // Extract session ID
923
- const buffer = taskStdoutBuffer.get(displayNumber) || [];
924
- const sessionId = extractSessionIdFromStdout(taskInfo.process, buffer);
958
+ // Extract session ID for both success and failure (needed for AI summary)
959
+ const buffer = taskStdoutBuffer.get(displayNumber) || [];
960
+ const sessionId = extractSessionIdFromStdout(taskInfo.process, buffer);
961
+ const worktreePath = getWorktreePath(displayNumber, projectPath);
962
+ const durationStr = duration < 60 ? `${duration}s` : `${Math.floor(duration / 60)}m ${duration % 60}s`;
963
+ const machineName = getMachineName() || 'Mac';
925
964
 
926
- if (sessionId) {
927
- log(`Task #${displayNumber} session_id: ${sessionId}`);
928
- } else {
929
- log(`Task #${displayNumber} could not extract session_id`);
930
- }
965
+ if (sessionId) {
966
+ log(`Task #${displayNumber} session_id: ${sessionId}`);
967
+ } else {
968
+ log(`Task #${displayNumber} could not extract session_id`);
969
+ }
931
970
 
971
+ if (exitCode === 0) {
932
972
  // Auto-create PR first so we can include it in the summary
933
973
  const prUrl = createPRForTask(displayNumber, summary, projectPath);
934
974
 
935
- // Build execution summary for Supabase (shown in iOS timeline)
936
- const durationStr = duration < 60 ? `${duration}s` : `${Math.floor(duration / 60)}m ${duration % 60}s`;
937
- const machineName = getMachineName() || 'Mac';
938
- let executionSummary = `Ran for ${durationStr} on ${machineName}.`;
975
+ // Ask Claude to summarize what it accomplished
976
+ const semanticSummary = extractSemanticSummary(worktreePath, sessionId);
977
+
978
+ // Combine: semantic summary first (what), then machine metadata (how)
979
+ let executionSummary = '';
980
+ if (semanticSummary) {
981
+ executionSummary = semanticSummary + '\n';
982
+ }
983
+ executionSummary += `Ran for ${durationStr} on ${machineName}.`;
939
984
  if (prUrl) {
940
985
  executionSummary += ` PR: ${prUrl}`;
941
986
  }
@@ -966,7 +1011,13 @@ function handleTaskCompletion(displayNumber, exitCode) {
966
1011
  });
967
1012
  } else {
968
1013
  const stderr = taskInfo.process.stderr?.read()?.toString() || '';
969
- const errorMsg = `Exit code ${exitCode}: ${stderr.slice(0, 200)}`;
1014
+
1015
+ // Ask Claude to explain what went wrong (if session exists)
1016
+ const failureSummary = extractSemanticSummary(worktreePath, sessionId);
1017
+ const errorMsg = failureSummary
1018
+ ? `${failureSummary}\nExit code ${exitCode}. Ran for ${durationStr} on ${machineName}.`
1019
+ : `Exit code ${exitCode}: ${stderr.slice(0, 200)}`;
1020
+
970
1021
  updateTaskStatus(displayNumber, 'failed', { error: errorMsg });
971
1022
 
972
1023
  if (NOTIFY_ON_FAILURE) {
package/lib/index.js CHANGED
@@ -39,7 +39,6 @@ export {
39
39
  updateTaskExecution,
40
40
  validateApiKey,
41
41
  registerProject,
42
- validateMachine,
43
42
  getLatestVersion
44
43
  } from './api.js';
45
44
 
package/lib/machine-id.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * File location: ~/.config/push/machine_id
10
10
  */
11
11
 
12
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
12
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
13
13
  import { homedir, hostname, platform, release, version, arch } from 'os';
14
14
  import { join, dirname } from 'path';
15
15
  import { randomUUID } from 'crypto';
@@ -90,7 +90,6 @@ export function getMachineInfo() {
90
90
  export function resetMachineId() {
91
91
  if (existsSync(MACHINE_ID_FILE)) {
92
92
  try {
93
- const { unlinkSync } = require('fs');
94
93
  unlinkSync(MACHINE_ID_FILE);
95
94
  } catch {
96
95
  // Ignore errors
@@ -148,6 +148,16 @@ export function formatTaskForDisplay(task) {
148
148
  lines.push('**Status:** Active');
149
149
  }
150
150
 
151
+ // Show execution summary (semantic + machine metadata)
152
+ const execSummary = task.executionSummary || task.execution_summary;
153
+ if (execSummary) {
154
+ lines.push('');
155
+ lines.push('### What was done');
156
+ for (const line of execSummary.split('\n').filter(Boolean)) {
157
+ lines.push(`> ${line}`);
158
+ }
159
+ }
160
+
151
161
  // Show session resume hint for any task with a session ID
152
162
  const sessionId = task.executionSessionId || task.execution_session_id;
153
163
  if (sessionId && displayNum) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "3.7.1",
3
+ "version": "3.7.2",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {