From 67618013ce10ed7938f3480ef600a5cdb98f34a2 Mon Sep 17 00:00:00 2001 From: konard Date: Thu, 23 Apr 2026 10:13:02 +0000 Subject: [PATCH 1/2] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/link-foundation/start/issues/105 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..76fba8a --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-04-23T10:13:02.568Z for PR creation at branch issue-105-0edd650b7149 for issue https://github.com/link-foundation/start/issues/105 \ No newline at end of file From 038b6dbc10562cc34578c06ca9a177282339b49b Mon Sep 17 00:00:00 2001 From: konard Date: Thu, 23 Apr 2026 10:50:04 +0000 Subject: [PATCH 2/2] feat(js): add currentTime to --status output for executing commands Adds a `currentTime` field to the `--status` output (links-notation, JSON, and text) whenever the execution is still `executing`. This surfaces the moment the query was made so users can easily compute how long a command has been running from `startTime`, which is what issue #105 asks for. Resolves #105 --- .gitkeep | 1 - js/.changeset/issue-105-current-time.md | 5 ++ js/src/lib/status-formatter.js | 53 +++++++++++++- js/test/session-name-status.test.js | 69 ++++++++++++++++++ js/test/status-query.test.js | 96 +++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 4 deletions(-) delete mode 100644 .gitkeep create mode 100644 js/.changeset/issue-105-current-time.md diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index 76fba8a..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-04-23T10:13:02.568Z for PR creation at branch issue-105-0edd650b7149 for issue https://github.com/link-foundation/start/issues/105 \ No newline at end of file diff --git a/js/.changeset/issue-105-current-time.md b/js/.changeset/issue-105-current-time.md new file mode 100644 index 0000000..7787f1f --- /dev/null +++ b/js/.changeset/issue-105-current-time.md @@ -0,0 +1,5 @@ +--- +'start-command': patch +--- + +Add `currentTime` to `--status` output when an execution is still `executing`, so users can see the query time alongside `startTime` and compute how long a command has been running. diff --git a/js/src/lib/status-formatter.js b/js/src/lib/status-formatter.js index 9786e80..fa38806 100644 --- a/js/src/lib/status-formatter.js +++ b/js/src/lib/status-formatter.js @@ -126,6 +126,45 @@ function enrichDetachedStatus(record) { return enriched; } +/** + * Wrap a record so its serialized form includes a `currentTime` field when + * the status is "executing". This reflects the moment `--status` was invoked, + * making it easy to compute how long a command has been running. + * The original record is not mutated. + * @param {Object} record - Execution record + * @returns {Object} Record-like object with `toObject()` augmented when executing + */ +function attachCurrentTime(record) { + if (!record || record.status !== 'executing') { + return record; + } + + const currentTime = new Date().toISOString(); + const wrapped = Object.create(Object.getPrototypeOf(record)); + Object.assign(wrapped, record); + wrapped.currentTime = currentTime; + wrapped.toObject = function () { + const base = + typeof record.toObject === 'function' + ? record.toObject.call(this) + : { ...this }; + // Insert currentTime right after startTime for readability + const ordered = {}; + for (const [key, value] of Object.entries(base)) { + ordered[key] = value; + if (key === 'startTime') { + ordered.currentTime = currentTime; + } + } + if (!('currentTime' in ordered)) { + ordered.currentTime = currentTime; + } + return ordered; + }; + + return wrapped; +} + /** * Format execution record as Links Notation (indented style) * Uses nested Links notation for object values (like options) instead of JSON @@ -192,9 +231,14 @@ function formatRecordAsText(record) { `Shell: ${obj.shell}`, `Platform: ${obj.platform}`, `Start Time: ${obj.startTime}`, - `End Time: ${obj.endTime || 'N/A'}`, - `Log Path: ${obj.logPath}`, ]; + if (obj.currentTime) { + lines.push(`Current Time: ${obj.currentTime}`); + } + lines.push( + `End Time: ${obj.endTime || 'N/A'}`, + `Log Path: ${obj.logPath}` + ); // Format options as nested list instead of JSON const optionEntries = Object.entries(obj.options || {}).filter( @@ -250,9 +294,11 @@ function queryStatus(store, identifier, outputFormat) { try { // Enrich detached execution status with live session check const enrichedRecord = enrichDetachedStatus(record); + // Attach currentTime so callers can see how long an executing command has been running + const withCurrentTime = attachCurrentTime(enrichedRecord); return { success: true, - output: formatRecord(enrichedRecord, outputFormat || 'links-notation'), + output: formatRecord(withCurrentTime, outputFormat || 'links-notation'), }; } catch (err) { return { success: false, error: err.message }; @@ -266,4 +312,5 @@ module.exports = { queryStatus, isDetachedSessionAlive, enrichDetachedStatus, + attachCurrentTime, }; diff --git a/js/test/session-name-status.test.js b/js/test/session-name-status.test.js index 08efaac..19397b4 100644 --- a/js/test/session-name-status.test.js +++ b/js/test/session-name-status.test.js @@ -18,6 +18,7 @@ const { queryStatus, isDetachedSessionAlive, enrichDetachedStatus, + attachCurrentTime, } = require('../src/lib/status-formatter'); // Use temp directory for tests @@ -308,4 +309,72 @@ describe('Issue #101: Detached status enrichment', () => { }); }); +describe('Issue #105: attachCurrentTime for executing status', () => { + it('should add currentTime to serialization when status is executing', () => { + const record = new ExecutionRecord({ + command: 'sleep 60', + pid: 12345, + logPath: '/tmp/test.log', + }); + + const before = Date.now(); + const wrapped = attachCurrentTime(record); + const obj = wrapped.toObject(); + const after = Date.now(); + + expect(obj.currentTime).toBeDefined(); + const currentTimeMs = new Date(obj.currentTime).getTime(); + expect(Number.isNaN(currentTimeMs)).toBe(false); + expect(currentTimeMs).toBeGreaterThanOrEqual(before - 1); + expect(currentTimeMs).toBeLessThanOrEqual(after + 1); + }); + + it('should not add currentTime when status is executed', () => { + const record = new ExecutionRecord({ + command: 'echo hello', + pid: 12345, + logPath: '/tmp/test.log', + }); + record.complete(0); + + const wrapped = attachCurrentTime(record); + // attachCurrentTime should return the original record unchanged + expect(wrapped).toBe(record); + const obj = wrapped.toObject(); + expect(obj.currentTime).toBeUndefined(); + }); + + it('should not mutate the original record', () => { + const record = new ExecutionRecord({ + command: 'sleep 60', + pid: 12345, + logPath: '/tmp/test.log', + }); + + const wrapped = attachCurrentTime(record); + expect(wrapped).not.toBe(record); + // The original record's toObject output should not include currentTime + const originalObj = record.toObject(); + expect(originalObj.currentTime).toBeUndefined(); + }); + + it('should place currentTime right after startTime in serialization order', () => { + const record = new ExecutionRecord({ + command: 'sleep 60', + pid: 12345, + logPath: '/tmp/test.log', + }); + + const wrapped = attachCurrentTime(record); + const keys = Object.keys(wrapped.toObject()); + const startIndex = keys.indexOf('startTime'); + expect(startIndex).toBeGreaterThanOrEqual(0); + expect(keys[startIndex + 1]).toBe('currentTime'); + }); + + it('should handle null record gracefully', () => { + expect(attachCurrentTime(null)).toBeNull(); + }); +}); + console.log('=== Session Name Status Tests (Issue #101) ==='); diff --git a/js/test/status-query.test.js b/js/test/status-query.test.js index 862fc31..c825c31 100644 --- a/js/test/status-query.test.js +++ b/js/test/status-query.test.js @@ -191,6 +191,102 @@ describe('--status query functionality', () => { expect(parsed.exitCode).toBeNull(); expect(parsed.endTime).toBeNull(); }); + + it('should include currentTime for executing commands (JSON)', () => { + const beforeQuery = Date.now(); + const executingRecord = new ExecutionRecord({ + command: 'sleep 100', + pid: 99999, + logPath: '/tmp/executing.log', + }); + store.save(executingRecord); + + const result = runCli([ + '--status', + executingRecord.uuid, + '--output-format', + 'json', + ]); + + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + expect(parsed.currentTime).toBeDefined(); + // currentTime should be a valid ISO timestamp at or after the query started + const currentTimeMs = new Date(parsed.currentTime).getTime(); + expect(Number.isNaN(currentTimeMs)).toBe(false); + expect(currentTimeMs).toBeGreaterThanOrEqual(beforeQuery - 1); + expect(currentTimeMs).toBeLessThanOrEqual(Date.now() + 1); + // Should be >= startTime + expect(currentTimeMs).toBeGreaterThanOrEqual( + new Date(parsed.startTime).getTime() + ); + }); + + it('should not include currentTime for completed commands (JSON)', () => { + // testRecord from beforeEach is already completed + const result = runCli([ + '--status', + testRecord.uuid, + '--output-format', + 'json', + ]); + + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + expect(parsed.status).toBe('executed'); + expect(parsed.currentTime).toBeUndefined(); + }); + + it('should include currentTime in links-notation for executing commands', () => { + const executingRecord = new ExecutionRecord({ + command: 'sleep 100', + pid: 99999, + logPath: '/tmp/executing.log', + }); + store.save(executingRecord); + + const result = runCli(['--status', executingRecord.uuid]); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('status executing'); + // currentTime should appear as an indented property with an ISO timestamp value + expect(result.stdout).toMatch( + /\n {2}currentTime "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ + ); + }); + + it('should include Current Time in text format for executing commands', () => { + const executingRecord = new ExecutionRecord({ + command: 'sleep 100', + pid: 99999, + logPath: '/tmp/executing.log', + }); + store.save(executingRecord); + + const result = runCli([ + '--status', + executingRecord.uuid, + '--output-format', + 'text', + ]); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('Status:'); + expect(result.stdout).toContain('executing'); + expect(result.stdout).toContain('Current Time:'); + }); + + it('should not include Current Time in text format for completed commands', () => { + const result = runCli([ + '--status', + testRecord.uuid, + '--output-format', + 'text', + ]); + + expect(result.exitCode).toBe(0); + expect(result.stdout).not.toContain('Current Time:'); + }); }); });