From 0d38c1b4714213e762bcf4d21ffef01fa435b327 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sun, 16 Mar 2025 17:10:42 +0100 Subject: [PATCH] ... --- .../ai_instruct/generate_player_for_models.md | 39 + lib/circles/actions/openapi.yaml | 109 +- lib/circles/actions/openapi.yaml.new | 935 ------------------ lib/circles/actions/play/create.v | 82 ++ lib/circles/actions/play/delete.v | 36 + lib/circles/actions/play/get.v | 41 + lib/circles/actions/play/list.v | 38 + lib/circles/actions/play/play_jobs.vsh | 61 ++ lib/circles/actions/play/player.v | 84 ++ lib/circles/actions/play/update_status.v | 64 ++ lib/circles/actions/{specs.v => specs.v_} | 30 +- 11 files changed, 539 insertions(+), 980 deletions(-) create mode 100644 aiprompts/ai_instruct/generate_player_for_models.md delete mode 100644 lib/circles/actions/openapi.yaml.new create mode 100644 lib/circles/actions/play/create.v create mode 100644 lib/circles/actions/play/delete.v create mode 100644 lib/circles/actions/play/get.v create mode 100644 lib/circles/actions/play/list.v create mode 100644 lib/circles/actions/play/play_jobs.vsh create mode 100644 lib/circles/actions/play/player.v create mode 100644 lib/circles/actions/play/update_status.v rename lib/circles/actions/{specs.v => specs.v_} (79%) diff --git a/aiprompts/ai_instruct/generate_player_for_models.md b/aiprompts/ai_instruct/generate_player_for_models.md new file mode 100644 index 00000000..7cf3aa50 --- /dev/null +++ b/aiprompts/ai_instruct/generate_player_for_models.md @@ -0,0 +1,39 @@ +generate specs for /Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions + +use mcp + +get the output of it un actions/specs.v + +then use these specs.v + +to generate play command instructions see @3_heroscript_vlang.md + +this play command gets heroscript in and will then call the methods for actions as are ONLY in @lib/circles/actions/db + +so the play only calls the methods in @lib/circles/actions/db + + +# put the play commands in + +/Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions/play + +do one file in the module per action + +each method is an action + +put them all on one Struct called Player +in this Player we have a method per action + +Player has a property called actor: which is the name of the actor as is used in the heroscript +Player has also a output called return format which is enum for heroscript or json + +input of the method - action is a params object + +on player there is a method play which takes the text as input or playbook + +if text then playbook is created + +then we walk over all actions + +all the ones starting with actions in this case are given to the right method + diff --git a/lib/circles/actions/openapi.yaml b/lib/circles/actions/openapi.yaml index 3e5e9d72..dc9e1df6 100644 --- a/lib/circles/actions/openapi.yaml +++ b/lib/circles/actions/openapi.yaml @@ -147,7 +147,25 @@ paths: status: "ok" dependencies: [] '500': - $ref: '#/components/responses/InternalServerError' + description: Internal server error + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + examples: + internalServerErrorExample: + value: + code: 500 + message: "Internal server error" /jobs/{id}: get: @@ -259,7 +277,25 @@ paths: schema: $ref: '#/components/schemas/Job' '400': - $ref: '#/components/responses/BadRequest' + description: Bad request + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + examples: + badRequestExample: + value: + code: 400 + message: "Invalid request parameters" '404': $ref: '#/components/responses/NotFound' '500': @@ -396,7 +432,25 @@ paths: schema: $ref: '#/components/schemas/Job' '400': - $ref: '#/components/responses/BadRequest' + description: Bad request + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + examples: + badRequestExample: + value: + code: 400 + message: "Invalid request parameters" '404': $ref: '#/components/responses/NotFound' '500': @@ -609,23 +663,28 @@ components: description: The public keys of the agent(s) which can execute the command items: type: string - + + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + description: Error code + message: + type: string + description: Error message + responses: BadRequest: description: Bad request content: application/json: schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string + $ref: '#/components/schemas/Error' examples: badRequestExample: value: @@ -637,16 +696,7 @@ components: content: application/json: schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string + $ref: '#/components/schemas/Error' examples: notFoundExample: value: @@ -658,16 +708,7 @@ components: content: application/json: schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string + $ref: '#/components/schemas/Error' examples: internalServerErrorExample: value: diff --git a/lib/circles/actions/openapi.yaml.new b/lib/circles/actions/openapi.yaml.new deleted file mode 100644 index 5aef2e19..00000000 --- a/lib/circles/actions/openapi.yaml.new +++ /dev/null @@ -1,935 +0,0 @@ -openapi: 3.1.0 -info: - title: HeroLib Circles API - description: API for managing jobs and actions in the HeroLib Circles module - version: 1.0.0 - contact: - name: FreeFlow Universe - url: https://github.com/freeflowuniverse/herolib - -servers: - - url: /api/v1 - description: Default API server - -paths: - /jobs: - get: - summary: List all jobs - description: Returns all job IDs in the system - operationId: listJobs - tags: - - jobs - responses: - '200': - description: A list of job IDs - content: - application/json: - schema: - type: array - items: - type: integer - format: int32 - examples: - listJobsExample: - value: [1, 2, 3, 4, 5] - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - post: - summary: Create a new job - description: Creates a new job in the system - operationId: createJob - tags: - - jobs - requestBody: - description: Job object to be created - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/JobCreate' - examples: - createJobExample: - value: - agents: ["agent1pubkey", "agent2pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "start" - params: - id: "10" - name: "test-vm" - timeout_schedule: 60 - timeout: 3600 - log: true - ignore_error: false - ignore_error_codes: [] - debug: false - retry: 0 - dependencies: [] - responses: - '201': - description: Job created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - '400': - description: Bad request - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - badRequestExample: - value: - code: 400 - message: "Invalid request parameters" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - - /jobs/all: - get: - summary: Get all jobs - description: Returns all jobs in the system - operationId: getAllJobs - tags: - - jobs - responses: - '200': - description: A list of jobs - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Job' - examples: - getAllJobsExample: - value: - - id: 1 - guid: "job-guid-1" - agents: ["agent1pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "start" - params: - id: "10" - timeout_schedule: 60 - timeout: 3600 - log: true - ignore_error: false - ignore_error_codes: [] - debug: false - retry: 0 - status: - guid: "job-guid-1" - created: "2025-03-16T13:20:30Z" - start: "2025-03-16T13:21:00Z" - end: "2025-03-16T13:25:45Z" - status: "ok" - dependencies: [] - - id: 2 - guid: "job-guid-2" - agents: ["agent2pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "stop" - params: - id: "11" - timeout_schedule: 60 - timeout: 3600 - log: true - ignore_error: false - ignore_error_codes: [] - debug: false - retry: 0 - status: - guid: "job-guid-2" - created: "2025-03-16T14:10:30Z" - start: "2025-03-16T14:11:00Z" - end: "2025-03-16T14:12:45Z" - status: "ok" - dependencies: [] - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - - /jobs/{id}: - get: - summary: Get a job by ID - description: Returns a job by its numeric ID - operationId: getJobById - tags: - - jobs - parameters: - - name: id - in: path - description: Job ID - required: true - schema: - type: integer - format: int32 - responses: - '200': - description: Job found - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - examples: - getJobByIdExample: - value: - id: 1 - guid: "job-guid-1" - agents: ["agent1pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "start" - params: - id: "10" - timeout_schedule: 60 - timeout: 3600 - log: true - ignore_error: false - ignore_error_codes: [] - debug: false - retry: 0 - status: - guid: "job-guid-1" - created: "2025-03-16T13:20:30Z" - start: "2025-03-16T13:21:00Z" - end: "2025-03-16T13:25:45Z" - status: "ok" - dependencies: [] - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - put: - summary: Update a job - description: Updates an existing job - operationId: updateJob - tags: - - jobs - parameters: - - name: id - in: path - description: Job ID - required: true - schema: - type: integer - format: int32 - requestBody: - description: Job object to update - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - examples: - updateJobExample: - value: - id: 1 - guid: "job-guid-1" - agents: ["agent1pubkey", "agent3pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "restart" - params: - id: "10" - force: "true" - timeout_schedule: 30 - timeout: 1800 - log: true - ignore_error: true - ignore_error_codes: [404] - debug: true - retry: 2 - status: - guid: "job-guid-1" - created: "2025-03-16T13:20:30Z" - start: "2025-03-16T13:21:00Z" - end: "2025-03-16T13:25:45Z" - status: "ok" - dependencies: [] - responses: - '200': - description: Job updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - '400': - description: Bad request - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - badRequestExample: - value: - code: 400 - message: "Invalid request parameters" - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - delete: - summary: Delete a job - description: Deletes a job by its ID - operationId: deleteJob - tags: - - jobs - parameters: - - name: id - in: path - description: Job ID - required: true - schema: - type: integer - format: int32 - responses: - '204': - description: Job deleted successfully - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - - /jobs/guid/{guid}: - get: - summary: Get a job by GUID - description: Returns a job by its GUID - operationId: getJobByGuid - tags: - - jobs - parameters: - - name: guid - in: path - description: Job GUID - required: true - schema: - type: string - responses: - '200': - description: Job found - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - examples: - getJobByGuidExample: - value: - id: 1 - guid: "job-guid-1" - agents: ["agent1pubkey"] - source: "sourcepubkey" - circle: "default" - context: "default" - actor: "vm_manager" - action: "start" - params: - id: "10" - timeout_schedule: 60 - timeout: 3600 - log: true - ignore_error: false - ignore_error_codes: [] - debug: false - retry: 0 - status: - guid: "job-guid-1" - created: "2025-03-16T13:20:30Z" - start: "2025-03-16T13:21:00Z" - end: "2025-03-16T13:25:45Z" - status: "ok" - dependencies: [] - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - delete: - summary: Delete a job by GUID - description: Deletes a job by its GUID - operationId: deleteJobByGuid - tags: - - jobs - parameters: - - name: guid - in: path - description: Job GUID - required: true - schema: - type: string - responses: - '204': - description: Job deleted successfully - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - - /jobs/guid/{guid}/status: - put: - summary: Update job status - description: Updates the status of a job by its GUID - operationId: updateJobStatus - tags: - - jobs - parameters: - - name: guid - in: path - description: Job GUID - required: true - schema: - type: string - requestBody: - description: New job status - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/JobStatus' - examples: - updateJobStatusExample: - value: - guid: "job-guid-1" - created: "2025-03-16T13:20:30Z" - start: "2025-03-16T13:21:00Z" - end: "2025-03-16T13:30:45Z" - status: "running" - responses: - '200': - description: Job status updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Job' - '400': - description: Bad request - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - badRequestExample: - value: - code: 400 - message: "Invalid request parameters" - '404': - description: Resource not found - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - notFoundExample: - value: - code: 404 - message: "Job not found" - '500': - description: Internal server error - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - examples: - internalServerErrorExample: - value: - code: 500 - message: "Internal server error" - -components: - schemas: - Job: - type: object - required: - - id - - guid - - agents - - source - - actor - - action - - status - properties: - id: - type: integer - format: int32 - description: Unique numeric ID for the job - guid: - type: string - description: Unique ID for the job - agents: - type: array - description: The public keys of the agent(s) which will execute the command - items: - type: string - source: - type: string - description: Public key from the agent who asked for the job - circle: - type: string - description: Circle in which the job is organized - default: default - context: - type: string - description: High level context in which actors will execute the work inside a circle - default: default - actor: - type: string - description: The actor that will execute the job (e.g. vm_manager) - action: - type: string - description: The action to be executed (e.g. start) - params: - type: object - description: Parameters for the job (e.g. id:10) - additionalProperties: - type: string - timeout_schedule: - type: integer - format: int32 - description: Timeout before the job is picked up (in seconds) - default: 60 - timeout: - type: integer - format: int32 - description: Timeout for job execution (in seconds) - default: 3600 - log: - type: boolean - description: Whether to log job execution - default: true - ignore_error: - type: boolean - description: If true, errors will be ignored and not reported - default: false - ignore_error_codes: - type: array - description: Error codes to ignore - items: - type: integer - format: int32 - debug: - type: boolean - description: If true, more context will be provided for debugging - default: false - retry: - type: integer - format: int32 - description: Number of retries for the job - default: 0 - status: - $ref: '#/components/schemas/JobStatus' - dependencies: - type: array - description: Jobs that must be completed before this job can execute - items: - $ref: '#/components/schemas/JobDependency' - - JobCreate: - type: object - required: - - agents - - source - - actor - - action - properties: - agents: - type: array - description: The public keys of the agent(s) which will execute the command - items: - type: string - source: - type: string - description: Public key from the agent who asked for the job - circle: - type: string - description: Circle in which the job is organized - default: default - context: - type: string - description: High level context in which actors will execute the work inside a circle - default: default - actor: - type: string - description: The actor that will execute the job (e.g. vm_manager) - action: - type: string - description: The action to be executed (e.g. start) - params: - type: object - description: Parameters for the job (e.g. id:10) - additionalProperties: - type: string - timeout_schedule: - type: integer - format: int32 - description: Timeout before the job is picked up (in seconds) - default: 60 - timeout: - type: integer - format: int32 - description: Timeout for job execution (in seconds) - default: 3600 - log: - type: boolean - description: Whether to log job execution - default: true - ignore_error: - type: boolean - description: If true, errors will be ignored and not reported - default: false - ignore_error_codes: - type: array - description: Error codes to ignore - items: - type: integer - format: int32 - debug: - type: boolean - description: If true, more context will be provided for debugging - default: false - retry: - type: integer - format: int32 - description: Number of retries for the job - default: 0 - dependencies: - type: array - description: Jobs that must be completed before this job can execute - items: - $ref: '#/components/schemas/JobDependency' - - JobStatus: - type: object - required: - - guid - - status - properties: - guid: - type: string - description: Unique ID for the job - created: - type: string - format: date-time - description: When the job was created - start: - type: string - format: date-time - description: When the job started or should start - end: - type: string - format: date-time - description: When the job ended - status: - type: string - description: Current status of the job - enum: - - created - - scheduled - - planned - - running - - error - - ok - - JobDependency: - type: object - required: - - guid - properties: - guid: - type: string - description: Unique ID for the dependent job - agents: - type: array - description: The public keys of the agent(s) which can execute the command - items: - type: string diff --git a/lib/circles/actions/play/create.v b/lib/circles/actions/play/create.v new file mode 100644 index 00000000..7f32a70c --- /dev/null +++ b/lib/circles/actions/play/create.v @@ -0,0 +1,82 @@ +module play + +import freeflowuniverse.herolib.data.ourtime +import freeflowuniverse.herolib.circles.actions.models { Job, JobStatus, Status } +import freeflowuniverse.herolib.data.paramsparser +import crypto.rand +import encoding.hex + +// create processes a job creation action +pub fn (mut p Player) create(params paramsparser.Params) ! { + // Create a new job + mut job := p.job_db.new() + + // Set job properties from parameters + job.guid = params.get_default('guid', generate_random_id()!)! + job.actor = params.get_default('actor', '')! + job.action = params.get_default('action', '')! + job.circle = params.get_default('circle', 'default')! + job.context = params.get_default('context', 'default')! + + // Set agents if provided + if params.exists('agents') { + job.agents = params.get_list('agents')! + } + + // Set source if provided + if params.exists('source') { + job.source = params.get('source')! + } + + // Set timeouts if provided + if params.exists('timeout_schedule') { + job.timeout_schedule = u16(params.get_int('timeout_schedule')!) + } + + if params.exists('timeout') { + job.timeout = u16(params.get_int('timeout')!) + } + + // Set flags + job.log = params.get_default_true('log') + job.ignore_error = params.get_default_false('ignore_error') + job.debug = params.get_default_false('debug') + + if params.exists('retry') { + job.retry = u8(params.get_int('retry')!) + } + + // Set initial status + job.status = JobStatus{ + guid: job.guid + created: ourtime.now() + status: Status.created + } + + // // Set any additional parameters + // for key, value in params.get_map() { + // if key !in ['guid', 'actor', 'action', 'circle', 'context', 'agents', + // 'source', 'timeout_schedule', 'timeout', 'log', 'ignore_error', 'debug', 'retry'] { + // job.params[key] = value + // } + // } + + // Save the job + saved_job := p.job_db.set(job)! + + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.created guid:\'${saved_job.guid}\' id:${saved_job.id}') + } + .json { + println('{"action": "job.created", "guid": "${saved_job.guid}", "id": ${saved_job.id}}') + } + } +} + +// generate_random_id creates a random ID string +fn generate_random_id() !string { + random_bytes := rand.bytes(16)! + return hex.encode(random_bytes) +} diff --git a/lib/circles/actions/play/delete.v b/lib/circles/actions/play/delete.v new file mode 100644 index 00000000..e6bdba81 --- /dev/null +++ b/lib/circles/actions/play/delete.v @@ -0,0 +1,36 @@ +module play + +import freeflowuniverse.herolib.data.paramsparser + +// delete processes a job deletion action +pub fn (mut p Player) delete(params paramsparser.Params) ! { + if params.exists('id') { + id := u32(params.get_int('id')!) + p.job_db.delete(id)! + + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.deleted id:${id}') + } + .json { + println('{"action": "job.deleted", "id": ${id}}') + } + } + } else if params.exists('guid') { + guid := params.get('guid')! + p.job_db.delete_by_guid(guid)! + + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.deleted guid:\'${guid}\'') + } + .json { + println('{"action": "job.deleted", "guid": "${guid}"}') + } + } + } else { + return error('Either id or guid must be provided for job.delete') + } +} diff --git a/lib/circles/actions/play/get.v b/lib/circles/actions/play/get.v new file mode 100644 index 00000000..e8dbf3e9 --- /dev/null +++ b/lib/circles/actions/play/get.v @@ -0,0 +1,41 @@ +module play + +import freeflowuniverse.herolib.data.paramsparser +import json + +// get processes a job retrieval action +pub fn (mut p Player) get(params paramsparser.Params) ! { + mut job_result := '' + + if params.exists('id') { + id := u32(params.get_int('id')!) + job := p.job_db.get(id)! + + // Return result based on format + match p.return_format { + .heroscript { + job_result = '!!job.result id:${job.id} guid:\'${job.guid}\' actor:\'${job.actor}\' action:\'${job.action}\' status:\'${job.status.status}\'' + } + .json { + job_result = json.encode(job) + } + } + } else if params.exists('guid') { + guid := params.get('guid')! + job := p.job_db.get_by_guid(guid)! + + // Return result based on format + match p.return_format { + .heroscript { + job_result = '!!job.result id:${job.id} guid:\'${job.guid}\' actor:\'${job.actor}\' action:\'${job.action}\' status:\'${job.status.status}\'' + } + .json { + job_result = json.encode(job) + } + } + } else { + return error('Either id or guid must be provided for job.get') + } + + println(job_result) +} diff --git a/lib/circles/actions/play/list.v b/lib/circles/actions/play/list.v new file mode 100644 index 00000000..1c619409 --- /dev/null +++ b/lib/circles/actions/play/list.v @@ -0,0 +1,38 @@ +module play + +import freeflowuniverse.herolib.data.paramsparser +import json + +// list processes a job listing action +pub fn (mut p Player) list(params paramsparser.Params) ! { + // Get all job IDs + ids := p.job_db.list()! + + if params.get_default_false('verbose') { + // Get all jobs if verbose mode is enabled + jobs := p.job_db.getall()! + + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.list_result count:${jobs.len}') + for job in jobs { + println('!!job.item id:${job.id} guid:\'${job.guid}\' actor:\'${job.actor}\' action:\'${job.action}\' status:\'${job.status.status}\'') + } + } + .json { + println(json.encode(jobs)) + } + } + } else { + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.list_result count:${ids.len} ids:\'${ids.map(it.str()).join(",")}\'') + } + .json { + println('{"action": "job.list_result", "count": ${ids.len}, "ids": ${json.encode(ids)}}') + } + } + } +} diff --git a/lib/circles/actions/play/play_jobs.vsh b/lib/circles/actions/play/play_jobs.vsh new file mode 100644 index 00000000..fa56d9d2 --- /dev/null +++ b/lib/circles/actions/play/play_jobs.vsh @@ -0,0 +1,61 @@ +#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run + +import freeflowuniverse.herolib.circles.actions.play { Player, ReturnFormat } +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.application('play_jobs.vsh') + fp.version('v0.1.0') + fp.description('Process heroscript job commands for circles actions') + fp.skip_executable() + + input_file := fp.string('file', `f`, '', 'Input heroscript file') + input_text := fp.string('text', `t`, '', 'Input heroscript text') + actor := fp.string('actor', `a`, 'job', 'Actor name to process') + json_output := fp.bool('json', `j`, false, 'Output in JSON format') + help_requested := fp.bool('help', `h`, false, 'Show help message') + + if help_requested { + println(fp.usage()) + exit(0) + } + + additional_args := fp.finalize() or { + eprintln(err) + println(fp.usage()) + exit(1) + } + + // Determine return format + return_format := if json_output { ReturnFormat.json } else { ReturnFormat.heroscript } + + // Create a new player + mut player := play.new_player(actor, return_format) or { + eprintln('Failed to create player: ${err}') + exit(1) + } + + // Load heroscript from file or text + mut input := '' + mut is_text := false + + if input_file != '' { + input = input_file + is_text = false + } else if input_text != '' { + input = input_text + is_text = true + } else { + eprintln('Either --file or --text must be provided') + println(fp.usage()) + exit(1) + } + + // Process the heroscript + player.play(input, is_text) or { + eprintln('Failed to process heroscript: ${err}') + exit(1) + } +} diff --git a/lib/circles/actions/play/player.v b/lib/circles/actions/play/player.v new file mode 100644 index 00000000..cb936cbf --- /dev/null +++ b/lib/circles/actions/play/player.v @@ -0,0 +1,84 @@ +module play + +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.circles.base { Databases, SessionState, new_session } +import freeflowuniverse.herolib.circles.actions.db { JobDB, new_jobdb } +import os + +// ReturnFormat defines the format for returning results +pub enum ReturnFormat { + heroscript + json +} + +// Player is the main struct for processing heroscript actions +@[heap] +pub struct Player { +pub mut: + actor string // The name of the actor as used in heroscript + return_format ReturnFormat // Format for returning results + session_state SessionState // Session state for database operations + job_db JobDB // Job database handler +} + +// new_player creates a new Player instance +pub fn new_player(actor string, return_format ReturnFormat) !Player { + // Initialize session state + mut session_state := new_session( + name: 'circles' + path: os.join_path(os.home_dir(), '.herolib', 'circles') + )! + + // Create a new job database + mut job_db := new_jobdb(session_state)! + + return Player{ + actor: actor + return_format: return_format + session_state: session_state + job_db: job_db + } +} + +// play processes a heroscript text or playbook +pub fn (mut p Player) play(input string, is_text bool) ! { + mut plbook := if is_text { + playbook.new(text: input)! + } else { + playbook.new(path: input)! + } + + // Find all actions for this actor + filter := '${p.actor}.' + actions := plbook.find(filter: filter)! + + if actions.len == 0 { + println('No actions found for actor: ${p.actor}') + return + } + + // Process each action + for action in actions { + action_name := action.name.split('.')[1] + + // Call the appropriate method based on the action name + match action_name { + 'create' { p.create(action.params)! } + 'get' { p.get(action.params)! } + 'delete' { p.delete(action.params)! } + 'update_status' { p.update_status(action.params)! } + 'list' { p.list(action.params)! } + else { println('Unknown action: ${action_name}') } + } + } +} + +// create method is implemented in create.v + +// get method is implemented in get.v + +// delete method is implemented in delete.v + +// update_status method is implemented in update_status.v + +// list method is implemented in list.v diff --git a/lib/circles/actions/play/update_status.v b/lib/circles/actions/play/update_status.v new file mode 100644 index 00000000..390fc206 --- /dev/null +++ b/lib/circles/actions/play/update_status.v @@ -0,0 +1,64 @@ +module play + +import freeflowuniverse.herolib.data.paramsparser +import freeflowuniverse.herolib.circles.actions.models { JobStatus, Status } +import freeflowuniverse.herolib.data.ourtime + +// update_status processes a job status update action +pub fn (mut p Player) update_status(params paramsparser.Params) ! { + if params.exists('guid') && params.exists('status') { + guid := params.get('guid')! + status_str := params.get('status')! + + // Convert status string to Status enum + mut new_status := Status.created + match status_str { + 'created' { new_status = Status.created } + 'scheduled' { new_status = Status.scheduled } + 'planned' { new_status = Status.planned } + 'running' { new_status = Status.running } + 'error' { new_status = Status.error } + 'ok' { new_status = Status.ok } + else { + return error('Invalid status value: ${status_str}') + } + } + + // Create job status object + mut job_status := JobStatus{ + guid: guid + created: ourtime.now() + status: new_status + } + + // Set start time if provided + if params.exists('start') { + job_status.start = params.get_time('start')! + } else { + job_status.start = ourtime.now() + } + + // Set end time if provided + if params.exists('end') { + job_status.end = params.get_time('end')! + } else if new_status in [Status.error, Status.ok] { + // Automatically set end time for terminal statuses + job_status.end = ourtime.now() + } + + // Update job status + p.job_db.update_job_status(guid, job_status)! + + // Return result based on format + match p.return_format { + .heroscript { + println('!!job.status_updated guid:\'${guid}\' status:\'${status_str}\'') + } + .json { + println('{"action": "job.status_updated", "guid": "${guid}", "status": "${status_str}"}') + } + } + } else { + return error('Both guid and status must be provided for job.update_status') + } +} diff --git a/lib/circles/actions/specs.v b/lib/circles/actions/specs.v_ similarity index 79% rename from lib/circles/actions/specs.v rename to lib/circles/actions/specs.v_ index 7d504b8c..ad209fbf 100644 --- a/lib/circles/actions/specs.v +++ b/lib/circles/actions/specs.v_ @@ -1,32 +1,40 @@ module actions -// new creates a new Job instance -pub fn (mut m JobDB) new() Job +// From file: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions/db/job_db.v +pub struct JobDB { +pub mut: + db DBHandler[Job] +} + +pub fn new_jobdb(session_state SessionState) !JobDB {} + +pub fn (mut m JobDB) new() Job {} // set adds or updates a job -pub fn (mut m JobDB) set(job Job) !Job +pub fn (mut m JobDB) set(job Job) !Job {} // get retrieves a job by its ID -pub fn (mut m JobDB) get(id u32) !Job +pub fn (mut m JobDB) get(id u32) !Job {} // list returns all job IDs -pub fn (mut m JobDB) list() ![]u32 +pub fn (mut m JobDB) list() ![]u32 {} -// getall returns all jobs -pub fn (mut m JobDB) getall() ![]Job +pub fn (mut m JobDB) getall() ![]Job {} // delete removes a job by its ID -pub fn (mut m JobDB) delete(id u32) ! +pub fn (mut m JobDB) delete(id u32) ! {} // get_by_guid retrieves a job by its GUID -pub fn (mut m JobDB) get_by_guid(guid string) !Job +pub fn (mut m JobDB) get_by_guid(guid string) !Job {} // delete_by_guid removes a job by its GUID -pub fn (mut m JobDB) delete_by_guid(guid string) ! +pub fn (mut m JobDB) delete_by_guid(guid string) ! {} // update_job_status updates the status of a job -pub fn (mut m JobDB) update_job_status(guid string, new_status JobStatus) !Job +pub fn (mut m JobDB) update_job_status(guid string, new_status JobStatus) !Job {} + +// From file: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/actions/models/job.v // Job represents a task to be executed by an agent pub struct Job { pub mut: