psei
psei is a lightweight interpreter for Cambridge International AS & A Level Computer Science 9618-style pseudocode.
It implements a practical subset of Cambridge-style pseudocode and can be used for:
- running
.pseudofiles locally - experimenting with pseudocode in a REPL
- executing pseudocode from Python tests or applications
- building teaching examples
- checking common runtime and type errors
pseiis not an official Cambridge tool. It is also not a full exam-format validator. Its goal is to provide a useful, testable interpreter for a Cambridge-style pseudocode subset.
Quick start
Install as a CLI tool:
pipx install psei
Or install with pip:
python -m pip install psei
Create hello.pseudo:
OUTPUT "Hello"
Run it:
pseudo run hello.pseudo
Contents
- Features
- Installation
- Command-line usage
- REPL usage
- Python API usage
- Pseudocode examples
- Strict mode
- Resource limits
- File handling
- User-defined types
- Procedures and functions
- Object-oriented pseudocode
- Built-in functions
- Errors
- Development
- Project structure
- Current limitations
Features
Basic language features
Supported:
DECLARECONSTANT- assignment using
← INPUTOUTPUT- comments using
//
Basic data types
Supported data types:
INTEGERREALCHARSTRINGBOOLEANDATE
Expressions and operators
Arithmetic operators:
+-*/DIVMOD
Comparison operators:
=<><<=>>=
Logic operators:
ANDORNOT
String concatenation:
&
AND and OR use short-circuit evaluation.
Selection and iteration
Supported control structures:
IF ... THEN ... ELSE ... ENDIFCASE OF ... OTHERWISE ... ENDCASEFOR ... TO ... STEP ... NEXTWHILE ... ENDWHILEREPEAT ... UNTIL
Arrays
Supported:
- one-dimensional arrays
- two-dimensional arrays
- explicit lower and upper bounds
- bounds checking
- whole-array assignment with copy semantics
User-defined types
Supported:
- enumerated types
- pointer types
- set types
- record types
- class/object types
Procedures and functions
Supported:
PROCEDUREFUNCTIONCALLRETURNBYVALBYREF
File handling
Supported text file operations:
OPENFILE ... FOR READOPENFILE ... FOR WRITEOPENFILE ... FOR APPENDREADFILEWRITEFILECLOSEFILEEOF(...)
Supported random file operations:
OPENFILE ... FOR RANDOMSEEKGETRECORDPUTRECORD
Object-oriented subset
Supported:
CLASS ... ENDCLASSPUBLICPRIVATEINHERITSSUPER- constructors using
PROCEDURE NEW(...) - object creation using
NEW ClassName(...) - method calls using
Object.Method(...)
Installation
1. Clone the repository
git clone <repo-url>
cd psei
2. Create a virtual environment
Linux / macOS:
python -m venv .venv
source .venv/bin/activate
Windows PowerShell:
python -m venv .venv
.venv\Scripts\Activate.ps1
3. Install the package
For normal use:
python -m pip install --upgrade pip
python -m pip install -e .
For development:
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Python requirement:
Python >= 3.10
Command-line usage
The package provides two equivalent console commands:
pseudo
psei
Run a pseudocode file
pseudo run path/to/program.pseudo
Example:
pseudo run examples/passing/declare_assign_output.pseudo
You can also run it as a Python module:
python -m psei run examples/passing/declare_assign_output.pseudo
Run with strict mode
pseudo run path/to/program.pseudo --strict
Example:
pseudo run examples/errors/strict_ascii_assignment.pseudo --strict
CLI error behavior
If a program produces a lexical, parse or runtime error:
- the error message is written to
stderr - the process exits with status code
1
REPL usage
Start the REPL:
pseudo repl
Or:
python -m psei repl
Start the REPL in strict mode:
pseudo repl --strict
Available REPL commands:
:help show help
:vars show variables in the current runtime
:reset reset the runtime
:quit exit
:exit exit
Example session:
pseudo> DECLARE X : INTEGER
pseudo> X ← 10
pseudo> OUTPUT X + 5
15
pseudo> :vars
X : INTEGER = 10
pseudo> :quit
For multi-line constructs such as IF, WHILE, PROCEDURE, FUNCTION and CLASS, the REPL waits until the block is complete.
Python API usage
psei can also be used as a Python library.
Run source code from a string
from psei import run_source
source = """
DECLARE Counter : INTEGER
Counter ← 0
Counter ← Counter + 1
OUTPUT Counter
"""
run_source(source)
Output:
1
Capture OUTPUT
By default, OUTPUT uses Python's print. To capture output in tests, create a custom Runtime.
from psei import Runtime, run_source
output = []
runtime = Runtime(output_writer=output.append)
run_source("""
OUTPUT "Hello"
OUTPUT "World"
""", runtime)
assert output == ["Hello", "World"]
Provide INPUT
from psei import Runtime, run_source
inputs = iter(["41"])
output = []
runtime = Runtime(
input_provider=lambda: next(inputs),
output_writer=output.append,
)
run_source("""
DECLARE X : INTEGER
INPUT X
OUTPUT X + 1
""", runtime)
assert output == ["42"]
Run a file
from psei import run_file
run_file("path/to/program.pseudo")
run_file() uses a local file system rooted at the directory containing the pseudocode file.
Pseudocode examples
Declaration, assignment and output
DECLARE Counter : INTEGER
Counter ← 0
Counter ← Counter + 1
OUTPUT Counter
Output:
1
Arrays and loops
DECLARE Values : ARRAY[1:4] OF INTEGER
DECLARE I : INTEGER
DECLARE Total : INTEGER
Total ← 0
FOR I ← 1 TO 4
Values[I] ← I * 2
Total ← Total + Values[I]
NEXT I
OUTPUT "Total=", Total
Output:
Total=20
IF statement
DECLARE Score : INTEGER
Score ← 75
IF Score >= 50 THEN
OUTPUT "Pass"
ELSE
OUTPUT "Fail"
ENDIF
Output:
Pass
CASE statement
DECLARE Mark : INTEGER
Mark ← 75
CASE OF Mark
0 TO 49 : OUTPUT "Fail"
50 TO 69 : OUTPUT "Pass"
70 TO 100 : OUTPUT "Distinction"
OTHERWISE : OUTPUT "Invalid"
ENDCASE
Output:
Distinction
WHILE loop
DECLARE Number : INTEGER
Number ← 27
WHILE Number > 9
Number ← Number - 9
ENDWHILE
OUTPUT Number
Output:
9
REPEAT ... UNTIL loop
DECLARE Number : INTEGER
Number ← 0
REPEAT
Number ← Number + 1
UNTIL Number = 3
OUTPUT Number
Output:
3
Strict mode
Strict mode is a limited Cambridge-style guardrail. It is not a complete style or exam-format validator.
Enable strict mode from the command line:
pseudo run program.pseudo --strict
Enable strict mode from Python:
from psei import Runtime, run_source
runtime = Runtime(strict=True)
run_source("""
DECLARE X : INTEGER
X ← 1
""", runtime, strict=True)
Strict mode currently enforces:
- assignment must use
← - ASCII assignment
<-is rejected - variables must be declared before assignment
- identifiers may contain only ASCII letters, digits and
_ - identifiers must start with an ASCII letter
Non-strict mode currently allows:
- assignment using either
←or<- - assignment to undeclared variables, creating them with inferred types
- non-ASCII alphabetic characters in identifiers
Both modes still perform core runtime checks, including:
- assignment type checks
- constant immutability
- array bounds checks
- unknown type checks
- record field checks
- enumerated type checks
- file mode checks
- division by zero checks
- Boolean condition checks
- procedure/function arity checks
- function return type checks
BYREFlvalue and type checks
Resource limits
Runtime applies conservative execution limits by default to protect the interpreter from runaway programs.
Default limits:
Runtime(
max_steps=1_000_000,
max_array_elements=1_000_000,
max_call_depth=1_000,
max_output_chars=1_000_000,
)
| Option | Purpose |
|---|---|
max_steps |
Limits executed statements and loop iterations |
max_array_elements |
Limits the number of elements in a single array |
max_call_depth |
Limits procedure, function and method call depth |
max_output_chars |
Limits the total number of output characters |
Example:
from psei import Runtime, run_source
from psei.errors import PseudoRuntimeError
runtime = Runtime(max_steps=1000)
try:
run_source("""
WHILE TRUE
ENDWHILE
""", runtime)
except PseudoRuntimeError as error:
print(error)
To disable a specific limit, pass None:
runtime = Runtime(max_steps=None)
These limits are not a full security sandbox. If you run untrusted code in production, also use process-level timeouts, memory limits, containers or operating-system sandboxing.
File handling
File handling with run_source()
run_source() uses an in-memory file system by default.
This means:
- no real files are created
- execution is deterministic
- tests and REPL usage are easier to manage
Example:
DECLARE Line : STRING
OPENFILE "Log.txt" FOR WRITE
WRITEFILE "Log.txt", "Hello"
CLOSEFILE "Log.txt"
OPENFILE "Log.txt" FOR READ
READFILE "Log.txt", Line
OUTPUT Line
CLOSEFILE "Log.txt"
File handling with run_file()
run_file() uses a local file system.
Important behavior:
- relative paths are resolved beside the pseudocode source file
- absolute paths are rejected
- paths escaping the program directory are rejected
- text files are read and written as UTF-8
- random files are persisted as JSON
Text file example
DECLARE LineOfText : STRING
OPENFILE "FileA.txt" FOR WRITE
WRITEFILE "FileA.txt", "First"
WRITEFILE "FileA.txt", "Second"
CLOSEFILE "FileA.txt"
OPENFILE "FileA.txt" FOR READ
WHILE NOT EOF("FileA.txt")
READFILE "FileA.txt", LineOfText
OUTPUT LineOfText
ENDWHILE
CLOSEFILE "FileA.txt"
Output:
First
Second
Random file example
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil : StudentRecord
DECLARE Loaded : StudentRecord
Pupil.LastName ← "Johnson"
Pupil.YearGroup ← 6
OPENFILE "StudentFile.Dat" FOR RANDOM
SEEK "StudentFile.Dat", 10
PUTRECORD "StudentFile.Dat", Pupil
SEEK "StudentFile.Dat", 10
GETRECORD "StudentFile.Dat", Loaded
CLOSEFILE "StudentFile.Dat"
OUTPUT Loaded.LastName, ":", Loaded.YearGroup
Output:
Johnson:6
Random files can store:
- scalar values
- arrays
- records
- sets
Random files cannot store:
- object instances
- pointer values
User-defined types
Enumerated types
TYPE Season = (Spring, Summer, Autumn, Winter)
DECLARE ThisSeason : Season
ThisSeason ← Summer
OUTPUT ThisSeason
Output:
Summer
Enumerated values are case-insensitive.
If a variable has the same name as an enumerated value, the variable shadows the enumerated value.
Record types
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE FirstName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil : StudentRecord
Pupil.LastName ← "Johnson"
Pupil.FirstName ← "Leroy"
Pupil.YearGroup ← 6
OUTPUT Pupil.LastName, ",", Pupil.FirstName, ",", Pupil.YearGroup
Output:
Johnson,Leroy,6
Record assignment
Record assignment uses copy semantics. Assigning one record to another does not alias their fields.
TYPE StudentRecord
DECLARE LastName : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Pupil1 : StudentRecord
DECLARE Pupil2 : StudentRecord
Pupil1.LastName ← "Johnson"
Pupil1.YearGroup ← 6
Pupil2 ← Pupil1
Pupil1.YearGroup ← 7
OUTPUT Pupil2.YearGroup
OUTPUT Pupil1.YearGroup
Output:
6
7
Arrays of records
TYPE StudentRecord
DECLARE Name : STRING
DECLARE YearGroup : INTEGER
ENDTYPE
DECLARE Form : ARRAY[1:2] OF StudentRecord
Form[1].Name ← "Ali"
Form[1].YearGroup ← 12
Form[2].Name ← "Mei"
Form[2].YearGroup ← 11
OUTPUT Form[1].Name, ":", Form[1].YearGroup
OUTPUT Form[2].Name, ":", Form[2].YearGroup
Output:
Ali:12
Mei:11
Pointer types
TYPE TIntPointer = ^INTEGER
DECLARE X : INTEGER
DECLARE P : TIntPointer
X ← 10
P ← ^X
OUTPUT P^
P^ ← 20
OUTPUT X
Output:
10
20
Set types
TYPE LetterSet = SET OF CHAR
DEFINE Vowels ('A','E','I','O','U') : LetterSet
OUTPUT Vowels
Sets currently support declaration, definition, assignment and placeholder output. A full set operation library is not implemented yet.
Procedures and functions
Procedure without parameters
PROCEDURE Hello()
OUTPUT "Hello"
ENDPROCEDURE
CALL Hello()
Output:
Hello
Procedure with parameters
PROCEDURE Square(Size : INTEGER)
FOR Side ← 1 TO 4
OUTPUT "Side length=", Size
NEXT Side
ENDPROCEDURE
CALL Square(100)
BYVAL
Parameters are passed by value by default.
PROCEDURE AddOne(X : INTEGER)
X ← X + 1
ENDPROCEDURE
DECLARE A : INTEGER
A ← 5
CALL AddOne(A)
OUTPUT A
Output:
5
BYREF
BYREF parameters modify the caller's variable, array element, record field, object property or pointer dereference.
PROCEDURE AddOne(BYREF X : INTEGER)
X ← X + 1
ENDPROCEDURE
DECLARE A : INTEGER
A ← 5
CALL AddOne(A)
OUTPUT A
Output:
6
The current passing mode continues across comma-separated parameters until another BYVAL or BYREF keyword appears.
PROCEDURE Swap(BYREF X : INTEGER, Y : INTEGER)
DECLARE Temp : INTEGER
Temp ← X
X ← Y
Y ← Temp
ENDPROCEDURE
In the example above, both X and Y are passed by reference.
To reset the mode explicitly:
PROCEDURE Test(BYREF X : INTEGER, BYVAL Y : INTEGER)
X ← 10
Y ← 20
ENDPROCEDURE
Functions
FUNCTION Max(Number1 : INTEGER, Number2 : INTEGER) RETURNS INTEGER
IF Number1 > Number2 THEN
RETURN Number1
ELSE
RETURN Number2
ENDIF
ENDFUNCTION
OUTPUT "Maximum=", Max(10, 20)
Output:
Maximum=20
Function calls must be used as part of an expression.
Valid:
OUTPUT Max(10, 20)
X ← Max(10, 20)
Invalid:
Max(10, 20)
Function parameters cannot be passed BYREF.
Object-oriented pseudocode
Basic class
CLASS Player
PRIVATE Attempts : INTEGER
Attempts ← 3
PUBLIC PROCEDURE SetAttempts(Number : INTEGER)
Attempts ← Number
ENDPROCEDURE
PUBLIC FUNCTION GetAttempts() RETURNS INTEGER
RETURN Attempts
ENDFUNCTION
ENDCLASS
DECLARE P : Player
P ← NEW Player()
OUTPUT P.GetAttempts()
P.SetAttempts(5)
OUTPUT P.GetAttempts()
Output:
3
5
Constructors
Constructors are procedures named NEW.
CLASS Pet
PRIVATE Name : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING)
Name ← GivenName
ENDPROCEDURE
PUBLIC FUNCTION GetName() RETURNS STRING
RETURN Name
ENDFUNCTION
ENDCLASS
MyPet ← NEW Pet("Kitty")
OUTPUT MyPet.GetName()
Output:
Kitty
Inheritance and SUPER
CLASS Pet
PRIVATE Name : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING)
Name ← GivenName
ENDPROCEDURE
PUBLIC FUNCTION GetName() RETURNS STRING
RETURN Name
ENDFUNCTION
ENDCLASS
CLASS Cat INHERITS Pet
PRIVATE Breed : STRING
PUBLIC PROCEDURE NEW(GivenName : STRING, GivenBreed : STRING)
SUPER.NEW(GivenName)
Breed ← GivenBreed
ENDPROCEDURE
PUBLIC FUNCTION GetBreed() RETURNS STRING
RETURN Breed
ENDFUNCTION
ENDCLASS
MyCat ← NEW Cat("Kitty", "Shorthaired")
OUTPUT MyCat.GetName()
OUTPUT MyCat.GetBreed()
Output:
Kitty
Shorthaired
PUBLIC and PRIVATE
PUBLIC members can be accessed from outside the object.
PRIVATE members can only be accessed from methods or initializers of the class that declares them.
Example:
CLASS Account
PRIVATE Balance : INTEGER
PUBLIC PROCEDURE NEW(StartBalance : INTEGER)
Balance ← StartBalance
ENDPROCEDURE
PUBLIC FUNCTION GetBalance() RETURNS INTEGER
RETURN Balance
ENDFUNCTION
ENDCLASS
A ← NEW Account(100)
OUTPUT A.GetBalance()
Output:
100
This external access raises a runtime error:
OUTPUT A.Balance
Built-in functions
Supported built-in functions:
| Function | Description |
|---|---|
RIGHT(ThisString, x) |
Returns the rightmost x characters |
MID(ThisString, x, y) |
Returns a substring of length y starting at one-based position x |
LENGTH(ThisString) |
Returns the length of a string |
LCASE(ThisChar) |
Converts ASCII uppercase letters to lowercase; other characters are unchanged |
UCASE(ThisChar) |
Converts ASCII lowercase letters to uppercase; other characters are unchanged |
INT(x) |
Returns the integer part of a number |
RAND(x) |
Returns a random REAL in the range [0, x) |
EOF(file) |
Returns whether an open text file has reached end-of-file |
Example:
OUTPUT RIGHT("ABCDEFGH", 3)
OUTPUT MID("ABCDEFGH", 2, 3)
OUTPUT LENGTH("Happy Days")
OUTPUT UCASE('h')
OUTPUT LCASE('W')
OUTPUT INT(27.5415)
Output:
FGH
BCD
10
H
w
27
Errors
Error classes are available from psei.errors:
from psei.errors import (
PseudoError,
LexError,
ParseError,
IncompleteInput,
PseudoRuntimeError,
)
| Error type | Meaning |
|---|---|
LexError |
Lexical error, such as an invalid character or malformed literal |
ParseError |
Syntax error |
IncompleteInput |
Used by the REPL when a block is incomplete |
PseudoRuntimeError |
Runtime error, such as type mismatch, division by zero or array bounds error |
Example:
from psei import run_source
from psei.errors import PseudoError
try:
run_source("""
DECLARE X : INTEGER
X ← "not an integer"
""")
except PseudoError as error:
print(error)
Development
Install development dependencies:
python -m pip install -e ".[dev]"
Run tests:
python -m pytest -q
The repository includes example programs:
examples/passing/
examples/errors/
examples/passing/ contains programs that should run successfully.
Each passing example has a matching .out file containing expected output.
examples/errors/ contains programs that should raise errors.
examples/errors/manifest.json records the expected error type for each error example.
Project structure
psei/
├── examples/
│ ├── passing/
│ └── errors/
├── src/
│ └── psei/
│ ├── lexer.py
│ ├── parser.py
│ ├── ast_nodes.py
│ ├── interpreter.py
│ ├── runner.py
│ ├── cli.py
│ ├── repl.py
│ ├── runtime/
│ │ ├── core.py
│ │ ├── environment.py
│ │ ├── files.py
│ │ ├── oop.py
│ │ ├── serialization.py
│ │ ├── types.py
│ │ └── values.py
│ ├── tokens.py
│ └── values.py
├── tests/
├── pyproject.toml
└── README.md
Main modules:
| File or directory | Purpose |
|---|---|
lexer.py |
Lexical analysis |
parser.py |
Parsing and AST construction |
ast_nodes.py |
AST node definitions |
interpreter.py |
AST execution |
runtime/core.py |
Runtime object, scopes and limits |
runtime/environment.py |
Variables, constants and references |
runtime/types.py |
Type system, coercion and cloning |
runtime/files.py |
Text and random file abstractions |
runtime/oop.py |
Class and object runtime structures |
runner.py |
run_source() and run_file() |
cli.py |
Command-line entry point |
repl.py |
Interactive REPL |
Current limitations
psei implements a practical Cambridge-style pseudocode subset. It is not a complete programming language implementation or a full Cambridge exam-format checker.
Not fully implemented:
- the full ADT library mentioned by the Cambridge syllabus, including:
- stack
- queue
- linked list
- dictionary
- binary tree
- full style validation, such as:
- checking that keywords are uppercase
- checking indentation
- checking mixed-case identifier style
- a complete set operation library
- full compiler-style static analysis
- process-level sandboxing
If you execute untrusted code, consider using:
- subprocess timeouts
- operating-system memory limits
- containers
- API-level request limits
- process isolation
Minimal example
Create hello.pseudo:
DECLARE Name : STRING
Name ← "Cambridge pseudocode"
OUTPUT "Hello, ", Name
Run it:
pseudo run hello.pseudo
Output:
Hello, Cambridge pseudocode
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file psei-0.1.1.tar.gz.
File metadata
- Download URL: psei-0.1.1.tar.gz
- Upload date:
- Size: 53.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce67ada1221fec1c8521f8caf6ea047430fac337651e1641adf36e8bd65ae4f7
|
|
| MD5 |
b227c67fee92cddf35a31d3d01690a0e
|
|
| BLAKE2b-256 |
37b77f7d1d6b43088aa149cdd7bbfad8babbb2cca2d11fbbcdcac89c188a2f40
|
Provenance
The following attestation bundles were made for psei-0.1.1.tar.gz:
Publisher:
publish.yml on luke-tangh/psei
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psei-0.1.1.tar.gz -
Subject digest:
ce67ada1221fec1c8521f8caf6ea047430fac337651e1641adf36e8bd65ae4f7 - Sigstore transparency entry: 2280361845
- Sigstore integration time:
-
Permalink:
luke-tangh/psei@e84d34a0bdf144a7d50447afc16a62104a43ee07 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/luke-tangh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e84d34a0bdf144a7d50447afc16a62104a43ee07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file psei-0.1.1-py3-none-any.whl.
File metadata
- Download URL: psei-0.1.1-py3-none-any.whl
- Upload date:
- Size: 47.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e11e881634c7d6e91bf7c3e1bce36aaee9e4242cf08a8b8c254edc0d38f8ad1
|
|
| MD5 |
ce8762433feeef6b3f735961287249eb
|
|
| BLAKE2b-256 |
baf8247ab9f7b3a50fe18bba9f5a6fe82e690caec42683ede8f1d3bc6d85f397
|
Provenance
The following attestation bundles were made for psei-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on luke-tangh/psei
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psei-0.1.1-py3-none-any.whl -
Subject digest:
9e11e881634c7d6e91bf7c3e1bce36aaee9e4242cf08a8b8c254edc0d38f8ad1 - Sigstore transparency entry: 2280361855
- Sigstore integration time:
-
Permalink:
luke-tangh/psei@e84d34a0bdf144a7d50447afc16a62104a43ee07 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/luke-tangh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e84d34a0bdf144a7d50447afc16a62104a43ee07 -
Trigger Event:
release
-
Statement type: